diff --git a/Cargo.lock b/Cargo.lock index 969d4d779b6..7a9463dad87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7776,7 +7776,7 @@ dependencies = [ [[package]] name = "iota-rust-sdk" version = "0.0.0" -source = "git+https://github.com/iotaledger/iota-rust-sdk.git?rev=2ba6b293bdede769a1d9b4d1aecaede2ff7682dd#2ba6b293bdede769a1d9b4d1aecaede2ff7682dd" +source = "git+https://github.com/iotaledger/iota-rust-sdk.git?rev=175d28d69a3ff8a4c1b936df5ef5d216672dfd26#175d28d69a3ff8a4c1b936df5ef5d216672dfd26" dependencies = [ "base64ct", "bcs", diff --git a/Cargo.toml b/Cargo.toml index 5b2efddbe69..c6c67fde42d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -414,7 +414,7 @@ iota-rosetta = { path = "crates/iota-rosetta" } iota-rpc-loadgen = { path = "crates/iota-rpc-loadgen" } iota-sdk = { path = "crates/iota-sdk" } # core-types with json format for REST API -iota-sdk2 = { package = "iota-rust-sdk", git = "https://github.com/iotaledger/iota-rust-sdk.git", rev = "2ba6b293bdede769a1d9b4d1aecaede2ff7682dd", features = ["hash", "serde", "schemars"] } +iota-sdk2 = { package = "iota-rust-sdk", git = "https://github.com/iotaledger/iota-rust-sdk.git", rev = "175d28d69a3ff8a4c1b936df5ef5d216672dfd26", features = ["hash", "serde", "schemars"] } iota-simulator = { path = "crates/iota-simulator" } iota-snapshot = { path = "crates/iota-snapshot" } iota-source-validation = { path = "crates/iota-source-validation" } diff --git a/crates/iota-core/src/authority.rs b/crates/iota-core/src/authority.rs index 60d3715868b..c71b07a07a4 100644 --- a/crates/iota-core/src/authority.rs +++ b/crates/iota-core/src/authority.rs @@ -66,7 +66,7 @@ use iota_types::{ TransactionEvents, VerifiedCertifiedTransactionEffects, VerifiedSignedTransactionEffects, }, error::{ExecutionError, IotaError, IotaResult, UserInputError}, - event::{Event, EventID, SystemEpochInfoEventV1}, + event::{Event, EventID, SystemEpochInfoEvent, SystemEpochInfoEventV1, SystemEpochInfoEventV2}, executable_transaction::VerifiedExecutableTransaction, execution_config_utils::to_binary_config, execution_status::ExecutionStatus, @@ -1730,6 +1730,7 @@ impl AuthorityState { // make a gas object if one was not provided let mut gas_object_refs = transaction.gas().to_vec(); + let reference_gas_price = epoch_store.reference_gas_price(); let ((gas_status, checked_input_objects), mock_gas) = if transaction.gas().is_empty() { let sender = transaction.sender(); // use a 1B iota coin @@ -1747,7 +1748,7 @@ impl AuthorityState { ( iota_transaction_checks::check_transaction_input_with_given_gas( epoch_store.protocol_config(), - epoch_store.reference_gas_price(), + reference_gas_price, &transaction, input_objects, receiving_objects, @@ -1760,7 +1761,7 @@ impl AuthorityState { ( iota_transaction_checks::check_transaction_input( epoch_store.protocol_config(), - epoch_store.reference_gas_price(), + reference_gas_price, &transaction, input_objects, &receiving_objects, @@ -1987,7 +1988,7 @@ impl AuthorityState { if transaction.gas().is_empty() { iota_transaction_checks::check_transaction_input_with_given_gas( epoch_store.protocol_config(), - epoch_store.reference_gas_price(), + reference_gas_price, &transaction, input_objects, receiving_objects, @@ -1997,7 +1998,7 @@ impl AuthorityState { } else { iota_transaction_checks::check_transaction_input( epoch_store.protocol_config(), - epoch_store.reference_gas_price(), + reference_gas_price, &transaction, input_objects, &receiving_objects, @@ -4526,7 +4527,7 @@ impl AuthorityState { epoch_start_timestamp_ms: CheckpointTimestamp, ) -> anyhow::Result<( IotaSystemState, - Option, + Option, TransactionEffects, )> { let mut txns = Vec::new(); @@ -4583,16 +4584,30 @@ impl AuthorityState { )); }; - txns.push(EndOfEpochTransactionKind::new_change_epoch( - next_epoch, - next_epoch_protocol_version, - gas_cost_summary.storage_cost, - gas_cost_summary.computation_cost, - gas_cost_summary.storage_rebate, - gas_cost_summary.non_refundable_storage_fee, - epoch_start_timestamp_ms, - next_epoch_system_package_bytes, - )); + if config.protocol_defined_base_fee() { + txns.push(EndOfEpochTransactionKind::new_change_epoch_v2( + next_epoch, + next_epoch_protocol_version, + gas_cost_summary.storage_cost, + gas_cost_summary.computation_cost, + gas_cost_summary.computation_cost_burned, + gas_cost_summary.storage_rebate, + gas_cost_summary.non_refundable_storage_fee, + epoch_start_timestamp_ms, + next_epoch_system_package_bytes, + )); + } else { + txns.push(EndOfEpochTransactionKind::new_change_epoch( + next_epoch, + next_epoch_protocol_version, + gas_cost_summary.storage_cost, + gas_cost_summary.computation_cost, + gas_cost_summary.storage_rebate, + gas_cost_summary.non_refundable_storage_fee, + epoch_start_timestamp_ms, + next_epoch_system_package_bytes, + )); + } let tx = VerifiedTransaction::new_end_of_epoch_transaction(txns); @@ -4609,6 +4624,7 @@ impl AuthorityState { ?next_epoch_protocol_version, ?next_epoch_system_packages, computation_cost=?gas_cost_summary.computation_cost, + computation_cost_burned=?gas_cost_summary.computation_cost_burned, storage_cost=?gas_cost_summary.storage_cost, storage_rebate=?gas_cost_summary.storage_rebate, non_refundable_storage_fee=?gas_cost_summary.non_refundable_storage_fee, @@ -4654,16 +4670,13 @@ impl AuthorityState { self.prepare_certificate(&execution_guard, &executable_tx, input_objects, epoch_store)?; let system_obj = get_iota_system_state(&temporary_store.written) .expect("change epoch tx must write to system object"); - // Find the SystemEpochInfoEventV1 emitted by the advance_epoch transaction. + // Find the SystemEpochInfoEvent emitted by the advance_epoch transaction. let system_epoch_info_event = temporary_store .events .data .iter() .find(|event| event.is_system_epoch_info_event()) - .map(|event| { - bcs::from_bytes::(&event.contents) - .expect("event deserialization should succeed as type was pre-validated") - }); + .map(|event| SystemEpochInfoEvent::from(event)); // The system epoch info event can be `None` in case if the `advance_epoch` // Move function call failed and was executed in the safe mode. assert!(system_epoch_info_event.is_some() || system_obj.safe_mode()); diff --git a/crates/iota-core/src/authority/authority_per_epoch_store.rs b/crates/iota-core/src/authority/authority_per_epoch_store.rs index 5fb00882a36..60a2e8fead0 100644 --- a/crates/iota-core/src/authority/authority_per_epoch_store.rs +++ b/crates/iota-core/src/authority/authority_per_epoch_store.rs @@ -1103,7 +1103,12 @@ impl AuthorityPerEpochStore { } pub fn reference_gas_price(&self) -> u64 { - self.epoch_start_state().reference_gas_price() + // Determine what to use as reference gas price based on protocol config. + if self.protocol_config().protocol_defined_base_fee() { + self.protocol_config().base_gas_price() + } else { + self.epoch_start_state().reference_gas_price() + } } pub fn protocol_version(&self) -> ProtocolVersion { diff --git a/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs b/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs index b1e4f3cfb38..aba6719606e 100644 --- a/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs +++ b/crates/iota-core/src/checkpoints/checkpoint_executor/tests.rs @@ -466,7 +466,7 @@ async fn sync_end_of_epoch_checkpoint( epoch_commitments: vec![ECMHLiveObjectSetDigest::default().into()], // Do not simulate supply changes in tests. // We would need to build this checkpoint after the below execution of advance_epoch to - // obtain this number from the SystemEpochInfoEventV1. + // obtain this number from the SystemEpochInfoEvent. epoch_supply_change: 0, }), ); diff --git a/crates/iota-core/src/checkpoints/mod.rs b/crates/iota-core/src/checkpoints/mod.rs index 532c5b1e002..008dd1440c6 100644 --- a/crates/iota-core/src/checkpoints/mod.rs +++ b/crates/iota-core/src/checkpoints/mod.rs @@ -33,7 +33,7 @@ use iota_types::{ digests::{CheckpointContentsDigest, CheckpointDigest}, effects::{TransactionEffects, TransactionEffectsAPI}, error::{IotaError, IotaResult}, - event::SystemEpochInfoEventV1, + event::SystemEpochInfoEvent, executable_transaction::VerifiedExecutableTransaction, gas::GasCostSummary, iota_system_state::{ @@ -1452,9 +1452,8 @@ impl CheckpointBuilder { // SAFETY: The number of minted and burnt tokens easily fit into an i64 and due // to those small numbers, no overflows will occur during conversion or // subtraction. - let epoch_supply_change = system_epoch_info_event.map_or(0, |event| { - event.minted_tokens_amount as i64 - event.burnt_tokens_amount as i64 - }); + let epoch_supply_change = + system_epoch_info_event.map_or(0, |event| event.supply_change()); let committee = system_state_obj .get_current_epoch_committee() @@ -1581,7 +1580,7 @@ impl CheckpointBuilder { checkpoint_effects: &mut Vec, signatures: &mut Vec>, checkpoint: CheckpointSequenceNumber, - ) -> anyhow::Result<(IotaSystemState, Option)> { + ) -> anyhow::Result<(IotaSystemState, Option)> { let (system_state, system_epoch_info_event, effects) = self .state .create_and_execute_advance_epoch_tx( diff --git a/crates/iota-core/src/rest_index.rs b/crates/iota-core/src/rest_index.rs index f3d22bb83e4..c89b442891d 100644 --- a/crates/iota-core/src/rest_index.rs +++ b/crates/iota-core/src/rest_index.rs @@ -187,14 +187,14 @@ impl IndexStoreTables { // Iterate through available, executed checkpoints that have yet to be pruned // to initialize checkpoint and transaction based indexes. - if let Some(highest_executed_checkpint) = + if let Some(highest_executed_checkpoint) = checkpoint_store.get_highest_executed_checkpoint_seq_number()? { let lowest_available_checkpoint = checkpoint_store .get_highest_pruned_checkpoint_seq_number()? .saturating_add(1); - let checkpoint_range = lowest_available_checkpoint..=highest_executed_checkpint; + let checkpoint_range = lowest_available_checkpoint..=highest_executed_checkpoint; info!( "Indexing {} checkpoints in range {checkpoint_range:?}", diff --git a/crates/iota-core/src/unit_tests/authority_tests.rs b/crates/iota-core/src/unit_tests/authority_tests.rs index ced5e1510ec..af598375d4b 100644 --- a/crates/iota-core/src/unit_tests/authority_tests.rs +++ b/crates/iota-core/src/unit_tests/authority_tests.rs @@ -327,6 +327,8 @@ async fn test_dev_inspect_object_by_bytes() { assert_eq!(effects.mutated().len(), 1); assert!(effects.deleted().is_empty()); assert!(effects.gas_cost_summary().computation_cost > 0); + assert!(effects.gas_cost_summary().computation_cost_burned > 0); + let mut results = results.unwrap(); assert_eq!(results.len(), 1); let exec_results = results.pop().unwrap(); @@ -395,6 +397,7 @@ async fn test_dev_inspect_object_by_bytes() { assert_eq!(effects.mutated().len(), 1); assert!(effects.deleted().is_empty()); assert!(effects.gas_cost_summary().computation_cost > 0); + assert!(effects.gas_cost_summary().computation_cost_burned > 0); let mut results = results.unwrap(); assert_eq!(results.len(), 1); @@ -499,6 +502,7 @@ async fn test_dev_inspect_unowned_object() { assert_eq!(effects.mutated().len(), 2); assert!(effects.deleted().is_empty()); assert!(effects.gas_cost_summary().computation_cost > 0); + assert!(effects.gas_cost_summary().computation_cost_burned > 0); let mut results = results.unwrap(); assert_eq!(results.len(), 1); @@ -613,6 +617,8 @@ async fn test_dev_inspect_dynamic_field() { // nothing is deleted assert!(effects.deleted().is_empty()); assert!(effects.gas_cost_summary().computation_cost > 0); + assert!(effects.gas_cost_summary().computation_cost_burned > 0); + assert_eq!(results.len(), 1); let exec_results = results.pop().unwrap(); let IotaExecutionResult { diff --git a/crates/iota-core/src/unit_tests/gas_tests.rs b/crates/iota-core/src/unit_tests/gas_tests.rs index 360b7c33cd9..5c76fae420d 100644 --- a/crates/iota-core/src/unit_tests/gas_tests.rs +++ b/crates/iota-core/src/unit_tests/gas_tests.rs @@ -316,6 +316,7 @@ async fn test_computation_oog_storage_ok_single_gas_coin() -> IotaResult { |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage() as u64; assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.storage_cost, summary.storage_rebate); @@ -344,6 +345,7 @@ async fn test_computation_oog_storage_ok_multi_gas_coins() -> IotaResult { |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage() as u64; assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.non_refundable_storage_fee, 0); @@ -373,6 +375,7 @@ async fn test_computation_oog_storage_ok_computation_is_entire_budget() -> IotaR |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage() as u64; assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.storage_cost, summary.storage_rebate); @@ -404,6 +407,7 @@ async fn test_computation_ok_storage_oog_single_gas_coin() -> IotaResult { |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage() as u64; assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.storage_cost, summary.storage_rebate); @@ -435,6 +439,7 @@ async fn test_computation_ok_storage_oog_multi_gas_coins() -> IotaResult { |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage(); assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.non_refundable_storage_fee, 0); @@ -466,6 +471,7 @@ async fn test_computation_ok_storage_oog_computation_is_entire_budget() -> IotaR |summary, initial_value, final_value| { let gas_used = summary.net_gas_usage() as u64; assert!(summary.computation_cost > 0); + assert!(summary.computation_cost_burned > 0); assert!(summary.storage_cost > 0); assert!(summary.storage_rebate > 0); assert_eq!(summary.storage_cost, summary.storage_rebate); @@ -504,6 +510,7 @@ async fn test_native_transfer_sufficient_gas() -> IotaResult { let gas_cost = effects.gas_cost_summary(); assert!(gas_cost.net_gas_usage() as u64 > *MIN_GAS_BUDGET_PRE_RGP); assert!(gas_cost.computation_cost > 0); + assert!(gas_cost.computation_cost_burned > 0); assert!(gas_cost.storage_cost > 0); // Removing genesis object does not have rebate. assert_eq!(gas_cost.storage_rebate, 0); diff --git a/crates/iota-core/src/unit_tests/pay_iota_tests.rs b/crates/iota-core/src/unit_tests/pay_iota_tests.rs index d76ce1419c7..a412ea2be79 100644 --- a/crates/iota-core/src/unit_tests/pay_iota_tests.rs +++ b/crates/iota-core/src/unit_tests/pay_iota_tests.rs @@ -70,7 +70,7 @@ async fn test_pay_iota_failure_insufficient_gas_balance_one_input_coin() { #[tokio::test] async fn test_pay_iota_failure_insufficient_total_balance_one_input_coin() { let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); - let coin1 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 500100); + let coin1 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 1000100); let recipient1 = dbg_addr(1); let recipient2 = dbg_addr(2); @@ -80,7 +80,7 @@ async fn test_pay_iota_failure_insufficient_total_balance_one_input_coin() { vec![100, 100], sender, sender_key, - 500000, + 1000000, ) .await; @@ -123,18 +123,18 @@ async fn test_pay_iota_failure_insufficient_gas_balance_multiple_input_coins() { #[tokio::test] async fn test_pay_iota_failure_insufficient_total_balance_multiple_input_coins() { let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); - let coin1 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 204000); - let coin2 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 303000); + let coin1 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 404000); + let coin2 = Object::with_id_owner_gas_for_testing(ObjectID::random(), sender, 603000); let recipient1 = dbg_addr(1); let recipient2 = dbg_addr(2); let res = execute_pay_iota( vec![coin1, coin2], vec![recipient1, recipient2], - vec![4000, 4000], + vec![8000, 8000], sender, sender_key, - 500000, + 1000000, ) .await; assert_eq!( diff --git a/crates/iota-core/tests/staged/iota.yaml b/crates/iota-core/tests/staged/iota.yaml index 8d4074f0ea6..49a79339cea 100644 --- a/crates/iota-core/tests/staged/iota.yaml +++ b/crates/iota-core/tests/staged/iota.yaml @@ -73,6 +73,25 @@ ChangeEpoch: SEQ: U8 - SEQ: TYPENAME: ObjectID +ChangeEpochV2: + STRUCT: + - epoch: U64 + - protocol_version: + TYPENAME: ProtocolVersion + - storage_charge: U64 + - computation_charge: U64 + - computation_charge_burned: U64 + - storage_rebate: U64 + - non_refundable_storage_fee: U64 + - epoch_start_timestamp_ms: U64 + - system_packages: + SEQ: + TUPLE: + - TYPENAME: SequenceNumber + - SEQ: + SEQ: U8 + - SEQ: + TYPENAME: ObjectID CheckpointCommitment: ENUM: 0: @@ -312,16 +331,20 @@ EndOfEpochTransactionKind: NEWTYPE: TYPENAME: ChangeEpoch 1: - AuthenticatorStateCreate: UNIT + ChangeEpochV2: + NEWTYPE: + TYPENAME: ChangeEpochV2 2: + AuthenticatorStateCreate: UNIT + 3: AuthenticatorStateExpire: NEWTYPE: TYPENAME: AuthenticatorStateExpire - 3: + 4: BridgeStateCreate: NEWTYPE: TYPENAME: ChainIdentifier - 4: + 5: BridgeCommitteeInit: NEWTYPE: TYPENAME: SequenceNumber diff --git a/crates/iota-e2e-tests/tests/apy_test.rs b/crates/iota-e2e-tests/tests/apy_test.rs index 9743961bb68..fa85bf5eb3c 100644 --- a/crates/iota-e2e-tests/tests/apy_test.rs +++ b/crates/iota-e2e-tests/tests/apy_test.rs @@ -18,7 +18,7 @@ use test_cluster::TestClusterBuilder; /// /// - A total stake of 3.5B IOTA. /// - The default validator commission of 2%. -/// - A validator target reward of 767K IOTA. +/// - A validator subsidy (target reward) of 767K IOTA. /// /// This test uses the TestCluster which has limitations on how the validators /// can be set up. Only the validator committee size can be changed, but not @@ -26,7 +26,7 @@ use test_cluster::TestClusterBuilder; /// default number of 4 validators and their initial stake of /// VALIDATOR_LOW_STAKE_THRESHOLD_NANOS. Note that in this case, each validator /// has 25% of the total voting power which results in each pool getting 25% of -/// the target reward. In order to get the total stake up to the 3.5B IOTA, we +/// the subsidy. In order to get the total stake up to the 3.5B IOTA, we /// would have to add that amount of stake to *each* pool. But: APY is /// calculated from the exchange rates of a single pool, which is independent of /// the total stake. So we actually only need to add a quarter of that stake diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system.move index b615e565ac1..2a6fb0c5e36 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system.move @@ -49,9 +49,10 @@ module iota_system::iota_system { #[allow(unused_function)] fun advance_epoch( - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: Balance, - computation_reward: Balance, + computation_charge: Balance, + computation_charge_burned: u64, wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, @@ -67,9 +68,10 @@ module iota_system::iota_system { self, new_epoch, next_protocol_version, - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, storage_rebate, non_refundable_storage_fee, reward_slashing_rate, diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system_state_inner.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system_state_inner.move index 14f0d9e0186..730d3165219 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system_state_inner.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/base/sources/iota_system_state_inner.move @@ -18,7 +18,7 @@ module iota_system::iota_system_state_inner { const SYSTEM_STATE_VERSION_V1: u64 = 18446744073709551605; // u64::MAX - 10 - public struct SystemEpochInfoEventV1 has copy, drop { + public struct SystemEpochInfoEventV2 has copy, drop { epoch: u64, protocol_version: u64, reference_gas_price: u64, @@ -30,6 +30,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: u64, burnt_tokens_amount: u64, minted_tokens_amount: u64, + tips_amount: u64, } public struct SystemParametersV1 has store { @@ -95,9 +96,10 @@ module iota_system::iota_system_state_inner { self: &mut IotaSystemStateV1, new_epoch: u64, next_protocol_version: u64, - _validator_target_reward: u64, + _validator_subsidy: u64, mut storage_charge: Balance, - mut computation_reward: Balance, + mut computation_charge: Balance, + mut _computation_charge_burned: u64, mut storage_rebate_amount: u64, mut _non_refundable_storage_fee_amount: u64, _reward_slashing_rate: u64, @@ -111,14 +113,14 @@ module iota_system::iota_system_state_inner { self.protocol_version = next_protocol_version; let storage_charge_value = storage_charge.value(); - let total_gas_fees = computation_reward.value(); + let total_gas_fees = computation_charge.value(); - balance::join(&mut self.storage_fund, computation_reward); + balance::join(&mut self.storage_fund, computation_charge); balance::join(&mut self.storage_fund, storage_charge); let storage_rebate = balance::split(&mut self.storage_fund, storage_rebate_amount); event::emit( - SystemEpochInfoEventV1 { + SystemEpochInfoEventV2 { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, @@ -130,6 +132,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: 0, burnt_tokens_amount: 0, minted_tokens_amount: 0, + tips_amount: 0, } ); diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system.move index 2d34ba2c6be..aae32e4ce4e 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system.move @@ -49,9 +49,10 @@ module iota_system::iota_system { #[allow(unused_function)] fun advance_epoch( - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: Balance, - computation_reward: Balance, + computation_charge: Balance, + computation_charge_burned: u64, wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, @@ -67,9 +68,10 @@ module iota_system::iota_system { self, new_epoch, next_protocol_version, - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, storage_rebate, non_refundable_storage_fee, reward_slashing_rate, diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system_state_inner.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system_state_inner.move index f7d69dd771f..1d62b3c5330 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system_state_inner.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/deep_upgrade/sources/iota_system_state_inner.move @@ -20,7 +20,7 @@ module iota_system::iota_system_state_inner { // Not using MAX - 9 since it's already used in the shallow upgrade test. const SYSTEM_STATE_VERSION_V2: u64 = 18446744073709551607; // u64::MAX - 8 - public struct SystemEpochInfoEventV1 has copy, drop { + public struct SystemEpochInfoEventV2 has copy, drop { epoch: u64, protocol_version: u64, reference_gas_price: u64, @@ -32,6 +32,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: u64, burnt_tokens_amount: u64, minted_tokens_amount: u64, + tips_amount: u64, } public struct SystemParametersV1 has store { @@ -117,9 +118,10 @@ module iota_system::iota_system_state_inner { self: &mut IotaSystemStateV2, new_epoch: u64, next_protocol_version: u64, - _validator_target_reward: u64, + _validator_subsidy: u64, mut storage_charge: Balance, - mut computation_reward: Balance, + mut computation_charge: Balance, + mut _computation_charge_burned: u64, mut storage_rebate_amount: u64, mut _non_refundable_storage_fee_amount: u64, _reward_slashing_rate: u64, @@ -135,14 +137,14 @@ module iota_system::iota_system_state_inner { self.protocol_version = next_protocol_version; let storage_charge_value = storage_charge.value(); - let total_gas_fees = computation_reward.value(); + let total_gas_fees = computation_charge.value(); - balance::join(&mut self.storage_fund, computation_reward); + balance::join(&mut self.storage_fund, computation_charge); balance::join(&mut self.storage_fund, storage_charge); let storage_rebate = balance::split(&mut self.storage_fund, storage_rebate_amount); event::emit( - SystemEpochInfoEventV1 { + SystemEpochInfoEventV2 { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, @@ -154,6 +156,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: 0, burnt_tokens_amount: 0, minted_tokens_amount: 0, + tips_amount: 0, } ); diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system.move index 701a2597b6d..a031b6b627f 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system.move @@ -49,9 +49,10 @@ module iota_system::iota_system { #[allow(unused_function)] fun advance_epoch( - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: Balance, - computation_reward: Balance, + computation_charge: Balance, + computation_charge_burned: u64, wrapper: &mut IotaSystemState, _new_epoch: u64, _next_protocol_version: u64, @@ -65,9 +66,10 @@ module iota_system::iota_system { assert!(tx_context::sender(ctx) == @0x1, 0); // aborts here let storage_rebate = iota_system_state_inner::advance_epoch( self, - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, storage_rebate, non_refundable_storage_fee, reward_slashing_rate, diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system_state_inner.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system_state_inner.move index 51361804b99..5474bbec4cf 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system_state_inner.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/safe_mode/sources/iota_system_state_inner.move @@ -16,7 +16,7 @@ module iota_system::iota_system_state_inner { const SYSTEM_STATE_VERSION_V1: u64 = 18446744073709551605; // u64::MAX - 10 - public struct SystemEpochInfoEventV1 has copy, drop { + public struct SystemEpochInfoEventV2 has copy, drop { epoch: u64, protocol_version: u64, reference_gas_price: u64, @@ -28,6 +28,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: u64, burnt_tokens_amount: u64, minted_tokens_amount: u64, + tips_amount: u64, } public struct SystemParametersV1 has store { @@ -92,23 +93,24 @@ module iota_system::iota_system_state_inner { public(package) fun advance_epoch( self: &mut IotaSystemStateV1, - _validator_target_reward: u64, + _validator_subsidy: u64, mut storage_charge: Balance, - mut computation_reward: Balance, + mut computation_charge: Balance, + mut _computation_charge_burned: u64, mut storage_rebate_amount: u64, mut _non_refundable_storage_fee_amount: u64, _reward_slashing_rate: u64, _ctx: &mut TxContext, ) : Balance { let storage_charge_value = storage_charge.value(); - let total_gas_fees = computation_reward.value(); + let total_gas_fees = computation_charge.value(); - balance::join(&mut self.storage_fund, computation_reward); + balance::join(&mut self.storage_fund, computation_charge); balance::join(&mut self.storage_fund, storage_charge); let storage_rebate = balance::split(&mut self.storage_fund, storage_rebate_amount); event::emit( - SystemEpochInfoEventV1 { + SystemEpochInfoEventV2 { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, @@ -120,6 +122,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: 0, burnt_tokens_amount: 0, minted_tokens_amount: 0, + tips_amount: 0, } ); diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system.move index 2d34ba2c6be..aae32e4ce4e 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system.move @@ -49,9 +49,10 @@ module iota_system::iota_system { #[allow(unused_function)] fun advance_epoch( - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: Balance, - computation_reward: Balance, + computation_charge: Balance, + computation_charge_burned: u64, wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, @@ -67,9 +68,10 @@ module iota_system::iota_system { self, new_epoch, next_protocol_version, - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, storage_rebate, non_refundable_storage_fee, reward_slashing_rate, diff --git a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system_state_inner.move b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system_state_inner.move index 21473700282..268487feb3a 100644 --- a/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system_state_inner.move +++ b/crates/iota-e2e-tests/tests/framework_upgrades/mock_iota_systems/shallow_upgrade/sources/iota_system_state_inner.move @@ -17,7 +17,7 @@ module iota_system::iota_system_state_inner { const SYSTEM_STATE_VERSION_V1: u64 = 18446744073709551605; // u64::MAX - 10 const SYSTEM_STATE_VERSION_V2: u64 = 18446744073709551606; // u64::MAX - 9 - public struct SystemEpochInfoEventV1 has copy, drop { + public struct SystemEpochInfoEventV2 has copy, drop { epoch: u64, protocol_version: u64, reference_gas_price: u64, @@ -29,6 +29,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: u64, burnt_tokens_amount: u64, minted_tokens_amount: u64, + tips_amount: u64, } public struct SystemParametersV1 has store { @@ -108,9 +109,10 @@ module iota_system::iota_system_state_inner { self: &mut IotaSystemStateV2, new_epoch: u64, next_protocol_version: u64, - _validator_target_reward: u64, + _validator_subsidy: u64, mut storage_charge: Balance, - mut computation_reward: Balance, + mut computation_charge: Balance, + mut _computation_charge_burned: u64, mut storage_rebate_amount: u64, mut _non_refundable_storage_fee_amount: u64, _reward_slashing_rate: u64, @@ -124,14 +126,14 @@ module iota_system::iota_system_state_inner { self.protocol_version = next_protocol_version; let storage_charge_value = storage_charge.value(); - let total_gas_fees = computation_reward.value(); + let total_gas_fees = computation_charge.value(); - balance::join(&mut self.storage_fund, computation_reward); + balance::join(&mut self.storage_fund, computation_charge); balance::join(&mut self.storage_fund, storage_charge); let storage_rebate = balance::split(&mut self.storage_fund, storage_rebate_amount); event::emit( - SystemEpochInfoEventV1 { + SystemEpochInfoEventV2 { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, @@ -143,6 +145,7 @@ module iota_system::iota_system_state_inner { total_stake_rewards_distributed: 0, burnt_tokens_amount: 0, minted_tokens_amount: 0, + tips_amount: 0, } ); diff --git a/crates/iota-e2e-tests/tests/protocol_version_tests.rs b/crates/iota-e2e-tests/tests/protocol_version_tests.rs index f6f35b039b9..9a1a1f61824 100644 --- a/crates/iota-e2e-tests/tests/protocol_version_tests.rs +++ b/crates/iota-e2e-tests/tests/protocol_version_tests.rs @@ -980,7 +980,7 @@ mod sim_only_tests { // The system state object will be upgraded next time we execute advance_epoch // transaction at epoch boundary. let system_state = test_cluster.wait_for_epoch(Some(2)).await; - if let IotaSystemState::V1(inner) = system_state { + if let IotaSystemState::V2(inner) = system_state { assert_eq!(inner.parameters.min_validator_count, 4); } else { unreachable!("Unexpected iota system state version"); diff --git a/crates/iota-e2e-tests/tests/reconfiguration_tests.rs b/crates/iota-e2e-tests/tests/reconfiguration_tests.rs index b731b4d1169..65d8b7753a8 100644 --- a/crates/iota-e2e-tests/tests/reconfiguration_tests.rs +++ b/crates/iota-e2e-tests/tests/reconfiguration_tests.rs @@ -847,7 +847,7 @@ async fn safe_mode_reconfig_test() { let system_state = test_cluster.wait_for_epoch(Some(1)).await; assert!(!system_state.safe_mode()); assert_eq!(system_state.epoch(), 1); - assert_eq!(system_state.system_state_version(), 1); + assert_eq!(system_state.system_state_version(), 2); let prev_epoch_start_timestamp = system_state.epoch_start_timestamp_ms(); @@ -876,7 +876,7 @@ async fn safe_mode_reconfig_test() { let system_state = test_cluster.wait_for_epoch(Some(3)).await; assert!(!system_state.safe_mode()); assert_eq!(system_state.epoch(), 3); - assert_eq!(system_state.system_state_version(), 1); + assert_eq!(system_state.system_state_version(), 2); } async fn add_validator_candidate( diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000001 b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000001 new file mode 100644 index 00000000000..6aae6016489 Binary files /dev/null and b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000001 differ diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000002 b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000002 new file mode 100644 index 00000000000..ea9d0986490 Binary files /dev/null and b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000002 differ diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000003 b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000003 new file mode 100644 index 00000000000..407a879cb3f Binary files /dev/null and b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x0000000000000000000000000000000000000000000000000000000000000003 differ diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000000b b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000000b new file mode 100644 index 00000000000..a28b3206877 Binary files /dev/null and b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000000b differ diff --git a/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000107a b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000107a new file mode 100644 index 00000000000..56d912f6c89 Binary files /dev/null and b/crates/iota-framework-snapshot/bytecode_snapshot/4/0x000000000000000000000000000000000000000000000000000000000000107a differ diff --git a/crates/iota-framework-snapshot/manifest.json b/crates/iota-framework-snapshot/manifest.json index 2f92c9d8340..7401dfd5a6b 100644 --- a/crates/iota-framework-snapshot/manifest.json +++ b/crates/iota-framework-snapshot/manifest.json @@ -28,5 +28,15 @@ "0x000000000000000000000000000000000000000000000000000000000000000b", "0x000000000000000000000000000000000000000000000000000000000000107a" ] + }, + "4": { + "git_revision": "c100adcbf188", + "package_ids": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x000000000000000000000000000000000000000000000000000000000000107a" + ] } } \ No newline at end of file diff --git a/crates/iota-framework/packages/bridge/tests/bridge_env.move b/crates/iota-framework/packages/bridge/tests/bridge_env.move index b3d5359e6e0..9234bbd84f0 100644 --- a/crates/iota-framework/packages/bridge/tests/bridge_env.move +++ b/crates/iota-framework/packages/bridge/tests/bridge_env.move @@ -53,7 +53,7 @@ module bridge::bridge_env { use iota::test_scenario::{Self, Scenario}; use iota::test_utils::destroy; use iota_system::governance_test_utils::{ - advance_epoch_with_reward_amounts, + advance_epoch_with_balanced_reward_amounts, create_iota_system_state_for_testing, create_validator_for_testing }; @@ -267,7 +267,7 @@ module bridge::bridge_env { ); env.validators = validators_info; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, scenario); } // diff --git a/crates/iota-framework/packages/bridge/tests/committee_test.move b/crates/iota-framework/packages/bridge/tests/committee_test.move index eee24556631..7a70fe5dfc0 100644 --- a/crates/iota-framework/packages/bridge/tests/committee_test.move +++ b/crates/iota-framework/packages/bridge/tests/committee_test.move @@ -23,7 +23,7 @@ module bridge::committee_test { use iota::{hex, test_scenario, test_utils::{Self, assert_eq}}; use bridge::chain_ids; use iota_system::governance_test_utils::{ - advance_epoch_with_reward_amounts, + advance_epoch_with_balanced_reward_amounts, create_iota_system_state_for_testing, create_validator_for_testing }; @@ -112,7 +112,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -162,7 +162,7 @@ module bridge::committee_test { create_validator_for_testing(@0xA, 100, ctx), ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -210,7 +210,7 @@ module bridge::committee_test { create_validator_for_testing(@0xA, 100, ctx), ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -259,7 +259,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -284,7 +284,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -312,7 +312,7 @@ module bridge::committee_test { create_validator_for_testing(@0xF, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -328,7 +328,7 @@ module bridge::committee_test { // Validator 0xA become inactive, total voting power become 50% iota_system::request_remove_validator(&mut system_state, &mut tx(@0xA, 0)); test_scenario::return_shared(system_state); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); let mut system_state = test_scenario::take_shared(&scenario); @@ -355,7 +355,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -401,7 +401,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -436,7 +436,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); @@ -467,7 +467,7 @@ module bridge::committee_test { create_validator_for_testing(@0xC, 100, ctx) ]; create_iota_system_state_for_testing(validators, 0, 0, ctx); - advance_epoch_with_reward_amounts(0, 0, &mut scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, &mut scenario); test_scenario::next_tx(&mut scenario, @0x0); let mut system_state = test_scenario::take_shared(&scenario); diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system.move b/crates/iota-framework/packages/iota-system/sources/iota_system.move index 81876d03abd..b2225398eec 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system.move @@ -49,7 +49,7 @@ module iota_system::iota_system { use iota::system_admin_cap::IotaSystemAdminCap; use iota_system::validator::ValidatorV1; use iota_system::validator_cap::UnverifiedValidatorOperationCap; - use iota_system::iota_system_state_inner::{Self, SystemParametersV1, IotaSystemStateV1}; + use iota_system::iota_system_state_inner::{Self, SystemParametersV1, IotaSystemStateV1, IotaSystemStateV2}; use iota_system::staking_pool::PoolTokenExchangeRate; use iota::dynamic_field; use iota::vec_map::VecMap; @@ -511,15 +511,16 @@ module iota_system::iota_system { /// 1. Add storage charge to the storage fund. /// 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's /// gas coins. - /// 3. Mint or burn IOTA tokens depending on whether the validator target reward is greater + /// 3. Mint or burn IOTA tokens depending on whether the validator subsidy is greater /// or smaller than the computation reward. - /// 4. Distribute the target reward to the validators. + /// 4. Distribute the rewards to the validators. /// 5. Burn any leftover rewards. /// 6. Update all validators. fun advance_epoch( - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: Balance, - computation_reward: Balance, + computation_charge: Balance, + computation_charge_burned: u64, wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, @@ -535,9 +536,10 @@ module iota_system::iota_system { let storage_rebate = self.advance_epoch( new_epoch, next_protocol_version, - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, storage_rebate, non_refundable_storage_fee, reward_slashing_rate, @@ -548,16 +550,25 @@ module iota_system::iota_system { storage_rebate } - fun load_system_state(self: &mut IotaSystemState): &IotaSystemStateV1 { + fun load_system_state(self: &mut IotaSystemState): &IotaSystemStateV2 { load_inner_maybe_upgrade(self) } - fun load_system_state_mut(self: &mut IotaSystemState): &mut IotaSystemStateV1 { + fun load_system_state_mut(self: &mut IotaSystemState): &mut IotaSystemStateV2 { load_inner_maybe_upgrade(self) } - fun load_inner_maybe_upgrade(self: &mut IotaSystemState): &mut IotaSystemStateV1 { - let inner: &mut IotaSystemStateV1 = dynamic_field::borrow_mut( + fun load_inner_maybe_upgrade(self: &mut IotaSystemState): &mut IotaSystemStateV2 { + if (self.version == 1) { + let v1: IotaSystemStateV1 = dynamic_field::remove( + &mut self.id, + self.version + ); + let v2 = v1.v1_to_v2(); + self.version = 2; + dynamic_field::add(&mut self.id, self.version, v2); + }; + let inner: &mut IotaSystemStateV2 = dynamic_field::borrow_mut( &mut self.id, self.version ); @@ -727,9 +738,10 @@ module iota_system::iota_system { wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, - validator_target_reward: u64, + validator_subsidy: u64, storage_charge: u64, computation_charge: u64, + computation_charge_burned: u64, storage_rebate: u64, non_refundable_storage_fee: u64, reward_slashing_rate: u64, @@ -737,11 +749,12 @@ module iota_system::iota_system { ctx: &mut TxContext, ): Balance { let storage_charge = balance::create_for_testing(storage_charge); - let computation_reward = balance::create_for_testing(computation_charge); + let computation_charge = balance::create_for_testing(computation_charge); let storage_rebate = advance_epoch( - validator_target_reward, + validator_subsidy, storage_charge, - computation_reward, + computation_charge, + computation_charge_burned, wrapper, new_epoch, next_protocol_version, diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move b/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move index b4840789b09..1f304d79e16 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system_state_inner.move @@ -108,8 +108,62 @@ module iota_system::iota_system_state_inner { extra_fields: Bag, } - /// Event containing system-level epoch information, emitted during - /// the epoch advancement transaction. + /// The top-level object containing all information of the Iota system. + /// An additional field `safe_mode_computation_charges_burned` is added over IotaSystemStateV1 to allow + /// for burning of base fees in safe mode when protocol_defined_base_fee is enabled in the protocol config. + public struct IotaSystemStateV2 has store { + /// The current epoch ID, starting from 0. + epoch: u64, + /// The current protocol version, starting from 1. + protocol_version: u64, + /// The current version of the system state data structure type. + /// This is always the same as IotaSystemState.version. Keeping a copy here so that + /// we know what version it is by inspecting IotaSystemStateV2 as well. + system_state_version: u64, + /// The IOTA's TreasuryCap. + iota_treasury_cap: IotaTreasuryCap, + /// Contains all information about the validators. + validators: ValidatorSetV1, + /// The storage fund. + storage_fund: StorageFundV1, + /// A list of system config parameters. + parameters: SystemParametersV1, + /// A capability allows to perform privileged IOTA system operations. + iota_system_admin_cap: IotaSystemAdminCap, + /// The reference gas price for the current epoch. + reference_gas_price: u64, + /// A map storing the records of validator reporting each other. + /// There is an entry in the map for each validator that has been reported + /// at least once. The entry VecSet contains all the validators that reported + /// them. If a validator has never been reported they don't have an entry in this map. + /// This map persists across epoch: a peer continues being in a reported state until the + /// reporter doesn't explicitly remove their report. + /// Note that in case we want to support validator address change in future, + /// the reports should be based on validator ids + validator_report_records: VecMap>, + + /// Whether the system is running in a downgraded safe mode due to a non-recoverable bug. + /// This is set whenever we failed to execute advance_epoch, and ended up executing advance_epoch_safe_mode. + /// It can be reset once we are able to successfully execute advance_epoch. + /// The rest of the fields starting with `safe_mode_` are accmulated during safe mode + /// when advance_epoch_safe_mode is executed. They will eventually be processed once we + /// are out of safe mode. + safe_mode: bool, + safe_mode_storage_charges: Balance, + safe_mode_computation_charges: Balance, + safe_mode_computation_charges_burned: u64, + safe_mode_storage_rebates: u64, + safe_mode_non_refundable_storage_fee: u64, + + /// Unix timestamp of the current epoch start + epoch_start_timestamp_ms: u64, + /// Any extra fields that's not defined statically. + extra_fields: Bag, + } + + #[allow(unused_field)] + /// The first version of the event containing system-level epoch information, + /// emitted during the epoch advancement transaction. public struct SystemEpochInfoEventV1 has copy, drop { epoch: u64, protocol_version: u64, @@ -124,6 +178,27 @@ module iota_system::iota_system_state_inner { minted_tokens_amount: u64, } + + #[allow(unused_field)] + /// The second version of the event containing system-level epoch information, + /// emitted during the epoch advancement transaction. + /// This version includes the tips_amount field to show how much of the total gas fees were paid to + /// validators (tips) rather than burned. + public struct SystemEpochInfoEventV2 has copy, drop { + epoch: u64, + protocol_version: u64, + reference_gas_price: u64, + total_stake: u64, + storage_charge: u64, + storage_rebate: u64, + storage_fund_balance: u64, + total_gas_fees: u64, + total_stake_rewards_distributed: u64, + burnt_tokens_amount: u64, + minted_tokens_amount: u64, + tips_amount: u64, + } + // Errors const ENotValidator: u64 = 0; const ELimitExceeded: u64 = 1; @@ -199,6 +274,50 @@ module iota_system::iota_system_state_inner { } } + public(package) fun v1_to_v2(self: IotaSystemStateV1): IotaSystemStateV2 { + let IotaSystemStateV1 { + epoch, + protocol_version, + system_state_version: _, + iota_treasury_cap, + validators, + storage_fund, + parameters, + iota_system_admin_cap, + reference_gas_price, + validator_report_records, + safe_mode, + safe_mode_storage_charges, + safe_mode_computation_rewards, + safe_mode_storage_rebates, + safe_mode_non_refundable_storage_fee, + epoch_start_timestamp_ms, + extra_fields + } = self; + // all computation charges are burned in protocol v1. + let safe_mode_computation_charges_burned = safe_mode_computation_rewards.value(); + IotaSystemStateV2 { + epoch, + protocol_version, + system_state_version: 2, + iota_treasury_cap, + validators, + storage_fund, + parameters, + iota_system_admin_cap, + reference_gas_price, + validator_report_records, + safe_mode, + safe_mode_storage_charges, + safe_mode_computation_charges: safe_mode_computation_rewards, + safe_mode_computation_charges_burned, + safe_mode_storage_rebates, + safe_mode_non_refundable_storage_fee, + epoch_start_timestamp_ms, + extra_fields + } + } + // ==== public(package) functions ==== /// Can be called by anyone who wishes to become a validator candidate and starts accuring delegated @@ -208,7 +327,7 @@ module iota_system::iota_system_state_inner { /// Note: `proof_of_possession` MUST be a valid signature using iota_address and authority_pubkey_bytes. /// To produce a valid PoP, run [fn test_proof_of_possession]. public(package) fun request_add_validator_candidate( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, authority_pubkey_bytes: vector, network_pubkey_bytes: vector, protocol_pubkey_bytes: vector, @@ -248,7 +367,7 @@ module iota_system::iota_system_state_inner { /// Called by a validator candidate to remove themselves from the candidacy. After this call /// their staking pool becomes deactivate. public(package) fun request_remove_validator_candidate( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, ctx: &mut TxContext, ) { self.validators.request_remove_validator_candidate(ctx); @@ -259,7 +378,7 @@ module iota_system::iota_system_state_inner { /// stake the validator has doesn't meet the min threshold, or if the number of new validators for the next /// epoch has already reached the maximum. public(package) fun request_add_validator( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, ctx: &TxContext, ) { assert!( @@ -276,7 +395,7 @@ module iota_system::iota_system_state_inner { /// At the end of the epoch, the `validator` object will be returned to the iota_address /// of the validator. public(package) fun request_remove_validator( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, ctx: &TxContext, ) { // Only check min validator condition if the current number of validators satisfy the constraint. @@ -295,7 +414,7 @@ module iota_system::iota_system_state_inner { /// A validator can call this function to submit a new gas price quote, to be /// used for the reference gas price calculation at the end of the epoch. public(package) fun request_set_gas_price( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, cap: &UnverifiedValidatorOperationCap, new_gas_price: u64, ) { @@ -308,7 +427,7 @@ module iota_system::iota_system_state_inner { /// This function is used to set new gas price for candidate validators public(package) fun set_candidate_validator_gas_price( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, cap: &UnverifiedValidatorOperationCap, new_gas_price: u64, ) { @@ -321,7 +440,7 @@ module iota_system::iota_system_state_inner { /// A validator can call this function to set a new commission rate, updated at the end of /// the epoch. public(package) fun request_set_commission_rate( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, new_commission_rate: u64, ctx: &TxContext, ) { @@ -333,7 +452,7 @@ module iota_system::iota_system_state_inner { /// This function is used to set new commission rate for candidate validators public(package) fun set_candidate_validator_commission_rate( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, new_commission_rate: u64, ctx: &TxContext, ) { @@ -343,7 +462,7 @@ module iota_system::iota_system_state_inner { /// Add stake to a validator's staking pool. public(package) fun request_add_stake( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, stake: Coin, validator_address: address, ctx: &mut TxContext, @@ -357,7 +476,7 @@ module iota_system::iota_system_state_inner { /// Add stake to a validator's staking pool using multiple coins. public(package) fun request_add_stake_mul_coin( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, stakes: vector>, stake_amount: option::Option, validator_address: address, @@ -369,7 +488,7 @@ module iota_system::iota_system_state_inner { /// Withdraw some portion of a stake from a validator's staking pool. public(package) fun request_withdraw_stake( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, staked_iota: StakedIota, ctx: &TxContext, ) : Balance { @@ -383,7 +502,7 @@ module iota_system::iota_system_state_inner { /// 3. the cap object is still valid. /// This function is idempotent. public(package) fun report_validator( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, cap: &UnverifiedValidatorOperationCap, reportee_addr: address, ) { @@ -400,7 +519,7 @@ module iota_system::iota_system_state_inner { /// 2. the sender has not previously reported the `reportee_addr`, or /// 3. the cap is not valid public(package) fun undo_report_validator( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, cap: &UnverifiedValidatorOperationCap, reportee_addr: address, ) { @@ -447,7 +566,7 @@ module iota_system::iota_system_state_inner { /// Create a new `UnverifiedValidatorOperationCap`, transfer it to the /// validator and registers it. The original object is thus revoked. public(package) fun rotate_operation_cap( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, ctx: &mut TxContext, ) { let validator = self.validators.get_validator_mut_with_ctx_including_candidates(ctx); @@ -456,7 +575,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's name. public(package) fun update_validator_name( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, name: vector, ctx: &TxContext, ) { @@ -467,7 +586,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's description public(package) fun update_validator_description( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, description: vector, ctx: &TxContext, ) { @@ -477,7 +596,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's image url public(package) fun update_validator_image_url( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, image_url: vector, ctx: &TxContext, ) { @@ -487,7 +606,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's project url public(package) fun update_validator_project_url( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, project_url: vector, ctx: &TxContext, ) { @@ -498,7 +617,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's network address. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_network_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, network_address: vector, ctx: &TxContext, ) { @@ -510,7 +629,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's network address. public(package) fun update_candidate_validator_network_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, network_address: vector, ctx: &TxContext, ) { @@ -521,7 +640,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's p2p address. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_p2p_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, p2p_address: vector, ctx: &TxContext, ) { @@ -533,7 +652,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's p2p address. public(package) fun update_candidate_validator_p2p_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, p2p_address: vector, ctx: &TxContext, ) { @@ -544,7 +663,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's primary address. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_primary_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, primary_address: vector, ctx: &TxContext, ) { @@ -554,7 +673,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's primary address. public(package) fun update_candidate_validator_primary_address( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, primary_address: vector, ctx: &TxContext, ) { @@ -565,7 +684,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's public key of authority key and proof of possession. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_authority_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, authority_pubkey: vector, proof_of_possession: vector, ctx: &TxContext, @@ -578,7 +697,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's public key of authority key and proof of possession. public(package) fun update_candidate_validator_authority_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, authority_pubkey: vector, proof_of_possession: vector, ctx: &TxContext, @@ -590,7 +709,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's public key of protocol key. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_protocol_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, protocol_pubkey: vector, ctx: &TxContext, ) { @@ -602,7 +721,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's public key of protocol key. public(package) fun update_candidate_validator_protocol_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, protocol_pubkey: vector, ctx: &TxContext, ) { @@ -613,7 +732,7 @@ module iota_system::iota_system_state_inner { /// Update a validator's public key of network key. /// The change will only take effects starting from the next epoch. public(package) fun update_validator_next_epoch_network_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, network_pubkey: vector, ctx: &TxContext, ) { @@ -625,7 +744,7 @@ module iota_system::iota_system_state_inner { /// Update candidate validator's public key of network key. public(package) fun update_candidate_validator_network_pubkey( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, network_pubkey: vector, ctx: &TxContext, ) { @@ -638,18 +757,19 @@ module iota_system::iota_system_state_inner { /// 1. Add storage charge to the storage fund. /// 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's /// gas coins. - /// 3. Mint or burn IOTA tokens depending on whether the validator target reward is greater - /// or smaller than the computation reward. - /// 4. Distribute the target reward to the validators. + /// 3. Mint or burn IOTA tokens depending on whether the validator subsidy is greater + /// or smaller than the burned component of the computation charges. + /// 4. Distribute the rewards to the validators. /// 5. Burn any leftover rewards. /// 6. Update all validators. public(package) fun advance_epoch( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, new_epoch: u64, next_protocol_version: u64, - validator_target_reward: u64, + validator_subsidy: u64, mut storage_charge: Balance, - mut computation_reward: Balance, + mut computation_charge: Balance, + mut computation_charge_burned: u64, mut storage_rebate_amount: u64, mut non_refundable_storage_fee_amount: u64, reward_slashing_rate: u64, // how much rewards are slashed to punish a validator, in bps. @@ -665,22 +785,25 @@ module iota_system::iota_system_state_inner { // Accumulate the gas summary during safe_mode before processing any rewards: let safe_mode_storage_charges = self.safe_mode_storage_charges.withdraw_all(); storage_charge.join(safe_mode_storage_charges); - let safe_mode_computation_rewards = self.safe_mode_computation_rewards.withdraw_all(); - computation_reward.join(safe_mode_computation_rewards); + let safe_mode_computation_charges = self.safe_mode_computation_charges.withdraw_all(); + computation_charge.join(safe_mode_computation_charges); + computation_charge_burned = computation_charge_burned + self.safe_mode_computation_charges_burned; storage_rebate_amount = storage_rebate_amount + self.safe_mode_storage_rebates; self.safe_mode_storage_rebates = 0; non_refundable_storage_fee_amount = non_refundable_storage_fee_amount + self.safe_mode_non_refundable_storage_fee; self.safe_mode_non_refundable_storage_fee = 0; let storage_charge_value = storage_charge.value(); - let computation_charge = computation_reward.value(); + let total_gas_fees = computation_charge.value(); + let tips_amount = total_gas_fees - computation_charge_burned; - // Mints or burns tokens depending on the target reward. + // Mints or burns tokens depending on the computation charge burned and the minted subsidy. // Since not all rewards are distributed in case of slashed validators, // tokens might be minted here and burnt in the same epoch change. - let (mut total_validator_rewards, minted_tokens_amount, mut burnt_tokens_amount) = match_computation_reward_to_target_reward( - validator_target_reward, - computation_reward, + let (mut total_validator_rewards, minted_tokens_amount, mut burnt_tokens_amount) = match_computation_charge_burned_to_validator_subsidy( + validator_subsidy, + computation_charge, + computation_charge_burned, &mut self.iota_treasury_cap, ctx ); @@ -725,7 +848,7 @@ module iota_system::iota_system_state_inner { ); event::emit( - SystemEpochInfoEventV1 { + SystemEpochInfoEventV2 { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, @@ -733,60 +856,72 @@ module iota_system::iota_system_state_inner { storage_charge: storage_charge_value, storage_rebate: storage_rebate_amount, storage_fund_balance: self.storage_fund.total_balance(), - total_gas_fees: computation_charge, + total_gas_fees, total_stake_rewards_distributed, burnt_tokens_amount, minted_tokens_amount, + tips_amount } ); self.safe_mode = false; // Double check that the gas from safe mode has been processed. assert!(self.safe_mode_storage_rebates == 0 && self.safe_mode_storage_charges.value() == 0 - && self.safe_mode_computation_rewards.value() == 0, ESafeModeGasNotProcessed); + && self.safe_mode_computation_charges.value() == 0, ESafeModeGasNotProcessed); // Return the storage rebate split from storage fund that's already refunded to the transaction senders. // This will be burnt at the last step of epoch change programmable transaction. refunded_storage_rebate } - /// Mint or burn IOTA tokens depending on the given target reward per validator + /// Mint or burn IOTA tokens depending on the given subsidy per validator /// and the amount of computation fees burned in this epoch. - fun match_computation_reward_to_target_reward( - validator_target_reward: u64, + fun match_computation_charge_burned_to_validator_subsidy( + validator_subsidy: u64, mut computation_charges: Balance, + computation_charge_burned: u64, iota_treasury_cap: &mut iota::iota::IotaTreasuryCap, ctx: &TxContext, ): (Balance, u64, u64) { - let burnt_tokens_amount = computation_charges.value(); - let minted_tokens_amount = validator_target_reward; + let burnt_tokens_amount = computation_charge_burned; + let minted_tokens_amount = validator_subsidy; if (burnt_tokens_amount < minted_tokens_amount) { let actual_amount_to_mint = minted_tokens_amount - burnt_tokens_amount; let balance_to_mint = iota_treasury_cap.mint_balance(actual_amount_to_mint, ctx); + // total validator reward + // = computation_charge + (minted_balance) + // = computation_charge + (validator_subsidy - computation_charge_burned) + // = validator_subsidy + (computation_charge - computation_charge_burned) + // = validator_subsidy + (tips) computation_charges.join(balance_to_mint); } else if (burnt_tokens_amount > minted_tokens_amount) { let actual_amount_to_burn = burnt_tokens_amount - minted_tokens_amount; - let balance_to_burn = computation_charges.split(actual_amount_to_burn); - iota_treasury_cap.burn_balance(balance_to_burn, ctx); + // total validator reward + // = computation_charge - (amount_to_burn) + // = computation_charge - (computation_charge_burned - validator_subsidy) + // = validator_subsidy + (computation_charge - computation_charge_burned) + // = validator_subsidy + (tips) + let balance_to_burn = computation_charges.split(actual_amount_to_burn); + iota_treasury_cap.burn_balance(balance_to_burn, ctx); }; (computation_charges, minted_tokens_amount, burnt_tokens_amount) } /// Return the current epoch number. Useful for applications that need a coarse-grained concept of time, /// since epochs are ever-increasing and epoch changes are intended to happen every 24 hours. - public(package) fun epoch(self: &IotaSystemStateV1): u64 { + public(package) fun epoch(self: &IotaSystemStateV2): u64 { self.epoch } - public(package) fun protocol_version(self: &IotaSystemStateV1): u64 { + public(package) fun protocol_version(self: &IotaSystemStateV2): u64 { self.protocol_version } - public(package) fun system_state_version(self: &IotaSystemStateV1): u64 { + public(package) fun system_state_version(self: &IotaSystemStateV2): u64 { self.system_state_version } - public(package) fun iota_system_admin_cap(self: &IotaSystemStateV1): &IotaSystemAdminCap { + public(package) fun iota_system_admin_cap(self: &IotaSystemStateV2): &IotaSystemAdminCap { &self.iota_system_admin_cap } @@ -797,19 +932,19 @@ module iota_system::iota_system_state_inner { } /// Returns unix timestamp of the start of current epoch - public(package) fun epoch_start_timestamp_ms(self: &IotaSystemStateV1): u64 { + public(package) fun epoch_start_timestamp_ms(self: &IotaSystemStateV2): u64 { self.epoch_start_timestamp_ms } /// Returns the total amount staked with `validator_addr`. /// Aborts if `validator_addr` is not an active validator. - public(package) fun validator_stake_amount(self: &IotaSystemStateV1, validator_addr: address): u64 { + public(package) fun validator_stake_amount(self: &IotaSystemStateV2, validator_addr: address): u64 { self.validators.validator_total_stake_amount(validator_addr) } /// Returns the voting power for `validator_addr`. /// Aborts if `validator_addr` is not an active validator. - public(package) fun active_validator_voting_powers(self: &IotaSystemStateV1): VecMap { + public(package) fun active_validator_voting_powers(self: &IotaSystemStateV2): VecMap { let mut active_validators = active_validator_addresses(self); let mut voting_powers = vec_map::empty(); while (!vector::is_empty(&active_validators)) { @@ -822,24 +957,24 @@ module iota_system::iota_system_state_inner { /// Returns the staking pool id of a given validator. /// Aborts if `validator_addr` is not an active validator. - public(package) fun validator_staking_pool_id(self: &IotaSystemStateV1, validator_addr: address): ID { + public(package) fun validator_staking_pool_id(self: &IotaSystemStateV2, validator_addr: address): ID { self.validators.validator_staking_pool_id(validator_addr) } /// Returns reference to the staking pool mappings that map pool ids to active validator addresses - public(package) fun validator_staking_pool_mappings(self: &IotaSystemStateV1): &Table { + public(package) fun validator_staking_pool_mappings(self: &IotaSystemStateV2): &Table { self.validators.staking_pool_mappings() } /// Returns the total iota supply. - public(package) fun get_total_iota_supply(self: &IotaSystemStateV1): u64 { + public(package) fun get_total_iota_supply(self: &IotaSystemStateV2): u64 { self.iota_treasury_cap.total_supply() } /// Returns all the validators who are currently reporting `addr` - public(package) fun get_reporters_of(self: &IotaSystemStateV1, addr: address): VecSet
{ + public(package) fun get_reporters_of(self: &IotaSystemStateV2, addr: address): VecSet
{ if (self.validator_report_records.contains(&addr)) { self.validator_report_records[&addr] @@ -848,23 +983,23 @@ module iota_system::iota_system_state_inner { } } - public(package) fun get_storage_fund_total_balance(self: &IotaSystemStateV1): u64 { + public(package) fun get_storage_fund_total_balance(self: &IotaSystemStateV2): u64 { self.storage_fund.total_balance() } - public(package) fun get_storage_fund_object_rebates(self: &IotaSystemStateV1): u64 { + public(package) fun get_storage_fund_object_rebates(self: &IotaSystemStateV2): u64 { self.storage_fund.total_object_storage_rebates() } public(package) fun pool_exchange_rates( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, pool_id: &ID ): &Table { let validators = &mut self.validators; validators.pool_exchange_rates(pool_id) } - public(package) fun active_validator_addresses(self: &IotaSystemStateV1): vector
{ + public(package) fun active_validator_addresses(self: &IotaSystemStateV2): vector
{ let validator_set = &self.validators; validator_set.active_validator_addresses() } @@ -894,36 +1029,36 @@ module iota_system::iota_system_state_inner { #[test_only] /// Return the current validator set - public(package) fun validators(self: &IotaSystemStateV1): &ValidatorSetV1 { + public(package) fun validators(self: &IotaSystemStateV2): &ValidatorSetV1 { &self.validators } #[test_only] /// Return the currently active validator by address - public(package) fun active_validator_by_address(self: &IotaSystemStateV1, validator_address: address): &ValidatorV1 { + public(package) fun active_validator_by_address(self: &IotaSystemStateV2, validator_address: address): &ValidatorV1 { self.validators().get_active_validator_ref(validator_address) } #[test_only] /// Return the currently pending validator by address - public(package) fun pending_validator_by_address(self: &IotaSystemStateV1, validator_address: address): &ValidatorV1 { + public(package) fun pending_validator_by_address(self: &IotaSystemStateV2, validator_address: address): &ValidatorV1 { self.validators().get_pending_validator_ref(validator_address) } #[test_only] /// Return the currently candidate validator by address - public(package) fun candidate_validator_by_address(self: &IotaSystemStateV1, validator_address: address): &ValidatorV1 { + public(package) fun candidate_validator_by_address(self: &IotaSystemStateV2, validator_address: address): &ValidatorV1 { validators(self).get_candidate_validator_ref(validator_address) } #[test_only] - public(package) fun set_epoch_for_testing(self: &mut IotaSystemStateV1, epoch_num: u64) { + public(package) fun set_epoch_for_testing(self: &mut IotaSystemStateV2, epoch_num: u64) { self.epoch = epoch_num } #[test_only] public(package) fun request_add_validator_for_testing( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, min_joining_stake_for_testing: u64, ctx: &TxContext, ) { @@ -940,7 +1075,7 @@ module iota_system::iota_system_state_inner { // in the process. #[test_only] public(package) fun request_add_validator_candidate_for_testing( - self: &mut IotaSystemStateV1, + self: &mut IotaSystemStateV2, pubkey_bytes: vector, network_pubkey_bytes: vector, protocol_pubkey_bytes: vector, diff --git a/crates/iota-framework/packages/iota-system/tests/delegation_tests.move b/crates/iota-framework/packages/iota-system/tests/delegation_tests.move index 7a233847c4f..06d175f8a38 100644 --- a/crates/iota-framework/packages/iota-system/tests/delegation_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/delegation_tests.move @@ -17,7 +17,7 @@ module iota_system::stake_tests { add_validator, add_validator_candidate, advance_epoch, - advance_epoch_with_reward_amounts, + advance_epoch_with_balanced_reward_amounts, assert_validator_total_stake_amounts, create_validator_for_testing, create_iota_system_state_for_testing, @@ -236,7 +236,7 @@ module iota_system::stake_tests { if (should_distribute_rewards) { // Each validator pool gets 40 IOTA. - advance_epoch_with_reward_amounts(0, 80, scenario); + advance_epoch_with_balanced_reward_amounts(0, 80, scenario); } else { advance_epoch(scenario); }; @@ -293,7 +293,7 @@ module iota_system::stake_tests { // Add some rewards after the validator requests to leave. Since the validator is still active // this epoch, they should get the rewards from this epoch. - advance_epoch_with_reward_amounts(0, 80, scenario); + advance_epoch_with_balanced_reward_amounts(0, 80, scenario); // Each validator pool gets 40 IOTA. let reward_amt = 20 * NANOS_PER_IOTA; @@ -372,8 +372,8 @@ module iota_system::stake_tests { stake_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, scenario); // Advance epoch twice with some rewards - advance_epoch_with_reward_amounts(0, 400, scenario); - advance_epoch_with_reward_amounts(0, 900, scenario); + advance_epoch_with_balanced_reward_amounts(0, 400, scenario); + advance_epoch_with_balanced_reward_amounts(0, 900, scenario); // Unstake from the preactive validator. There should be no rewards earned. unstake(STAKER_ADDR_1, 0, scenario); @@ -415,7 +415,7 @@ module iota_system::stake_tests { // At this point we got the following distribution of stake: // V1: 100, V2: 100, V3: 100, storage fund: 100 - advance_epoch_with_reward_amounts(0, 300, scenario); + advance_epoch_with_balanced_reward_amounts(0, 300, scenario); // At this point we got the following distribution of stake: // V1: 250, V2: 250, V3: 100, storage fund: 100 @@ -428,7 +428,7 @@ module iota_system::stake_tests { // At this point we got the following distribution of stake: // V1: 250, V2: 250, V3: 250, storage fund: 100 - advance_epoch_with_reward_amounts(0, 85, scenario); + advance_epoch_with_balanced_reward_amounts(0, 85, scenario); // At this point we got the following distribution of stake: // V1: 278_330_500_000, V2: 278_330_500_000, V3: 278_339_000_000, storage fund: 100 @@ -441,7 +441,7 @@ module iota_system::stake_tests { unstake(STAKER_ADDR_3, 0, scenario); assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 111_335_600_000); - advance_epoch_with_reward_amounts(0, 85, scenario); + advance_epoch_with_balanced_reward_amounts(0, 85, scenario); unstake(STAKER_ADDR_2, 0, scenario); // staker 2 earns about 1/5 * 85 * 1/3 = 5.66 IOTA from the previous epoch // and 85 * 1/3 = 28.33 from this one @@ -468,7 +468,7 @@ module iota_system::stake_tests { // staker 1 earns a bit greater than 30 IOTA here. A bit greater because the new validator's voting power // is slightly greater than 1/3 of the total voting power. - advance_epoch_with_reward_amounts(0, 90, scenario); + advance_epoch_with_balanced_reward_amounts(0, 90, scenario); // And now the validator leaves the validator set. remove_validator(NEW_VALIDATOR_ADDR, scenario); @@ -493,7 +493,7 @@ module iota_system::stake_tests { stake_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, scenario); // Advance epoch and give out some rewards. The candidate should get nothing, of course. - advance_epoch_with_reward_amounts(0, 800, scenario); + advance_epoch_with_balanced_reward_amounts(0, 800, scenario); // Now the candidate leaves. remove_validator_candidate(NEW_VALIDATOR_ADDR, scenario); @@ -522,7 +522,7 @@ module iota_system::stake_tests { test_scenario::return_to_address(@0x42, staked_iota); advance_epoch(scenario); // advances epoch to effectuate the stake // Each staking pool gets 10 IOTA of rewards. - advance_epoch_with_reward_amounts(0, 20, scenario); + advance_epoch_with_balanced_reward_amounts(0, 20, scenario); let mut system_state = scenario.take_shared(); let rates = system_state.pool_exchange_rates(&pool_id); assert_eq(rates.length(), 3); diff --git a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move index 6c708b04546..f46ca03132a 100644 --- a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move +++ b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move @@ -114,11 +114,11 @@ module iota_system::governance_test_utils { } public fun advance_epoch(scenario: &mut Scenario) { - advance_epoch_with_reward_amounts(0, 0, scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, scenario); } public fun advance_epoch_with_reward_amounts_return_rebate( - validator_target_reward: u64, storage_charge: u64, computation_charge: u64, storage_rebate: u64, non_refundable_storage_rebate: u64, scenario: &mut Scenario, + validator_subsidy: u64, storage_charge: u64, computation_charge: u64, computation_charge_burned: u64, storage_rebate: u64, non_refundable_storage_rebate: u64, scenario: &mut Scenario, ): Balance { scenario.next_tx(@0x0); let new_epoch = scenario.ctx().epoch() + 1; @@ -127,25 +127,25 @@ module iota_system::governance_test_utils { let ctx = scenario.ctx(); let storage_rebate = system_state.advance_epoch_for_testing( - new_epoch, 1, validator_target_reward, storage_charge, computation_charge, storage_rebate, non_refundable_storage_rebate, 0, 0, ctx, + new_epoch, 1, validator_subsidy, storage_charge, computation_charge, computation_charge_burned, storage_rebate, non_refundable_storage_rebate, 0, 0, ctx, ); test_scenario::return_shared(system_state); scenario.next_epoch(@0x0); storage_rebate } - /// Advances the epoch with the given reward amounts and setting validator_target_reward equal to the computation charge. - public fun advance_epoch_with_reward_amounts( - storage_charge: u64, computation_charge: u64, scenario: &mut Scenario + /// Advances the epoch with the given storage charge and setting validator_subsidy, computation charge and computation charge burned all equal to the specified amount. + public fun advance_epoch_with_balanced_reward_amounts( + storage_charge: u64, computation_charge_and_subsidy_amount: u64, scenario: &mut Scenario ) { - advance_epoch_with_target_reward_amounts(computation_charge, storage_charge, computation_charge, scenario) + advance_epoch_with_amounts(computation_charge_and_subsidy_amount, storage_charge, computation_charge_and_subsidy_amount, computation_charge_and_subsidy_amount, scenario) } - /// Advances the epoch with the given validator target reward and storage and computation charge amounts. - public fun advance_epoch_with_target_reward_amounts( - validator_target_reward: u64, storage_charge: u64, computation_charge: u64, scenario: &mut Scenario + /// Advances the epoch with the given validator subsidy, storage charge, computation charge and computation charge burned amounts. + public fun advance_epoch_with_amounts( + validator_subsidy: u64, storage_charge: u64, computation_charge: u64, computation_charge_burned: u64, scenario: &mut Scenario ) { - let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(validator_target_reward * NANOS_PER_IOTA, storage_charge * NANOS_PER_IOTA, computation_charge * NANOS_PER_IOTA, 0, 0, scenario); + let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(validator_subsidy * NANOS_PER_IOTA, storage_charge * NANOS_PER_IOTA, computation_charge * NANOS_PER_IOTA, computation_charge_burned * NANOS_PER_IOTA, 0, 0, scenario); test_utils::destroy(storage_rebate) } @@ -161,9 +161,9 @@ module iota_system::governance_test_utils { let ctx = scenario.ctx(); - let validator_target_reward = computation_charge; + let validator_subsidy = computation_charge; let storage_rebate = system_state.advance_epoch_for_testing( - new_epoch, 1, validator_target_reward * NANOS_PER_IOTA, storage_charge * NANOS_PER_IOTA, computation_charge * NANOS_PER_IOTA, 0, 0, reward_slashing_rate, 0, ctx + new_epoch, 1, validator_subsidy * NANOS_PER_IOTA, storage_charge * NANOS_PER_IOTA, computation_charge * NANOS_PER_IOTA, computation_charge * NANOS_PER_IOTA, 0, 0, reward_slashing_rate, 0, ctx ); test_utils::destroy(storage_rebate); test_scenario::return_shared(system_state); diff --git a/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move b/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move index b57fb2e841e..7c4151b6202 100644 --- a/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/rewards_distribution_tests.move @@ -9,10 +9,10 @@ module iota_system::rewards_distribution_tests { use iota_system::validator_cap::UnverifiedValidatorOperationCap; use iota_system::governance_test_utils::{ advance_epoch, - advance_epoch_with_reward_amounts, + advance_epoch_with_balanced_reward_amounts, advance_epoch_with_reward_amounts_return_rebate, advance_epoch_with_reward_amounts_and_slashing_rates, - advance_epoch_with_target_reward_amounts, + advance_epoch_with_amounts, assert_validator_total_stake_amounts, assert_validator_non_self_stake_amounts, assert_validator_self_stake_amounts, @@ -58,7 +58,7 @@ module iota_system::rewards_distribution_tests { scenario ); - advance_epoch_with_reward_amounts(0, 100, scenario); + advance_epoch_with_balanced_reward_amounts(0, 100, scenario); // rewards of 100 IOTA are split evenly between the validators // => +25 IOTA for each validator @@ -77,7 +77,7 @@ module iota_system::rewards_distribution_tests { advance_epoch(scenario); - advance_epoch_with_reward_amounts(0, 100, scenario); + advance_epoch_with_balanced_reward_amounts(0, 100, scenario); // Even though validator 2 has a lot more stake now, it should not get more rewards because // the voting power is capped at 10%. @@ -117,7 +117,7 @@ module iota_system::rewards_distribution_tests { scenario ); - advance_epoch_with_reward_amounts(0, 100, scenario); + advance_epoch_with_balanced_reward_amounts(0, 100, scenario); // rewards of 100 IOTA are split evenly between the validators // => +25 IOTA for each validator @@ -134,22 +134,23 @@ module iota_system::rewards_distribution_tests { } #[test] - fun test_validator_target_reward_no_supply_change() { + fun test_validator_subsidy_no_supply_change() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; let prev_supply = total_supply(scenario); - let validator_target_reward = 100; - let computation_reward = 100; + let validator_subsidy = 100; + let computation_charge = 100; + let computation_charge_burned = 100; // need to advance epoch so validator's staking starts counting advance_epoch(scenario); - advance_epoch_with_target_reward_amounts(validator_target_reward, 0, computation_reward, scenario); + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); let new_supply = total_supply(scenario); - // Since the target reward and computation reward are the same, no new tokens should + // Since the validator subsidy and computation charge are the same, no new tokens should // have been minted, so the supply should stay constant. assert!(prev_supply == new_supply, 0); @@ -157,51 +158,53 @@ module iota_system::rewards_distribution_tests { } #[test] - fun test_validator_target_reward_deflation() { + fun test_validator_subsidy_deflation() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; let prev_supply = total_supply(scenario); - let validator_target_reward = 60; - let computation_reward = 100; + let validator_subsidy = 60; + let computation_charge = 100; + let computation_charge_burned = 100; // need to advance epoch so validator's staking starts counting advance_epoch(scenario); - advance_epoch_with_target_reward_amounts(validator_target_reward, 0, computation_reward, scenario); + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); let new_supply = total_supply(scenario); - // The difference between target reward and computation reward should have been burned. - assert_eq(prev_supply - (computation_reward - validator_target_reward) * NANOS_PER_IOTA, new_supply); + // The difference between computation charge burned and validator subsidy should have been burned. + assert_eq(prev_supply - (computation_charge_burned - validator_subsidy) * NANOS_PER_IOTA, new_supply); scenario_val.end(); } #[test] - fun test_validator_target_reward_inflation() { + fun test_validator_subsidy_inflation() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; let prev_supply = total_supply(scenario); - let validator_target_reward = 100; - let computation_reward = 60; + let validator_subsidy = 100; + let computation_charge = 60; + let computation_charge_burned = 60; // need to advance epoch so validator's staking starts counting advance_epoch(scenario); - advance_epoch_with_target_reward_amounts(validator_target_reward, 0, computation_reward, scenario); + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); let new_supply = total_supply(scenario); - // The difference between target reward and computation reward should have been minted. - assert_eq(prev_supply + (validator_target_reward - computation_reward) * NANOS_PER_IOTA, new_supply); + // The difference between validator subsidy and computation charge burned should have been minted. + assert_eq(prev_supply + (validator_subsidy - computation_charge_burned) * NANOS_PER_IOTA, new_supply); scenario_val.end(); } #[test] - fun test_validator_target_reward_higher_than_computation_reward() { + fun test_validator_subsidy_higher_than_computation_charge() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; @@ -220,8 +223,8 @@ module iota_system::rewards_distribution_tests { scenario ); - // The computation reward is lower than the target reward, so 400 IOTA should be minted. - advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + // The computation charge is lower than the validator subsidy, so 400 IOTA should be minted. + advance_epoch_with_amounts(800, 0, 400, 400, scenario); // Each validator pool has 25% of the voting power and thus gets 25% of the reward. // => +200 IOTA for each validator @@ -245,7 +248,7 @@ module iota_system::rewards_distribution_tests { } #[test] - fun test_validator_target_reward_lower_than_computation_reward() { + fun test_validator_subsidy_lower_than_computation_charge() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; @@ -264,8 +267,8 @@ module iota_system::rewards_distribution_tests { scenario ); - // The computation reward is higher than the target reward, so 200 IOTA should be burned. - advance_epoch_with_target_reward_amounts(800, 0, 1000, scenario); + // The computation charge is higher than the validator subsidy, so 200 IOTA should be burned. + advance_epoch_with_amounts(800, 0, 1000, 1000, scenario); // Each validator pool has 25% of the voting power and thus gets 25% of the reward. // => +200 IOTA for each validator @@ -289,7 +292,7 @@ module iota_system::rewards_distribution_tests { } #[test] - fun test_validator_target_reward_higher_than_computation_reward_with_commission() { + fun test_validator_subsidy_higher_than_computation_charge_with_commission() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; @@ -313,8 +316,8 @@ module iota_system::rewards_distribution_tests { set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 500, scenario); // 5% commission - // The computation reward is lower than the target reward, so 400 IOTA should be minted. - advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + // The computation charge is lower than the validator subsidy, so 400 IOTA should be minted. + advance_epoch_with_amounts(800, 0, 400, 400, scenario); // Each validator pool has 25% of the voting power and thus gets 25% of the reward. // => +200 IOTA for each validator @@ -342,6 +345,164 @@ module iota_system::rewards_distribution_tests { scenario_val.end(); } + #[test] + fun test_validator_subsidy_higher_than_computation_charge_with_tips() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + // need to advance epoch so validator's staking starts counting + advance_epoch(scenario); + + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + 100 * NANOS_PER_IOTA, + 200 * NANOS_PER_IOTA, + 300 * NANOS_PER_IOTA, + 400 * NANOS_PER_IOTA, + ], + scenario + ); + + let validator_subsidy = 800; + let computation_charge = 500; // 100 IOTA tips + let computation_charge_burned = 400; + + // The computation charge is lower than the validator subsidy, so 400 IOTA should be minted. + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); + + // Each validator pool has 25% of the voting power and thus gets 25% of the reward. + // total reward is validator subsidy (800) + tips (100) = 900 + // => +225 IOTA for each validator + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (100 + 225) * NANOS_PER_IOTA, + (200 + 225) * NANOS_PER_IOTA, + (300 + 225) * NANOS_PER_IOTA, + (400 + 225) * NANOS_PER_IOTA, + ], + scenario + ); + + unstake(VALIDATOR_ADDR_1, 0, scenario); + + // Validator 1 should get the entire reward of 200 plus its initially staked 100 IOTA. + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), (100+225) * NANOS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + fun test_validator_subsidy_lower_than_computation_charge_with_tips() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + // need to advance epoch so validator's staking starts counting + advance_epoch(scenario); + + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + 100 * NANOS_PER_IOTA, + 200 * NANOS_PER_IOTA, + 300 * NANOS_PER_IOTA, + 400 * NANOS_PER_IOTA, + ], + scenario + ); + + let validator_subsidy = 800; + let computation_charge = 1100; // 100 IOTA tips + let computation_charge_burned = 1000; + + // The computation charge is higher than the validator subsidy, so 200 IOTA should be burned. + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); + + // Each validator pool has 25% of the voting power and thus gets 25% of the reward. + // total reward is validator subsidy (800) + tips (100) = 900 + // => +225 IOTA for each validator + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (100 + 225) * NANOS_PER_IOTA, + (200 + 225) * NANOS_PER_IOTA, + (300 + 225) * NANOS_PER_IOTA, + (400 + 225) * NANOS_PER_IOTA, + ], + scenario + ); + + unstake(VALIDATOR_ADDR_1, 0, scenario); + + // Validator 1 should get the entire reward of 200 plus its initially staked 100 IOTA. + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), (100+225) * NANOS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + fun test_validator_subsidy_higher_than_computation_charge_with_commission_and_tips() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + stake_with(STAKER_ADDR_1, VALIDATOR_ADDR_1, 100, scenario); + stake_with(STAKER_ADDR_2, VALIDATOR_ADDR_2, 50, scenario); + + // need to advance epoch so validator's staking starts counting + advance_epoch(scenario); + + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (100 + 100) * NANOS_PER_IOTA, + (200 + 50) * NANOS_PER_IOTA, + 300 * NANOS_PER_IOTA, + 400 * NANOS_PER_IOTA, + ], + scenario + ); + + set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 500, scenario); // 5% commission + + + let validator_subsidy = 700; + let computation_charge = 500; // 100 IOTA tips + let computation_charge_burned = 400; + + // The computation charge is lower than the validator subsidy, so 400 IOTA should be minted. + advance_epoch_with_amounts(validator_subsidy, 0, computation_charge, computation_charge_burned, scenario); + + // Each validator pool has 25% of the voting power and thus gets 25% of the reward. + // total reward is validator subsidy (700) + tips (100) = 900 + // => +200 IOTA for each validator + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (200 + 200) * NANOS_PER_IOTA, + (250 + 200) * NANOS_PER_IOTA, + (300 + 200) * NANOS_PER_IOTA, + (400 + 200) * NANOS_PER_IOTA, + ], + scenario + ); + + unstake(STAKER_ADDR_1, 0, scenario); + unstake(STAKER_ADDR_2, 0, scenario); + + // Staker 1 should have its original 100 staked IOTA and get half the pool reward (100) + // minus the validator's commission (100 * 0.05 = 5), so 95. + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), (100 + 95) * NANOS_PER_IOTA); + + // Staker 2 should get 50/250 = 1/5 of the pool reward, which is 40. + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), (50 + 40) * NANOS_PER_IOTA); + + scenario_val.end(); + } + #[test] fun test_stake_rewards() { set_up_iota_system_state(); @@ -378,7 +539,7 @@ module iota_system::rewards_distribution_tests { // Validator 1 gets 100/300 * 30 IOTA => +10 IOTA for validator 1 // Validator 2 gets 200/300 * 30 IOTA => +20 IOTA for validator 2 // Validators 3 and 4 have all the stake in the pool => +30 IOTA for validators 3 and 4 - advance_epoch_with_reward_amounts(0, 120, scenario); + advance_epoch_with_balanced_reward_amounts(0, 120, scenario); assert_validator_self_stake_amounts( validator_addrs(), vector[ @@ -394,7 +555,7 @@ module iota_system::rewards_distribution_tests { // Each validator pool has 25% of the voting power and thus gets 25% of the reward. // => +30 IOTA for each pool - advance_epoch_with_reward_amounts(0, 120, scenario); + advance_epoch_with_balanced_reward_amounts(0, 120, scenario); // staker 1 receives only 200/300*30=20 IOTA of rewards, (not 40) since we are using pre-epoch exchange rate. assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), (200 + 20) * NANOS_PER_IOTA); // The recent changes in stake are not valid for this epoch yet. Thus: @@ -416,7 +577,7 @@ module iota_system::rewards_distribution_tests { assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), (100 + 20) * NANOS_PER_IOTA); // +10 IOTA for each pool - advance_epoch_with_reward_amounts(0, 40, scenario); + advance_epoch_with_balanced_reward_amounts(0, 40, scenario); // unstakes the additional 600 IOTA of principal + rewards relative to past epoch // the rewarded amount is 600/740*10 IOTA @@ -439,17 +600,17 @@ module iota_system::rewards_distribution_tests { // need to advance epoch so validator's staking starts counting advance_epoch(scenario); - advance_epoch_with_reward_amounts(0, 150000, scenario); + advance_epoch_with_balanced_reward_amounts(0, 150000, scenario); // stake a small amount stake_with(STAKER_ADDR_1, VALIDATOR_ADDR_1, 10, scenario); - advance_epoch_with_reward_amounts(0, 130, scenario); + advance_epoch_with_balanced_reward_amounts(0, 130, scenario); // unstake the stakes unstake(STAKER_ADDR_1, 1, scenario); // and advance epoch should succeed - advance_epoch_with_reward_amounts(0, 150, scenario); + advance_epoch_with_balanced_reward_amounts(0, 150, scenario); scenario_val.end(); } @@ -471,7 +632,7 @@ module iota_system::rewards_distribution_tests { // Each validator pool has 25% of the voting power and thus gets 25% of the reward. // => +30 IOTA for each pool - advance_epoch_with_reward_amounts(0, 120, scenario); + advance_epoch_with_balanced_reward_amounts(0, 120, scenario); // staker 1 gets 100/200*30 IOTA => +15 IOTA // staker 2 would get 100/300*30 IOTA = 10 IOTA. However, since the commission rate is 20% for this pool, they get 8 IOTA @@ -501,7 +662,7 @@ module iota_system::rewards_distribution_tests { set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 1000, scenario); // 10% commission // +60 IOTA for each pool - advance_epoch_with_reward_amounts(0, 240, scenario); + advance_epoch_with_balanced_reward_amounts(0, 240, scenario); assert_validator_total_stake_amounts( validator_addrs(), vector[ @@ -554,7 +715,7 @@ module iota_system::rewards_distribution_tests { // Validator 1: 10% commission. set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 1000, scenario); - advance_epoch_with_reward_amounts(0, 800, scenario); + advance_epoch_with_balanced_reward_amounts(0, 800, scenario); // Each validator pool gets 25% of the voting power and thus gets 25% of the reward (200 IOTA). assert_validator_total_stake_amounts( @@ -708,7 +869,7 @@ module iota_system::rewards_distribution_tests { let initial_supply = total_supply(scenario); // Put 300 IOTA into the storage fund. This should not change the pools' stake or give rewards. - advance_epoch_with_reward_amounts(300, 0, scenario); + advance_epoch_with_balanced_reward_amounts(300, 0, scenario); assert_validator_total_stake_amounts( validator_addrs(), vector[ @@ -730,7 +891,7 @@ module iota_system::rewards_distribution_tests { report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_4, scenario); report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_4, scenario); - // 1000 IOTA of storage charges, 1500 IOTA of computation rewards, 50% slashing threshold + // 1000 IOTA of storage charges, 1500 IOTA of computation charges, 50% slashing threshold // and 20% slashing rate // because of the voting power cap, each pool gets +375 IOTA advance_epoch_with_reward_amounts_and_slashing_rates( @@ -817,7 +978,7 @@ module iota_system::rewards_distribution_tests { // since the voting power is capped at 10%, each pool gets +10 IOTA // Pools' stake after this are // P1: 100 + 220 + 10 = 330; P2: 200 + 10 = 210; P3: 300 + 10 = 310; P4: 400 + 10 = 410 - advance_epoch_with_reward_amounts(0, 40, scenario); + advance_epoch_with_balanced_reward_amounts(0, 40, scenario); stake_with(STAKER_ADDR_2, VALIDATOR_ADDR_1, 480, scenario); @@ -825,7 +986,7 @@ module iota_system::rewards_distribution_tests { // Staker 1 gets 220/330 * 30 = +20 IOTA, totalling 240 IOTA of stake // Pools' stake after this are // P1: 330 + 480 + 30 = 840; P2: 210 + 30 = 240; P3: 310 + 30 = 340; P4: 410 + 30 = 440 - advance_epoch_with_reward_amounts(0, 120, scenario); + advance_epoch_with_balanced_reward_amounts(0, 120, scenario); stake_with(STAKER_ADDR_1, VALIDATOR_ADDR_1, 130, scenario); stake_with(STAKER_ADDR_3, VALIDATOR_ADDR_1, 390, scenario); @@ -835,7 +996,7 @@ module iota_system::rewards_distribution_tests { // Staker 2 gets 480/840*70 = +40 IOTA, totalling 520 IOTA of stake // Pools' stake after this are // P1: 840 + 130 + 390 + 70 = 1430; P2: 240 + 70 = 310; P3: 340 + 70 = 410; P4: 440 + 70 = 510 - advance_epoch_with_reward_amounts(0, 280, scenario); + advance_epoch_with_balanced_reward_amounts(0, 280, scenario); stake_with(STAKER_ADDR_3, VALIDATOR_ADDR_1, 280, scenario); stake_with(STAKER_ADDR_4, VALIDATOR_ADDR_1, 1400, scenario); @@ -847,7 +1008,7 @@ module iota_system::rewards_distribution_tests { // Pools' stake after this are // P1: 1430 + 280 + 1400 + 110 = 3220; P2: 310 + 110 = 420 // P3: 410 + 110 = 520; P4: 510 + 110 = 620 - advance_epoch_with_reward_amounts(0, 440, scenario); + advance_epoch_with_balanced_reward_amounts(0, 440, scenario); scenario.next_tx(@0x0); let mut system_state = scenario.take_shared(); @@ -868,7 +1029,7 @@ module iota_system::rewards_distribution_tests { assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 700 * NANOS_PER_IOTA); assert_eq(total_iota_balance(STAKER_ADDR_4, scenario), 1400 * NANOS_PER_IOTA); - advance_epoch_with_reward_amounts(0, 0, scenario); + advance_epoch_with_balanced_reward_amounts(0, 0, scenario); scenario.next_tx(@0x0); let mut system_state = scenario.take_shared(); @@ -898,7 +1059,7 @@ module iota_system::rewards_distribution_tests { create_iota_system_state_for_testing(validators, 0, 0, ctx); // Each validator's stake gets doubled. - advance_epoch_with_reward_amounts(0, 10000, scenario); + advance_epoch_with_balanced_reward_amounts(0, 10000, scenario); let mut i = 0; scenario.next_tx(@0x0); @@ -919,10 +1080,10 @@ module iota_system::rewards_distribution_tests { let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; - // To get the leftover, we have to slash every validator. This way, the computation reward will remain as leftover. + // To get the leftover, we have to slash every validator. This way, the computation charge will remain as leftover. slash_all_validators(scenario); - // Pass 700 IOTA as computation reward(for an instance). + // Pass 700 IOTA as computation charge(for an instance). advance_epoch_with_reward_amounts_and_slashing_rates( 1000, 700, 10_000, scenario ); @@ -941,10 +1102,10 @@ module iota_system::rewards_distribution_tests { let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; - // To get the leftover, we have to slash every validator. This way, the computation reward will remain as leftover. + // To get the leftover, we have to slash every validator. This way, the computation charge will remain as leftover. slash_all_validators(scenario); - // Pass 1700 IOTA as computation reward which is larger than the total supply of 1000 IOTA. + // Pass 1700 IOTA as computation charge which is larger than the total supply of 1000 IOTA. advance_epoch_with_reward_amounts_and_slashing_rates( 1000, 1700, 10_000, scenario ); @@ -960,7 +1121,7 @@ module iota_system::rewards_distribution_tests { // The leftover comes from the unequal distribution of rewards to validators. // As example 1_000_000_000_1 cannot be split into equal parts, so it cause leftover. - let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(1_000_000_000_1, 1_000_000_000_000, 1_000_000_000_1, 0, 0, scenario); + let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(1_000_000_000_1, 1_000_000_000_000, 1_000_000_000_1, 1_000_000_000_1, 0, 0, scenario); destroy(storage_rebate); scenario.next_tx(@0x0); diff --git a/crates/iota-framework/packages/iota-system/tests/timelocked_delegation_tests.move b/crates/iota-framework/packages/iota-system/tests/timelocked_delegation_tests.move index 295a04d6d8c..ccab6cb008e 100644 --- a/crates/iota-framework/packages/iota-system/tests/timelocked_delegation_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/timelocked_delegation_tests.move @@ -20,8 +20,8 @@ module iota_system::timelocked_stake_tests { add_validator, add_validator_candidate, advance_epoch, - advance_epoch_with_reward_amounts, - advance_epoch_with_target_reward_amounts, + advance_epoch_with_balanced_reward_amounts, + advance_epoch_with_amounts, assert_validator_total_stake_amounts, create_validator_for_testing, create_iota_system_state_for_testing, @@ -604,7 +604,7 @@ module iota_system::timelocked_stake_tests { ); // Each validator pool gets 40 IOTA. - advance_epoch_with_reward_amounts(0, 80, scenario); + advance_epoch_with_balanced_reward_amounts(0, 80, scenario); remove_validator(VALIDATOR_ADDR_1, scenario); @@ -659,7 +659,7 @@ module iota_system::timelocked_stake_tests { // Add some rewards after the validator requests to leave. Since the validator is still active // this epoch, they should get the rewards from this epoch. - advance_epoch_with_reward_amounts(0, 80, scenario); + advance_epoch_with_balanced_reward_amounts(0, 80, scenario); // Each validator pool gets 40 IOTA. let reward_amt = 20 * NANOS_PER_IOTA; @@ -740,8 +740,8 @@ module iota_system::timelocked_stake_tests { stake_timelocked_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, 10, scenario); // Advance epoch twice with some rewards - advance_epoch_with_reward_amounts(0, 400, scenario); - advance_epoch_with_reward_amounts(0, 900, scenario); + advance_epoch_with_balanced_reward_amounts(0, 400, scenario); + advance_epoch_with_balanced_reward_amounts(0, 900, scenario); // Unstake from the preactive validator. There should be no rewards earned. unstake_timelocked(STAKER_ADDR_1, 0, scenario); @@ -784,7 +784,7 @@ module iota_system::timelocked_stake_tests { // At this point we got the following distribution of stake: // V1: 100, V2: 100, V3: 100, storage fund: 100 - advance_epoch_with_reward_amounts(0, 300, scenario); + advance_epoch_with_balanced_reward_amounts(0, 300, scenario); // At this point we got the following distribution of stake: // V1: 250, V2: 250, V3: 100, storage fund: 100 @@ -797,7 +797,7 @@ module iota_system::timelocked_stake_tests { // At this point we got the following distribution of stake: // V1: 250, V2: 250, V3: 250, storage fund: 100 - advance_epoch_with_reward_amounts(0, 85, scenario); + advance_epoch_with_balanced_reward_amounts(0, 85, scenario); // At this point we got the following distribution of stake: // V1: 278_330_500_000, V2: 278_330_500_000, V3: 278_339_000_000, storage fund: 100 @@ -813,7 +813,7 @@ module iota_system::timelocked_stake_tests { assert_eq(total_timelocked_iota_balance(STAKER_ADDR_3, scenario), 100 * NANOS_PER_IOTA); assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 11_335_600_000); - advance_epoch_with_reward_amounts(0, 85, scenario); + advance_epoch_with_balanced_reward_amounts(0, 85, scenario); unstake_timelocked(STAKER_ADDR_2, 0, scenario); // staker 2 earns about 1/5 * 85 * 1/3 = 5.66 IOTA from the previous epoch @@ -842,7 +842,7 @@ module iota_system::timelocked_stake_tests { // staker 1 earns a bit greater than 30 IOTA here. A bit greater because the new validator's voting power // is slightly greater than 1/3 of the total voting power. - advance_epoch_with_reward_amounts(0, 90, scenario); + advance_epoch_with_balanced_reward_amounts(0, 90, scenario); // And now the validator leaves the validator set. remove_validator(NEW_VALIDATOR_ADDR, scenario); @@ -868,7 +868,7 @@ module iota_system::timelocked_stake_tests { stake_timelocked_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, 10, scenario); // Advance epoch and give out some rewards. The candidate should get nothing, of course. - advance_epoch_with_reward_amounts(0, 800, scenario); + advance_epoch_with_balanced_reward_amounts(0, 800, scenario); // Now the candidate leaves. remove_validator_candidate(NEW_VALIDATOR_ADDR, scenario); @@ -898,7 +898,7 @@ module iota_system::timelocked_stake_tests { test_scenario::return_to_address(@0x42, staked_iota); advance_epoch(scenario); // advances epoch to effectuate the stake // Each staking pool gets 10 IOTA of rewards. - advance_epoch_with_reward_amounts(0, 20, scenario); + advance_epoch_with_balanced_reward_amounts(0, 20, scenario); let mut system_state = scenario.take_shared(); let rates = system_state.pool_exchange_rates(&pool_id); assert_eq(rates.length(), 3); @@ -910,7 +910,7 @@ module iota_system::timelocked_stake_tests { } #[test] - fun test_timelock_validator_target_reward_higher_than_computation_reward() { + fun test_timelock_validator_subsidy_higher_than_computation_charge() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; @@ -920,9 +920,9 @@ module iota_system::timelocked_stake_tests { advance_epoch(scenario); // V1: 200, V2: 200 - advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + advance_epoch_with_amounts(800, 0, 400, 400, scenario); - // The computation reward is lower than the target reward, so 400 IOTA should be minted. + // The computation charge burned is lower than the validator subsidy, so 400 IOTA should be minted. // Each validator pool has 50% of the voting power and thus gets 50% of the reward (400 IOTA). assert_validator_total_stake_amounts( validator_addrs(), @@ -948,7 +948,7 @@ module iota_system::timelocked_stake_tests { } #[test] - fun test_timelock_validator_target_reward_lower_than_computation_reward() { + fun test_timelock_validator_subsidy_lower_than_computation_charge() { set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; @@ -958,9 +958,9 @@ module iota_system::timelocked_stake_tests { advance_epoch(scenario); // V1: 200, V2: 250 - advance_epoch_with_target_reward_amounts(800, 0, 1000, scenario); + advance_epoch_with_amounts(800, 0, 1000, 1000, scenario); - // The computation reward is higher than the target reward, so 200 IOTA should be burned. + // The computation charge burned is higher than the validator subsidy, so 200 IOTA should be burned. // Each validator pool has 50% of the voting power and thus gets 50% of the reward (400 IOTA). assert_validator_total_stake_amounts( validator_addrs(), diff --git a/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move b/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move index eb9c1981e41..f66c2fa1405 100644 --- a/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/validator_set_tests.move @@ -431,10 +431,10 @@ module iota_system::validator_set_tests { fun advance_epoch_with_dummy_rewards(validator_set: &mut ValidatorSetV1, scenario: &mut Scenario) { scenario.next_epoch(@0x0); - let mut dummy_computation_reward = balance::zero(); + let mut dummy_computation_charge = balance::zero(); validator_set.advance_epoch( - &mut dummy_computation_reward, + &mut dummy_computation_charge, &mut vec_map::empty(), 0, // reward_slashing_rate 0, // low_stake_threshold @@ -443,7 +443,7 @@ module iota_system::validator_set_tests { scenario.ctx() ); - dummy_computation_reward.destroy_zero(); + dummy_computation_charge.destroy_zero(); } fun advance_epoch_with_low_stake_params( @@ -454,9 +454,9 @@ module iota_system::validator_set_tests { scenario: &mut Scenario ) { scenario.next_epoch(@0x0); - let mut dummy_computation_reward = balance::zero(); + let mut dummy_computation_charge = balance::zero(); validator_set.advance_epoch( - &mut dummy_computation_reward, + &mut dummy_computation_charge, &mut vec_map::empty(), 0, // reward_slashing_rate low_stake_threshold * NANOS_PER_IOTA, @@ -465,7 +465,7 @@ module iota_system::validator_set_tests { scenario.ctx() ); - dummy_computation_reward.destroy_zero(); + dummy_computation_charge.destroy_zero(); } fun add_and_activate_validator(validator_set: &mut ValidatorSetV1, validator: ValidatorV1, scenario: &mut Scenario) { diff --git a/crates/iota-framework/packages_compiled/iota-system b/crates/iota-framework/packages_compiled/iota-system index 3869d79f95f..c4463ac103b 100644 Binary files a/crates/iota-framework/packages_compiled/iota-system and b/crates/iota-framework/packages_compiled/iota-system differ diff --git a/crates/iota-framework/published_api.txt b/crates/iota-framework/published_api.txt index b26c1ea49ef..d9625e47504 100644 --- a/crates/iota-framework/published_api.txt +++ b/crates/iota-framework/published_api.txt @@ -616,15 +616,24 @@ SystemParametersV1 IotaSystemStateV1 public struct 0x3::iota_system_state_inner +IotaSystemStateV2 + public struct + 0x3::iota_system_state_inner SystemEpochInfoEventV1 public struct 0x3::iota_system_state_inner +SystemEpochInfoEventV2 + public struct + 0x3::iota_system_state_inner create public(package) fun 0x3::iota_system_state_inner create_system_parameters public(package) fun 0x3::iota_system_state_inner +v1_to_v2 + public(package) fun + 0x3::iota_system_state_inner request_add_validator_candidate public(package) fun 0x3::iota_system_state_inner @@ -724,7 +733,7 @@ update_candidate_validator_network_pubkey advance_epoch public(package) fun 0x3::iota_system_state_inner -match_computation_reward_to_target_reward +match_computation_charge_burned_to_validator_subsidy fun 0x3::iota_system_state_inner epoch diff --git a/crates/iota-genesis-builder/src/lib.rs b/crates/iota-genesis-builder/src/lib.rs index c71620c67f5..4943e63a3a1 100644 --- a/crates/iota-genesis-builder/src/lib.rs +++ b/crates/iota-genesis-builder/src/lib.rs @@ -537,9 +537,9 @@ impl Builder { } = self.parameters.to_genesis_chain_parameters(); // In non-testing code, genesis type must always be V1. - #[expect(clippy::infallible_destructuring_match)] let system_state = match unsigned_genesis.iota_system_object() { IotaSystemState::V1(inner) => inner, + IotaSystemState::V2(_) => unreachable!(), #[cfg(msim)] _ => { // Types other than V1 used in simtests do not need to be validated. diff --git a/crates/iota-protocol-config/src/lib.rs b/crates/iota-protocol-config/src/lib.rs index d7427ae18d9..2a8d1e24b0c 100644 --- a/crates/iota-protocol-config/src/lib.rs +++ b/crates/iota-protocol-config/src/lib.rs @@ -26,7 +26,8 @@ pub const MAX_PROTOCOL_VERSION: u64 = 4; // Version 3: Set the `relocate_event_module` to be true so that the module that // is associated as the "sending module" for an event is relocated by linkage. // Add `Clock` based unlock to `Timelock` objects. -// Version 4: TODO +// Version 4: Introduce fixed protocol-defined base fee, IotaSystemStateV2 and +// SystemEpochInfoEventV2 #[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ProtocolVersion(u64); @@ -191,6 +192,10 @@ struct FeatureFlags { // Makes the event's sending module version-aware. #[serde(skip_serializing_if = "is_false")] relocate_event_module: bool, + + // Enable a protocol-defined base gas price for all transactions. + #[serde(skip_serializing_if = "is_false")] + protocol_defined_base_fee: bool, } fn is_true(b: &bool) -> bool { @@ -606,11 +611,13 @@ pub struct ProtocolConfig { /// In basis point. reward_slashing_rate: Option, - /// Unit gas price, Nanos per internal gas unit. + /// Unit storage gas price, Nanos per internal gas unit. storage_gas_price: Option, - /// The number of tokens that the set of validators should receive per - /// epoch. + // Base gas price for computation gas, nanos per computation unit. + base_gas_price: Option, + + /// The number of tokens minted as a validator subsidy per epoch. validator_target_reward: Option, // === Core Protocol === @@ -1076,6 +1083,10 @@ impl ProtocolConfig { pub fn relocate_event_module(&self) -> bool { self.feature_flags.relocate_event_module } + + pub fn protocol_defined_base_fee(&self) -> bool { + self.feature_flags.protocol_defined_base_fee + } } #[cfg(not(msim))] @@ -1290,7 +1301,8 @@ impl ProtocolConfig { // Change reward slashing rate to 100%. reward_slashing_rate: Some(10000), storage_gas_price: Some(76), - // The initial target reward for validators per epoch. + base_gas_price: None, + // The initial subsidy (target reward) for validators per epoch. // Refer to the IOTA tokenomics for the origin of this value. validator_target_reward: Some(767_000 * 1_000_000_000), max_transactions_per_checkpoint: Some(10_000), @@ -1667,7 +1679,11 @@ impl ProtocolConfig { cfg.feature_flags.relocate_event_module = true; } // version 4 - 4 => {} + 4 => { + cfg.feature_flags.protocol_defined_base_fee = true; + cfg.base_gas_price = Some(1000); + } + // Use this template when making changes: // // // modify an existing constant. diff --git a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Mainnet_version_4.snap b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Mainnet_version_4.snap index 606b1bfd889..322653f0fb7 100644 --- a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Mainnet_version_4.snap +++ b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Mainnet_version_4.snap @@ -9,6 +9,7 @@ feature_flags: per_object_congestion_control_mode: TotalTxCount zklogin_max_epoch_upper_bound_delta: 30 relocate_event_module: true + protocol_defined_base_fee: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -92,6 +93,7 @@ obj_metadata_cost_non_refundable: 50 storage_rebate_rate: 10000 reward_slashing_rate: 10000 storage_gas_price: 76 +base_gas_price: 1000 validator_target_reward: 767000000000000 max_transactions_per_checkpoint: 10000 max_checkpoint_size_bytes: 31457280 diff --git a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Testnet_version_4.snap b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Testnet_version_4.snap index 4447ad20654..2f44df93899 100644 --- a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Testnet_version_4.snap +++ b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__Testnet_version_4.snap @@ -9,6 +9,7 @@ feature_flags: per_object_congestion_control_mode: TotalTxCount zklogin_max_epoch_upper_bound_delta: 30 relocate_event_module: true + protocol_defined_base_fee: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -92,6 +93,7 @@ obj_metadata_cost_non_refundable: 50 storage_rebate_rate: 10000 reward_slashing_rate: 10000 storage_gas_price: 76 +base_gas_price: 1000 validator_target_reward: 767000000000000 max_transactions_per_checkpoint: 10000 max_checkpoint_size_bytes: 31457280 diff --git a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_4.snap b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_4.snap index 74250ff0ee4..2cdd4637a73 100644 --- a/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_4.snap +++ b/crates/iota-protocol-config/src/snapshots/iota_protocol_config__test__version_4.snap @@ -13,6 +13,7 @@ feature_flags: enable_vdf: true passkey_auth: true relocate_event_module: true + protocol_defined_base_fee: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -96,6 +97,7 @@ obj_metadata_cost_non_refundable: 50 storage_rebate_rate: 10000 reward_slashing_rate: 10000 storage_gas_price: 76 +base_gas_price: 1000 validator_target_reward: 767000000000000 max_transactions_per_checkpoint: 10000 max_checkpoint_size_bytes: 31457280 diff --git a/crates/iota-types/src/event.rs b/crates/iota-types/src/event.rs index 7e63e7dd960..68e07272605 100755 --- a/crates/iota-types/src/event.rs +++ b/crates/iota-types/src/event.rs @@ -138,11 +138,21 @@ impl Event { }) } - pub fn is_system_epoch_info_event(&self) -> bool { + pub fn is_system_epoch_info_event_v1(&self) -> bool { self.type_.address == IOTA_SYSTEM_ADDRESS && self.type_.module.as_ident_str() == ident_str!("iota_system_state_inner") && self.type_.name.as_ident_str() == ident_str!("SystemEpochInfoEventV1") } + + pub fn is_system_epoch_info_event_v2(&self) -> bool { + self.type_.address == IOTA_SYSTEM_ADDRESS + && self.type_.module.as_ident_str() == ident_str!("iota_system_state_inner") + && self.type_.name.as_ident_str() == ident_str!("SystemEpochInfoEventV2") + } + + pub fn is_system_epoch_info_event(&self) -> bool { + self.is_system_epoch_info_event_v1() || self.is_system_epoch_info_event_v2() + } } impl Event { @@ -162,7 +172,42 @@ impl Event { } } -// Event emitted in move code `fun advance_epoch` +#[derive(Deserialize)] +pub enum SystemEpochInfoEvent { + V1(SystemEpochInfoEventV1), + V2(SystemEpochInfoEventV2), +} + +impl SystemEpochInfoEvent { + pub fn supply_change(&self) -> i64 { + match self { + SystemEpochInfoEvent::V1(event) => { + event.minted_tokens_amount as i64 - event.burnt_tokens_amount as i64 + } + SystemEpochInfoEvent::V2(event) => { + event.minted_tokens_amount as i64 - event.burnt_tokens_amount as i64 + } + } + } +} + +impl From for SystemEpochInfoEvent { + fn from(event: Event) -> Self { + if event.is_system_epoch_info_event_v2() { + SystemEpochInfoEvent::V2( + bcs::from_bytes::(&event.contents) + .expect("event deserialization should succeed as type was pre-validated"), + ) + } else { + SystemEpochInfoEvent::V1( + bcs::from_bytes::(&event.contents) + .expect("event deserialization should succeed as type was pre-validated"), + ) + } + } +} + +/// Event emitted in move code `fun advance_epoch` in protocol versions 1 to 3 #[derive(Deserialize)] pub struct SystemEpochInfoEventV1 { pub epoch: u64, @@ -177,3 +222,24 @@ pub struct SystemEpochInfoEventV1 { pub burnt_tokens_amount: u64, pub minted_tokens_amount: u64, } + +/// Event emitted in move code `fun advance_epoch` in protocol versions 4 and +/// later. +/// This second version of the event includes the tips amount to show how much +/// of the gas fees go to the validators when protocol_defined_base_fee is +/// enabled in the protocol config. +#[derive(Deserialize)] +pub struct SystemEpochInfoEventV2 { + pub epoch: u64, + pub protocol_version: u64, + pub reference_gas_price: u64, + pub total_stake: u64, + pub storage_charge: u64, + pub storage_rebate: u64, + pub storage_fund_balance: u64, + pub total_gas_fees: u64, + pub total_stake_rewards_distributed: u64, + pub burnt_tokens_amount: u64, + pub minted_tokens_amount: u64, + pub tips_amount: u64, +} diff --git a/crates/iota-types/src/gas.rs b/crates/iota-types/src/gas.rs index b8996eae528..bcae5459c2c 100644 --- a/crates/iota-types/src/gas.rs +++ b/crates/iota-types/src/gas.rs @@ -67,7 +67,7 @@ pub mod checked { // Common checks. We may pull them into version specific status as needed, but // they are unlikely to change. - // gas price must be bigger or equal to reference gas price + // gas price must be greater than or equal to reference gas price if gas_price < reference_gas_price { return Err(UserInputError::GasPriceUnderRGP { gas_price, @@ -91,6 +91,8 @@ pub mod checked { } pub fn new_unmetered() -> Self { + // Always return V1 as unmetered gas status is identical from V1 to V2. + // This is only used for system transactions which do not pay gas. Self::V1(IotaGasStatusV1::new_unmetered()) } diff --git a/crates/iota-types/src/gas_model/gas_v1.rs b/crates/iota-types/src/gas_model/gas_v1.rs index 5dee9b58361..85998abcd83 100644 --- a/crates/iota-types/src/gas_model/gas_v1.rs +++ b/crates/iota-types/src/gas_model/gas_v1.rs @@ -167,34 +167,39 @@ mod checked { #[derive(Debug)] pub struct IotaGasStatus { - // GasStatus as used by the VM, that is all the VM sees + /// GasStatus as used by the VM, that is all the VM sees pub gas_status: GasStatus, - // Cost table contains a set of constant/config for the gas model/charging + /// Cost table contains a set of constant/config for the gas + /// model/charging cost_table: IotaCostTable, - // Gas budget for this gas status instance. - // Typically the gas budget as defined in the `TransactionData::GasData` + /// Gas budget for this gas status instance. + /// Typically the gas budget as defined in the + /// `TransactionData::GasData` gas_budget: u64, - // Computation cost after execution. This is the result of the gas used by the `GasStatus` - // properly bucketized. - // Starts at 0 and it is assigned in `bucketize_computation`. + /// Computation cost after execution. This is the result of the gas used + /// by the `GasStatus` properly bucketized. + /// Starts at 0 and it is assigned in `bucketize_computation`. computation_cost: u64, - // Whether to charge or go unmetered + /// Whether to charge or go unmetered charge: bool, - // Gas price for computation. - // This is a multiplier on the final charge as related to the RGP (reference gas price). - // Checked at signing: `gas_price >= reference_gas_price` - // and then conceptually - // `final_computation_cost = total_computation_cost * gas_price / reference_gas_price` + /// Gas price for computation. + /// This is a multiplier on the final charge as related to the RGP + /// (reference gas price). Checked at signing: `gas_price >= + /// reference_gas_price` and then conceptually + /// `final_computation_cost = total_computation_cost * gas_price / + /// reference_gas_price` gas_price: u64, - // RGP as defined in the protocol config. + // Reference gas price as defined in protocol config. + // If `protocol_defined_base_fee' is enabled, this is a mandatory base fee paid to the + // protocol. reference_gas_price: u64, - // Gas price for storage. This is a multiplier on the final charge - // as related to the storage gas price defined in the system - // (`ProtocolConfig::storage_gas_price`). - // Conceptually, given a constant `obj_data_cost_refundable` - // (defined in `ProtocolConfig::obj_data_cost_refundable`) - // `total_storage_cost = storage_bytes * obj_data_cost_refundable` - // `final_storage_cost = total_storage_cost * storage_gas_price` + /// Gas price for storage. This is a multiplier on the final charge + /// as related to the storage gas price defined in the system + /// (`ProtocolConfig::storage_gas_price`). + /// Conceptually, given a constant `obj_data_cost_refundable` + /// (defined in `ProtocolConfig::obj_data_cost_refundable`) + /// `total_storage_cost = storage_bytes * obj_data_cost_refundable` + /// `final_storage_cost = total_storage_cost * storage_gas_price` storage_gas_price: u64, /// Per Object Storage Cost and Storage Rebate, used to get accumulated /// values at the end of execution to determine storage charges @@ -209,6 +214,9 @@ mod checked { unmetered_storage_rebate: u64, /// Rounding value to round up gas charges. gas_rounding_step: Option, + /// Flag to indicate whether the protocol-defined base fee is enabled, + /// in which case the reference gas price is burned. + protocol_defined_base_fee: bool, } impl IotaGasStatus { @@ -222,6 +230,7 @@ mod checked { rebate_rate: u64, gas_rounding_step: Option, cost_table: IotaCostTable, + protocol_defined_base_fee: bool, ) -> IotaGasStatus { let gas_rounding_step = gas_rounding_step.map(|val| val.max(1)); IotaGasStatus { @@ -237,6 +246,7 @@ mod checked { unmetered_storage_rebate: 0, gas_rounding_step, cost_table, + protocol_defined_base_fee, } } @@ -270,6 +280,7 @@ mod checked { config.storage_rebate_rate(), gas_rounding_step, iota_cost_table, + config.protocol_defined_base_fee(), ) } @@ -284,6 +295,7 @@ mod checked { 0, None, IotaCostTable::unmetered(), + false, ) } @@ -371,24 +383,24 @@ mod checked { } fn bucketize_computation(&mut self) -> Result<(), ExecutionError> { - let gas_used = self.gas_status.gas_used_pre_gas_price(); - let gas_used = if let Some(gas_rounding) = self.gas_rounding_step { - if gas_used > 0 && gas_used % gas_rounding == 0 { - gas_used * self.gas_price - } else { - ((gas_used / gas_rounding) + 1) * gas_rounding * self.gas_price + let mut computation_units = self.gas_status.gas_used_pre_gas_price(); + if let Some(gas_rounding) = self.gas_rounding_step { + if gas_rounding > 0 + && (computation_units == 0 || computation_units % gas_rounding > 0) + { + computation_units = ((computation_units / gas_rounding) + 1) * gas_rounding; } } else { - let bucket_cost = get_bucket_cost(&self.cost_table.computation_bucket, gas_used); - // charge extra on top of `computation_cost` to make the total computation - // cost a bucket value - bucket_cost * self.gas_price + // use the max value of the bucket that the computation_units falls into. + computation_units = + get_bucket_cost(&self.cost_table.computation_bucket, computation_units); }; - if self.gas_budget <= gas_used { + let computation_cost = computation_units * self.gas_price; + if self.gas_budget <= computation_cost { self.computation_cost = self.gas_budget; Err(ExecutionErrorKind::InsufficientGas.into()) } else { - self.computation_cost = gas_used; + self.computation_cost = computation_cost; Ok(()) } } @@ -397,15 +409,26 @@ mod checked { /// of the gas meter. We use initial budget, combined with /// remaining gas and storage cost to derive computation cost. fn summary(&self) -> GasCostSummary { - // compute storage rebate, both rebate and non refundable fee + // compute computation cost burned and storage rebate, both rebate and non + // refundable fee + let computation_cost_burned = if self.protocol_defined_base_fee { + // when protocol_defined_base_fee is enabled, the computation cost burned is + // computed as follows: + // computation_cost_burned = computation_units * reference_gas_price. + // = (computation_cost / gas_price) * reference_gas_price + self.computation_cost * self.reference_gas_price / self.gas_price + } else { + // when protocol_defined_base_fee is disabled, the entire computation cost is + // burned. + self.computation_cost + }; let storage_rebate = self.storage_rebate(); let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate); assert!(sender_rebate <= storage_rebate); let non_refundable_storage_fee = storage_rebate - sender_rebate; GasCostSummary { computation_cost: self.computation_cost, - // entire computation cost is burned. - computation_cost_burned: self.computation_cost, + computation_cost_burned, storage_cost: self.storage_cost(), storage_rebate: sender_rebate, non_refundable_storage_fee, diff --git a/crates/iota-types/src/iota_sdk2_conversions.rs b/crates/iota-types/src/iota_sdk2_conversions.rs index 031089f698c..3b7e53c4cc2 100644 --- a/crates/iota-types/src/iota_sdk2_conversions.rs +++ b/crates/iota-types/src/iota_sdk2_conversions.rs @@ -13,6 +13,7 @@ use fastcrypto::traits::ToFromBytes; use iota_sdk2::types::{ object::{MovePackage, MoveStruct}, + transaction::{ChangeEpoch, ChangeEpochV2}, *, }; use move_core_types::language_storage::ModuleId; @@ -513,6 +514,27 @@ impl From for EndOfEpochTransacti .collect(), }) } + crate::transaction::EndOfEpochTransactionKind::ChangeEpochV2(change_epoch_v2) => { + EndOfEpochTransactionKind::ChangeEpochV2(ChangeEpochV2 { + epoch: change_epoch_v2.epoch, + protocol_version: change_epoch_v2.protocol_version.as_u64(), + storage_charge: change_epoch_v2.storage_charge, + computation_charge: change_epoch_v2.computation_charge, + computation_charge_burned: change_epoch_v2.computation_charge_burned, + storage_rebate: change_epoch_v2.storage_rebate, + non_refundable_storage_fee: change_epoch_v2.non_refundable_storage_fee, + epoch_start_timestamp_ms: change_epoch_v2.epoch_start_timestamp_ms, + system_packages: change_epoch_v2 + .system_packages + .into_iter() + .map(|(version, modules, dependencies)| SystemPackage { + version: version.value(), + modules, + dependencies: dependencies.into_iter().map(Into::into).collect(), + }) + .collect(), + }) + } crate::transaction::EndOfEpochTransactionKind::AuthenticatorStateCreate => { EndOfEpochTransactionKind::AuthenticatorStateCreate } @@ -563,6 +585,29 @@ impl From for crate::transaction::EndOfEpochTransacti .collect(), }) } + EndOfEpochTransactionKind::ChangeEpochV2(change_epoch_v2) => { + Self::ChangeEpochV2(crate::transaction::ChangeEpochV2 { + epoch: change_epoch_v2.epoch, + protocol_version: change_epoch_v2.protocol_version.into(), + storage_charge: change_epoch_v2.storage_charge, + computation_charge: change_epoch_v2.computation_charge, + computation_charge_burned: change_epoch_v2.computation_charge_burned, + storage_rebate: change_epoch_v2.storage_rebate, + non_refundable_storage_fee: change_epoch_v2.non_refundable_storage_fee, + epoch_start_timestamp_ms: change_epoch_v2.epoch_start_timestamp_ms, + system_packages: change_epoch_v2 + .system_packages + .into_iter() + .map(|package| { + ( + package.version.into(), + package.modules, + package.dependencies.into_iter().map(Into::into).collect(), + ) + }) + .collect(), + }) + } EndOfEpochTransactionKind::AuthenticatorStateCreate => Self::AuthenticatorStateCreate, EndOfEpochTransactionKind::AuthenticatorStateExpire(authenticator_state_expire) => { Self::AuthenticatorStateExpire(crate::transaction::AuthenticatorStateExpire { diff --git a/crates/iota-types/src/iota_system_state/iota_system_state_inner_v1.rs b/crates/iota-types/src/iota_system_state/iota_system_state_inner_v1.rs index 12c8ed0cc3d..121beeecb73 100644 --- a/crates/iota-types/src/iota_system_state/iota_system_state_inner_v1.rs +++ b/crates/iota-types/src/iota_system_state/iota_system_state_inner_v1.rs @@ -11,7 +11,9 @@ use super::{ AdvanceEpochParams, IotaSystemStateTrait, epoch_start_iota_system_state::EpochStartValidatorInfoV1, get_validators_from_table_vec, - iota_system_state_summary::{IotaSystemStateSummary, IotaValidatorSummary}, + iota_system_state_summary::{ + IotaSystemStateSummary, IotaSystemStateSummaryV1, IotaValidatorSummary, + }, }; use crate::{ balance::Balance, @@ -566,10 +568,6 @@ impl IotaSystemStateTrait for IotaSystemStateV1 { } fn into_iota_system_state_summary(self) -> IotaSystemStateSummary { - // If you are making any changes to IotaSystemStateV1 or any of its dependent - // types before mainnet, please also update IotaSystemStateSummary and - // its corresponding TS type. Post-mainnet, we will need to introduce a - // new version. let Self { epoch, protocol_version, @@ -635,7 +633,7 @@ impl IotaSystemStateTrait for IotaSystemStateV1 { epoch_start_timestamp_ms, extra_fields: _, } = self; - IotaSystemStateSummary { + IotaSystemStateSummary::V1(IotaSystemStateSummaryV1 { epoch, protocol_version, system_state_version, @@ -648,7 +646,7 @@ impl IotaSystemStateTrait for IotaSystemStateV1 { reference_gas_price, safe_mode, safe_mode_storage_charges: safe_mode_storage_charges.value(), - safe_mode_computation_rewards: safe_mode_computation_rewards.value(), + safe_mode_computation_charges: safe_mode_computation_rewards.value(), safe_mode_storage_rebates, safe_mode_non_refundable_storage_fee, epoch_start_timestamp_ms, @@ -681,7 +679,7 @@ impl IotaSystemStateTrait for IotaSystemStateV1 { validator_low_stake_threshold, validator_very_low_stake_threshold, validator_low_stake_grace_period, - } + }) } } diff --git a/crates/iota-types/src/iota_system_state/iota_system_state_inner_v2.rs b/crates/iota-types/src/iota_system_state/iota_system_state_inner_v2.rs new file mode 100644 index 00000000000..0a404f63748 --- /dev/null +++ b/crates/iota-types/src/iota_system_state/iota_system_state_inner_v2.rs @@ -0,0 +1,283 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use super::{ + AdvanceEpochParams, IotaSystemStateTrait, + epoch_start_iota_system_state::EpochStartValidatorInfoV1, + get_validators_from_table_vec, + iota_system_state_inner_v1::{StorageFundV1, SystemParametersV1, ValidatorSetV1, ValidatorV1}, + iota_system_state_summary::{ + IotaSystemStateSummary, IotaSystemStateSummaryV2, IotaValidatorSummary, + }, +}; +use crate::{ + balance::Balance, + base_types::IotaAddress, + collection_types::{Bag, Table, TableVec, VecMap, VecSet}, + committee::{CommitteeWithNetworkMetadata, NetworkMetadata}, + error::IotaError, + gas_coin::IotaTreasuryCap, + iota_system_state::epoch_start_iota_system_state::EpochStartSystemState, + storage::ObjectStore, + system_admin_cap::IotaSystemAdminCap, +}; + +/// Rust version of the Move [`iota_system::iota_system::IotaSystemStateV1`] +/// type An additional field `safe_mode_computation_charges_burned` is added +/// over +/// [`IotaSystemStateV1`](super::iota_system_state_inner_v1::IotaSystemStateV1) +/// to allow for burning of base fees in safe mode when +/// protocol_defined_base_fee is enabled in the protocol config. +#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +pub struct IotaSystemStateV2 { + pub epoch: u64, + pub protocol_version: u64, + pub system_state_version: u64, + pub iota_treasury_cap: IotaTreasuryCap, + pub validators: ValidatorSetV1, + pub storage_fund: StorageFundV1, + pub parameters: SystemParametersV1, + pub iota_system_admin_cap: IotaSystemAdminCap, + pub reference_gas_price: u64, + pub validator_report_records: VecMap>, + pub safe_mode: bool, + pub safe_mode_storage_charges: Balance, + pub safe_mode_computation_charges: Balance, + pub safe_mode_computation_charges_burned: u64, + pub safe_mode_storage_rebates: u64, + pub safe_mode_non_refundable_storage_fee: u64, + pub epoch_start_timestamp_ms: u64, + pub extra_fields: Bag, + // TODO: Use getters instead of all pub. +} + +impl IotaSystemStateTrait for IotaSystemStateV2 { + fn epoch(&self) -> u64 { + self.epoch + } + + fn reference_gas_price(&self) -> u64 { + self.reference_gas_price + } + + fn protocol_version(&self) -> u64 { + self.protocol_version + } + + fn system_state_version(&self) -> u64 { + self.system_state_version + } + + fn epoch_start_timestamp_ms(&self) -> u64 { + self.epoch_start_timestamp_ms + } + + fn epoch_duration_ms(&self) -> u64 { + self.parameters.epoch_duration_ms + } + + fn safe_mode(&self) -> bool { + self.safe_mode + } + + fn advance_epoch_safe_mode(&mut self, params: &AdvanceEpochParams) { + self.epoch = params.epoch; + self.safe_mode = true; + self.safe_mode_storage_charges + .deposit_for_safe_mode(params.storage_charge); + self.safe_mode_storage_rebates += params.storage_rebate; + self.safe_mode_computation_charges + .deposit_for_safe_mode(params.computation_charge); + self.safe_mode_computation_charges_burned += params.computation_charge_burned; + self.safe_mode_non_refundable_storage_fee += params.non_refundable_storage_fee; + self.epoch_start_timestamp_ms = params.epoch_start_timestamp_ms; + self.protocol_version = params.next_protocol_version.as_u64(); + } + + fn get_current_epoch_committee(&self) -> CommitteeWithNetworkMetadata { + let validators = self + .validators + .active_validators + .iter() + .map(|validator| { + let verified_metadata = validator.verified_metadata(); + let name = verified_metadata.iota_pubkey_bytes(); + ( + name, + ( + validator.voting_power, + NetworkMetadata { + network_address: verified_metadata.net_address.clone(), + primary_address: verified_metadata.primary_address.clone(), + }, + ), + ) + }) + .collect(); + CommitteeWithNetworkMetadata::new(self.epoch, validators) + } + + fn get_pending_active_validators( + &self, + object_store: &S, + ) -> Result, IotaError> { + let table_id = self.validators.pending_active_validators.contents.id; + let table_size = self.validators.pending_active_validators.contents.size; + let validators: Vec = + get_validators_from_table_vec(&object_store, table_id, table_size)?; + Ok(validators + .into_iter() + .map(|v| v.into_iota_validator_summary()) + .collect()) + } + + fn into_epoch_start_state(self) -> EpochStartSystemState { + EpochStartSystemState::new_v1( + self.epoch, + self.protocol_version, + self.reference_gas_price, + self.safe_mode, + self.epoch_start_timestamp_ms, + self.parameters.epoch_duration_ms, + self.validators + .active_validators + .iter() + .map(|validator| { + let metadata = validator.verified_metadata(); + EpochStartValidatorInfoV1 { + iota_address: metadata.iota_address, + authority_pubkey: metadata.authority_pubkey.clone(), + network_pubkey: metadata.network_pubkey.clone(), + protocol_pubkey: metadata.protocol_pubkey.clone(), + iota_net_address: metadata.net_address.clone(), + p2p_address: metadata.p2p_address.clone(), + primary_address: metadata.primary_address.clone(), + voting_power: validator.voting_power, + hostname: metadata.name.clone(), + } + }) + .collect(), + ) + } + + fn into_iota_system_state_summary(self) -> IotaSystemStateSummary { + let Self { + epoch, + protocol_version, + system_state_version, + iota_treasury_cap, + validators: + ValidatorSetV1 { + total_stake, + active_validators, + pending_active_validators: + TableVec { + contents: + Table { + id: pending_active_validators_id, + size: pending_active_validators_size, + }, + }, + pending_removals, + staking_pool_mappings: + Table { + id: staking_pool_mappings_id, + size: staking_pool_mappings_size, + }, + inactive_validators: + Table { + id: inactive_pools_id, + size: inactive_pools_size, + }, + validator_candidates: + Table { + id: validator_candidates_id, + size: validator_candidates_size, + }, + at_risk_validators: + VecMap { + contents: at_risk_validators, + }, + extra_fields: _, + }, + storage_fund, + parameters: + SystemParametersV1 { + epoch_duration_ms, + min_validator_count, + max_validator_count, + min_validator_joining_stake, + validator_low_stake_threshold, + validator_very_low_stake_threshold, + validator_low_stake_grace_period, + extra_fields: _, + }, + iota_system_admin_cap: _, + reference_gas_price, + validator_report_records: + VecMap { + contents: validator_report_records, + }, + safe_mode, + safe_mode_storage_charges, + safe_mode_computation_charges, + safe_mode_computation_charges_burned, + safe_mode_storage_rebates, + safe_mode_non_refundable_storage_fee, + epoch_start_timestamp_ms, + extra_fields: _, + } = self; + IotaSystemStateSummary::V2(IotaSystemStateSummaryV2 { + epoch, + protocol_version, + system_state_version, + iota_total_supply: iota_treasury_cap.total_supply().value, + iota_treasury_cap_id: iota_treasury_cap.id().to_owned(), + storage_fund_total_object_storage_rebates: storage_fund + .total_object_storage_rebates + .value(), + storage_fund_non_refundable_balance: storage_fund.non_refundable_balance.value(), + reference_gas_price, + safe_mode, + safe_mode_storage_charges: safe_mode_storage_charges.value(), + safe_mode_computation_charges: safe_mode_computation_charges.value(), + safe_mode_computation_charges_burned, + safe_mode_storage_rebates, + safe_mode_non_refundable_storage_fee, + epoch_start_timestamp_ms, + epoch_duration_ms, + total_stake, + active_validators: active_validators + .into_iter() + .map(|v| v.into_iota_validator_summary()) + .collect(), + pending_active_validators_id, + pending_active_validators_size, + pending_removals, + staking_pool_mappings_id, + staking_pool_mappings_size, + inactive_pools_id, + inactive_pools_size, + validator_candidates_id, + validator_candidates_size, + at_risk_validators: at_risk_validators + .into_iter() + .map(|e| (e.key, e.value)) + .collect(), + validator_report_records: validator_report_records + .into_iter() + .map(|e| (e.key, e.value.contents)) + .collect(), + min_validator_count, + max_validator_count, + min_validator_joining_stake, + validator_low_stake_threshold, + validator_very_low_stake_threshold, + validator_low_stake_grace_period, + }) + } +} diff --git a/crates/iota-types/src/iota_system_state/iota_system_state_summary.rs b/crates/iota-types/src/iota_system_state/iota_system_state_summary.rs index b92565404e6..2d744d70b36 100644 --- a/crates/iota-types/src/iota_system_state/iota_system_state_summary.rs +++ b/crates/iota-types/src/iota_system_state/iota_system_state_summary.rs @@ -20,14 +20,186 @@ use crate::{ storage::ObjectStore, }; -/// This is the JSON-RPC type for the IOTA system state object. -/// It flattens all fields to make them top-level fields such that it as minimum -/// dependencies to the internal data structures of the IOTA system state type. +/// This is the JSON-RPC type for IOTA system state objects. +/// It is an enum type that can represent either V1 or V2 system state objects. +pub enum IotaSystemStateSummary { + V1(IotaSystemStateSummaryV1), + V2(IotaSystemStateSummaryV2), +} + +/// This is the JSON-RPC type for the +/// [`IotaSystemStateV1`](super::iota_system_state_inner_v1::IotaSystemStateV1) +/// object. It flattens all fields to make them top-level fields such that it as +/// minimum dependencies to the internal data structures of the IOTA system +/// state type. +#[serde_as] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct IotaSystemStateSummaryV1 { + /// The current epoch ID, starting from 0. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub epoch: u64, + /// The current protocol version, starting from 1. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub protocol_version: u64, + /// The current version of the system state data structure type. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub system_state_version: u64, + /// The current IOTA supply. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub iota_total_supply: u64, + /// The `TreasuryCap` object ID. + pub iota_treasury_cap_id: ObjectID, + /// The storage rebates of all the objects on-chain stored in the storage + /// fund. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub storage_fund_total_object_storage_rebates: u64, + /// The non-refundable portion of the storage fund coming from + /// non-refundable storage rebates and any leftover + /// staking rewards. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub storage_fund_non_refundable_balance: u64, + /// The reference gas price for the current epoch. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub reference_gas_price: u64, + /// Whether the system is running in a downgraded safe mode due to a + /// non-recoverable bug. This is set whenever we failed to execute + /// advance_epoch, and ended up executing advance_epoch_safe_mode. + /// It can be reset once we are able to successfully execute advance_epoch. + pub safe_mode: bool, + /// Amount of storage charges accumulated (and not yet distributed) during + /// safe mode. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub safe_mode_storage_charges: u64, + /// Amount of computation charges accumulated (and not yet distributed) + /// during safe mode. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub safe_mode_computation_charges: u64, + /// Amount of storage rebates accumulated (and not yet burned) during safe + /// mode. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub safe_mode_storage_rebates: u64, + /// Amount of non-refundable storage fee accumulated during safe mode. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub safe_mode_non_refundable_storage_fee: u64, + /// Unix timestamp of the current epoch start + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub epoch_start_timestamp_ms: u64, + + // System parameters + /// The duration of an epoch, in milliseconds. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub epoch_duration_ms: u64, + + /// Minimum number of active validators at any moment. + /// We do not allow the number of validators in any epoch to go under this. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub min_validator_count: u64, + + /// Maximum number of active validators at any moment. + /// We do not allow the number of validators in any epoch to go above this. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub max_validator_count: u64, + + /// Lower-bound on the amount of stake required to become a validator. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub min_validator_joining_stake: u64, + + /// Validators with stake amount below `validator_low_stake_threshold` are + /// considered to have low stake and will be escorted out of the + /// validator set after being below this threshold for more than + /// `validator_low_stake_grace_period` number of epochs. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub validator_low_stake_threshold: u64, + + /// Validators with stake below `validator_very_low_stake_threshold` will be + /// removed immediately at epoch change, no grace period. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub validator_very_low_stake_threshold: u64, + + /// A validator can have stake below `validator_low_stake_threshold` + /// for this many epochs before being kicked out. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub validator_low_stake_grace_period: u64, + + // Validator set + /// Total amount of stake from all active validators at the beginning of the + /// epoch. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub total_stake: u64, + /// The list of active validators in the current epoch. + pub active_validators: Vec, + /// ID of the object that contains the list of new validators that will join + /// at the end of the epoch. + pub pending_active_validators_id: ObjectID, + /// Number of new validators that will join at the end of the epoch. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub pending_active_validators_size: u64, + /// Removal requests from the validators. Each element is an index + /// pointing to `active_validators`. + #[schemars(with = "Vec>")] + #[serde_as(as = "Vec, _>>")] + pub pending_removals: Vec, + /// ID of the object that maps from staking pool's ID to the iota address of + /// a validator. + pub staking_pool_mappings_id: ObjectID, + /// Number of staking pool mappings. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub staking_pool_mappings_size: u64, + /// ID of the object that maps from a staking pool ID to the inactive + /// validator that has that pool as its staking pool. + pub inactive_pools_id: ObjectID, + /// Number of inactive staking pools. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub inactive_pools_size: u64, + /// ID of the object that stores preactive validators, mapping their + /// addresses to their `Validator` structs. + pub validator_candidates_id: ObjectID, + /// Number of preactive validators. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub validator_candidates_size: u64, + /// Map storing the number of epochs for which each validator has been below + /// the low stake threshold. + #[schemars(with = "Vec<(IotaAddress, BigInt)>")] + #[serde_as(as = "Vec<(_, Readable, _>)>")] + pub at_risk_validators: Vec<(IotaAddress, u64)>, + /// A map storing the records of validator reporting each other. + pub validator_report_records: Vec<(IotaAddress, Vec)>, +} +/// This is the JSON-RPC type for the +/// [`IotaSystemStateV2`](super::iota_system_state_inner_v2::IotaSystemStateV1) +/// object. It flattens all fields to make them top-level fields such that it as +/// minimum dependencies to the internal data structures of the IOTA system +/// state type. #[serde_as] #[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct IotaSystemStateSummary { +pub struct IotaSystemStateSummaryV2 { /// The current epoch ID, starting from 0. #[schemars(with = "BigInt")] #[serde_as(as = "Readable, _>")] @@ -71,11 +243,15 @@ pub struct IotaSystemStateSummary { #[schemars(with = "BigInt")] #[serde_as(as = "Readable, _>")] pub safe_mode_storage_charges: u64, - /// Amount of computation rewards accumulated (and not yet distributed) + /// Amount of computation charges accumulated (and not yet distributed) /// during safe mode. #[schemars(with = "BigInt")] #[serde_as(as = "Readable, _>")] - pub safe_mode_computation_rewards: u64, + pub safe_mode_computation_charges: u64, + /// Amount of burned computation charges accumulated during safe mode. + #[schemars(with = "BigInt")] + #[serde_as(as = "Readable, _>")] + pub safe_mode_computation_charges_burned: u64, /// Amount of storage rebates accumulated (and not yet burned) during safe /// mode. #[schemars(with = "BigInt")] @@ -184,6 +360,40 @@ pub struct IotaSystemStateSummary { } impl IotaSystemStateSummary { + pub fn get_iota_committee_for_benchmarking(&self) -> CommitteeWithNetworkMetadata { + match self { + Self::V1(v1) => v1.get_iota_committee_for_benchmarking(), + Self::V2(v2) => v2.get_iota_committee_for_benchmarking(), + } + } +} + +impl IotaSystemStateSummaryV1 { + pub fn get_iota_committee_for_benchmarking(&self) -> CommitteeWithNetworkMetadata { + let validators = self + .active_validators + .iter() + .map(|validator| { + let name = AuthorityName::from_bytes(&validator.authority_pubkey_bytes).unwrap(); + ( + name, + ( + validator.voting_power, + NetworkMetadata { + network_address: Multiaddr::try_from(validator.net_address.clone()) + .unwrap(), + primary_address: Multiaddr::try_from(validator.primary_address.clone()) + .unwrap(), + }, + ), + ) + }) + .collect(); + CommitteeWithNetworkMetadata::new(self.epoch, validators) + } +} + +impl IotaSystemStateSummaryV2 { pub fn get_iota_committee_for_benchmarking(&self) -> CommitteeWithNetworkMetadata { let validators = self .active_validators @@ -320,7 +530,7 @@ pub struct IotaValidatorSummary { impl Default for IotaSystemStateSummary { fn default() -> Self { - Self { + Self::V2(IotaSystemStateSummaryV2 { epoch: 0, protocol_version: 1, system_state_version: 1, @@ -331,7 +541,8 @@ impl Default for IotaSystemStateSummary { reference_gas_price: 1, safe_mode: false, safe_mode_storage_charges: 0, - safe_mode_computation_rewards: 0, + safe_mode_computation_charges: 0, + safe_mode_computation_charges_burned: 0, safe_mode_storage_rebates: 0, safe_mode_non_refundable_storage_fee: 0, epoch_start_timestamp_ms: 0, @@ -355,7 +566,7 @@ impl Default for IotaSystemStateSummary { validator_candidates_size: 0, at_risk_validators: vec![], validator_report_records: vec![], - } + }) } } @@ -412,6 +623,73 @@ pub fn get_validator_by_pool_id( system_state_summary: &IotaSystemStateSummary, pool_id: ObjectID, ) -> Result +where + S: ObjectStore + ?Sized, +{ + match system_state_summary { + IotaSystemStateSummary::V1(summary) => { + get_validator_by_pool_id_v1(object_store, system_state, summary, pool_id) + } + IotaSystemStateSummary::V2(summary) => { + get_validator_by_pool_id_v2(object_store, system_state, summary, pool_id) + } + } +} + +fn get_validator_by_pool_id_v1( + object_store: &S, + system_state: &IotaSystemState, + system_state_summary: &IotaSystemStateSummaryV1, + pool_id: ObjectID, +) -> Result +where + S: ObjectStore + ?Sized, +{ + // First try to find in active validator set. + let active_validator = system_state_summary + .active_validators + .iter() + .find(|v| v.staking_pool_id == pool_id); + if let Some(active) = active_validator { + return Ok(active.clone()); + } + // Then try to find in pending active validator set. + let pending_active_validators = system_state.get_pending_active_validators(object_store)?; + let pending_active = pending_active_validators + .iter() + .find(|v| v.staking_pool_id == pool_id); + if let Some(pending) = pending_active { + return Ok(pending.clone()); + } + // After that try to find in inactive pools. + let inactive_table_id = system_state_summary.inactive_pools_id; + if let Ok(inactive) = + get_validator_from_table(&object_store, inactive_table_id, &ID::new(pool_id)) + { + return Ok(inactive); + } + // Finally look up the candidates pool. + let candidate_address: IotaAddress = get_dynamic_field_from_store( + &object_store, + system_state_summary.staking_pool_mappings_id, + &ID::new(pool_id), + ) + .map_err(|err| { + IotaError::IotaSystemStateRead(format!( + "Failed to load candidate address from pool mappings: {:?}", + err + )) + })?; + let candidate_table_id = system_state_summary.validator_candidates_id; + get_validator_from_table(&object_store, candidate_table_id, &candidate_address) +} + +fn get_validator_by_pool_id_v2( + object_store: &S, + system_state: &IotaSystemState, + system_state_summary: &IotaSystemStateSummaryV2, + pool_id: ObjectID, +) -> Result where S: ObjectStore + ?Sized, { diff --git a/crates/iota-types/src/iota_system_state/mod.rs b/crates/iota-types/src/iota_system_state/mod.rs index ab8c4545a5a..9a5f33286d4 100644 --- a/crates/iota-types/src/iota_system_state/mod.rs +++ b/crates/iota-types/src/iota_system_state/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize, de::DeserializeOwned}; use self::{ iota_system_state_inner_v1::{IotaSystemStateV1, ValidatorV1}, + iota_system_state_inner_v2::IotaSystemStateV2, iota_system_state_summary::{IotaSystemStateSummary, IotaValidatorSummary}, }; use crate::{ @@ -29,6 +30,7 @@ use crate::{ pub mod epoch_start_iota_system_state; pub mod iota_system_state_inner_v1; +pub mod iota_system_state_inner_v2; pub mod iota_system_state_summary; #[cfg(msim)] @@ -102,6 +104,13 @@ impl IotaSystemStateWrapper { protocol_config, ); } + 2 => { + Self::advance_epoch_safe_mode_impl::( + move_object, + params, + protocol_config, + ); + } #[cfg(msim)] IOTA_SYSTEM_STATE_SIM_TEST_V1 => { Self::advance_epoch_safe_mode_impl::( @@ -190,6 +199,7 @@ pub trait IotaSystemStateTrait { #[enum_dispatch(IotaSystemStateTrait)] pub enum IotaSystemState { V1(IotaSystemStateV1), + V2(IotaSystemStateV2), #[cfg(msim)] SimTestV1(SimTestIotaSystemStateV1), #[cfg(msim)] @@ -211,11 +221,8 @@ impl IotaSystemState { pub fn into_genesis_version_for_tooling(self) -> IotaSystemStateInnerGenesis { match self { IotaSystemState::V1(inner) => inner, - #[cfg(msim)] - _ => { - // Types other than V1 used in simtests should be unreachable - unreachable!() - } + // Types other than V1 should be unreachable + _ => unreachable!(), } } @@ -259,6 +266,18 @@ pub fn get_iota_system_state(object_store: &dyn ObjectStore) -> Result { + let result: IotaSystemStateV2 = + get_dynamic_field_from_store(object_store, id, &wrapper.version).map_err( + |err| { + IotaError::DynamicFieldRead(format!( + "Failed to load iota system state inner object with ID {:?} and version {:?}: {:?}", + id, wrapper.version, err + )) + }, + )?; + Ok(IotaSystemState::V2(result)) + } #[cfg(msim)] IOTA_SYSTEM_STATE_SIM_TEST_V1 => { let result: SimTestIotaSystemStateV1 = @@ -418,9 +437,10 @@ pub struct Validator { pub struct AdvanceEpochParams { pub epoch: u64, pub next_protocol_version: ProtocolVersion, - pub validator_target_reward: u64, + pub validator_subsidy: u64, pub storage_charge: u64, pub computation_charge: u64, + pub computation_charge_burned: u64, pub storage_rebate: u64, pub non_refundable_storage_fee: u64, pub reward_slashing_rate: u64, diff --git a/crates/iota-types/src/storage/read_store.rs b/crates/iota-types/src/storage/read_store.rs index 2c7e26e665f..27399a60031 100644 --- a/crates/iota-types/src/storage/read_store.rs +++ b/crates/iota-types/src/storage/read_store.rs @@ -53,12 +53,12 @@ pub trait ReadStore: ObjectStore { Ok(latest_checkpoint.epoch()) } - /// Get the highest verified checkpint. This is the highest checkpoint + /// Get the highest verified checkpoint. This is the highest checkpoint /// summary that has been verified, generally by state-sync. Only the /// checkpoint header is guaranteed to be present in the store. fn get_highest_verified_checkpoint(&self) -> Result; - /// Get the highest synced checkpint. This is the highest checkpoint that + /// Get the highest synced checkpoint. This is the highest checkpoint that /// has been synced from state-synce. The checkpoint header, contents, /// transactions, and effects of this checkpoint are guaranteed to be /// present in the store diff --git a/crates/iota-types/src/transaction.rs b/crates/iota-types/src/transaction.rs index b7f18c242f8..63b8b9ad45e 100644 --- a/crates/iota-types/src/transaction.rs +++ b/crates/iota-types/src/transaction.rs @@ -58,7 +58,7 @@ use crate::{ pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000; pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000; -pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 70_000; +pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 50_000; pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000; pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000; pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000; @@ -165,6 +165,7 @@ fn type_tag_validity_check( Ok(()) } +// System transaction for advancing the epoch. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct ChangeEpoch { /// The next (to become) epoch ID. @@ -190,6 +191,36 @@ pub struct ChangeEpoch { pub system_packages: Vec<(SequenceNumber, Vec>, Vec)>, } +// System transaction for advancing the epoch. +// This version includes the computation_charge_burned field for when +// protocol_defined_base_fee is enabled in the protocol config. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +pub struct ChangeEpochV2 { + /// The next (to become) epoch ID. + pub epoch: EpochId, + /// The protocol version in effect in the new epoch. + pub protocol_version: ProtocolVersion, + /// The total amount of gas charged for storage during the epoch. + pub storage_charge: u64, + /// The total amount of gas charged for computation during the epoch. + pub computation_charge: u64, + /// The burned component of the total computation/execution costs. + pub computation_charge_burned: u64, + /// The amount of storage rebate refunded to the txn senders. + pub storage_rebate: u64, + /// The non-refundable storage fee. + pub non_refundable_storage_fee: u64, + /// Unix timestamp when epoch started + pub epoch_start_timestamp_ms: u64, + /// System packages (specifically framework and move stdlib) that are + /// written before the new epoch starts. This tracks framework upgrades + /// on chain. When executing the ChangeEpochV2 txn, the validator must + /// write out the modules below. Modules are provided with the version they + /// will be upgraded to, their modules in serialized form (which include + /// their package ID), and a list of their transitive dependencies. + pub system_packages: Vec<(SequenceNumber, Vec>, Vec)>, +} + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct GenesisTransaction { pub objects: Vec, @@ -295,6 +326,7 @@ pub enum TransactionKind { #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, IntoStaticStr)] pub enum EndOfEpochTransactionKind { ChangeEpoch(ChangeEpoch), + ChangeEpochV2(ChangeEpochV2), AuthenticatorStateCreate, AuthenticatorStateExpire(AuthenticatorStateExpire), BridgeStateCreate(ChainIdentifier), @@ -324,6 +356,30 @@ impl EndOfEpochTransactionKind { }) } + pub fn new_change_epoch_v2( + next_epoch: EpochId, + protocol_version: ProtocolVersion, + storage_charge: u64, + computation_charge: u64, + computation_charge_burned: u64, + storage_rebate: u64, + non_refundable_storage_fee: u64, + epoch_start_timestamp_ms: u64, + system_packages: Vec<(SequenceNumber, Vec>, Vec)>, + ) -> Self { + Self::ChangeEpochV2(ChangeEpochV2 { + epoch: next_epoch, + protocol_version, + storage_charge, + computation_charge, + computation_charge_burned, + storage_rebate, + non_refundable_storage_fee, + epoch_start_timestamp_ms, + system_packages, + }) + } + pub fn new_authenticator_state_expire( min_epoch: u64, authenticator_obj_initial_shared_version: SequenceNumber, @@ -355,6 +411,13 @@ impl EndOfEpochTransactionKind { mutable: true, }] } + Self::ChangeEpochV2(_) => { + vec![InputObjectKind::SharedMoveObject { + id: IOTA_SYSTEM_STATE_OBJECT_ID, + initial_shared_version: IOTA_SYSTEM_STATE_OBJECT_SHARED_VERSION, + mutable: true, + }] + } Self::AuthenticatorStateCreate => vec![], Self::AuthenticatorStateExpire(expire) => { vec![InputObjectKind::SharedMoveObject { @@ -384,6 +447,9 @@ impl EndOfEpochTransactionKind { Self::ChangeEpoch(_) => { Either::Left(vec![SharedInputObject::IOTA_SYSTEM_OBJ].into_iter()) } + Self::ChangeEpochV2(_) => { + Either::Left(vec![SharedInputObject::IOTA_SYSTEM_OBJ].into_iter()) + } Self::AuthenticatorStateExpire(expire) => Either::Left( vec![SharedInputObject { id: IOTA_AUTHENTICATOR_STATE_OBJECT_ID, @@ -410,7 +476,20 @@ impl EndOfEpochTransactionKind { fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult { match self { - Self::ChangeEpoch(_) => (), + Self::ChangeEpoch(_) => { + if config.protocol_defined_base_fee() { + return Err(UserInputError::Unsupported( + "protocol defined base fee not supported".to_string(), + )); + } + } + Self::ChangeEpochV2(_) => { + if !config.protocol_defined_base_fee() { + return Err(UserInputError::Unsupported( + "protocol defined base fee required".to_string(), + )); + } + } Self::AuthenticatorStateCreate | Self::AuthenticatorStateExpire(_) => { if !config.enable_jwk_consensus_updates() { return Err(UserInputError::Unsupported( @@ -1146,20 +1225,20 @@ impl TransactionKind { /// gas rebated). TODO: We should use GasCostSummary directly in /// ChangeEpoch struct, and return that directly. pub fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> { - let e = match self { + match self { Self::EndOfEpochTransaction(txns) => { - if let EndOfEpochTransactionKind::ChangeEpoch(e) = - txns.last().expect("at least one end-of-epoch txn required") - { - e - } else { - panic!("final end-of-epoch txn must be ChangeEpoch") + match txns.last().expect("at least one end-of-epoch txn required") { + EndOfEpochTransactionKind::ChangeEpoch(e) => { + Some((e.computation_charge + e.storage_charge, e.storage_rebate)) + } + EndOfEpochTransactionKind::ChangeEpochV2(e) => { + Some((e.computation_charge + e.storage_charge, e.storage_rebate)) + } + _ => panic!("final end-of-epoch txn must be ChangeEpoch"), } } - _ => return None, - }; - - Some((e.computation_charge + e.storage_charge, e.storage_rebate)) + _ => None, + } } pub fn contains_shared_object(&self) -> bool { diff --git a/iota-execution/latest/iota-adapter/src/execution_engine.rs b/iota-execution/latest/iota-adapter/src/execution_engine.rs index 9b9128532b6..645678074b9 100644 --- a/iota-execution/latest/iota-adapter/src/execution_engine.rs +++ b/iota-execution/latest/iota-adapter/src/execution_engine.rs @@ -56,8 +56,9 @@ mod checked { storage::{BackingStore, Storage}, transaction::{ Argument, AuthenticatorStateExpire, AuthenticatorStateUpdateV1, CallArg, ChangeEpoch, - CheckedInputObjects, Command, EndOfEpochTransactionKind, GenesisTransaction, ObjectArg, - ProgrammableTransaction, RandomnessStateUpdate, TransactionKind, + ChangeEpochV2, CheckedInputObjects, Command, EndOfEpochTransactionKind, + GenesisTransaction, ObjectArg, ProgrammableTransaction, RandomnessStateUpdate, + TransactionKind, }, }; use move_binary_format::CompiledModule; @@ -202,6 +203,7 @@ mod checked { trace!( tx_digest = ?transaction_digest, computation_gas_cost = gas_cost_summary.computation_cost, + computation_gas_cost_burned = gas_cost_summary.computation_cost_burned, storage_gas_cost = gas_cost_summary.storage_cost, storage_gas_rebate = gas_cost_summary.storage_rebate, "Finished execution of transaction with status {:?}", @@ -664,7 +666,7 @@ mod checked { match tx { EndOfEpochTransactionKind::ChangeEpoch(change_epoch) => { assert_eq!(i, len - 1); - advance_epoch( + advance_epoch_v1( builder, change_epoch, temporary_store, @@ -676,6 +678,20 @@ mod checked { )?; return Ok(Mode::empty_results()); } + EndOfEpochTransactionKind::ChangeEpochV2(change_epoch_v2) => { + assert_eq!(i, len - 1); + advance_epoch_v2( + builder, + change_epoch_v2, + temporary_store, + tx_ctx, + move_vm, + gas_charger, + protocol_config, + metrics, + )?; + return Ok(Mode::empty_results()); + } EndOfEpochTransactionKind::AuthenticatorStateCreate => { assert!(protocol_config.enable_jwk_consensus_updates()); builder = setup_authenticator_state_create(builder); @@ -731,11 +747,11 @@ mod checked { Ok(result) } - /// Mints epoch rewards by creating both storage and computation rewards + /// Mints epoch rewards by creating both storage and computation charges /// using a `ProgrammableTransactionBuilder`. The function takes in the /// `AdvanceEpochParams`, serializes the storage and computation /// charges, and invokes the reward creation function within the IOTA - /// Prepares invocations for creating both storage and computation rewards + /// Prepares invocations for creating both storage and computation charges /// with a `ProgrammableTransactionBuilder` using the `AdvanceEpochParams`. /// The corresponding functions from the IOTA framework can be invoked later /// during execution of the programmable transaction. @@ -757,56 +773,48 @@ mod checked { vec![storage_charge_arg], ); - // Create computation rewards. + // Create computation charges. let computation_charge_arg = builder .input(CallArg::Pure( bcs::to_bytes(¶ms.computation_charge).unwrap(), )) .unwrap(); - let computation_rewards = builder.programmable_move_call( + let computation_charges = builder.programmable_move_call( IOTA_FRAMEWORK_PACKAGE_ID, BALANCE_MODULE_NAME.to_owned(), BALANCE_CREATE_REWARDS_FUNCTION_NAME.to_owned(), vec![GAS::type_tag()], vec![computation_charge_arg], ); - (storage_charges, computation_rewards) + (storage_charges, computation_charges) } /// Constructs a `ProgrammableTransaction` to advance the epoch. It creates - /// storage charges and computation rewards by invoking + /// storage charges and computation charges by invoking /// `mint_epoch_rewards_in_pt`, advances the epoch by setting up the /// necessary arguments, such as epoch number, protocol version, storage /// rebate, and slashing rate, and executing the `advance_epoch` function /// within the IOTA system. Then, it destroys the storage rebates to /// complete the transaction. - pub fn construct_advance_epoch_pt( + pub fn construct_advance_epoch_pt_impl( mut builder: ProgrammableTransactionBuilder, params: &AdvanceEpochParams, + call_arg_vec: Vec, ) -> Result { - // Step 1: Create storage charges and computation rewards. - let (storage_charges, computation_rewards) = mint_epoch_rewards_in_pt(&mut builder, params); - - // Step 2: Advance the epoch. + // Create storage and computation charges and add them as arguments. + let (storage_charges, computation_charges) = mint_epoch_rewards_in_pt(&mut builder, params); let mut arguments = vec![ builder - .pure(params.validator_target_reward) + .pure(params.validator_subsidy) .expect("bcs encoding a u64 should not fail"), storage_charges, - computation_rewards, + computation_charges, ]; - let call_arg_arguments = vec![ - CallArg::IOTA_SYSTEM_MUT, - CallArg::Pure(bcs::to_bytes(¶ms.epoch).unwrap()), - CallArg::Pure(bcs::to_bytes(¶ms.next_protocol_version.as_u64()).unwrap()), - CallArg::Pure(bcs::to_bytes(¶ms.storage_rebate).unwrap()), - CallArg::Pure(bcs::to_bytes(¶ms.non_refundable_storage_fee).unwrap()), - CallArg::Pure(bcs::to_bytes(¶ms.reward_slashing_rate).unwrap()), - CallArg::Pure(bcs::to_bytes(¶ms.epoch_start_timestamp_ms).unwrap()), - ] - .into_iter() - .map(|a| builder.input(a)) - .collect::>(); + + let call_arg_arguments = call_arg_vec + .into_iter() + .map(|a| builder.input(a)) + .collect::>(); assert_invariant!( call_arg_arguments.is_ok(), @@ -836,16 +844,49 @@ mod checked { Ok(builder.finish()) } - /// Advances the epoch by constructing a `ProgrammableTransaction` with - /// `construct_advance_epoch_pt` and executing it. - /// If the transaction fails, it switches to safe mode and retries the - /// epoch advancement in a more controlled environment. The function also + pub fn construct_advance_epoch_pt_v1( + builder: ProgrammableTransactionBuilder, + params: &AdvanceEpochParams, + ) -> Result { + let call_arg_vec = vec![ + CallArg::IOTA_SYSTEM_MUT, + CallArg::Pure(bcs::to_bytes(¶ms.epoch).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.next_protocol_version.as_u64()).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.storage_rebate).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.non_refundable_storage_fee).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.reward_slashing_rate).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.epoch_start_timestamp_ms).unwrap()), + ]; + construct_advance_epoch_pt_impl(builder, params, call_arg_vec) + } + + pub fn construct_advance_epoch_pt_v2( + builder: ProgrammableTransactionBuilder, + params: &AdvanceEpochParams, + ) -> Result { + let call_arg_vec = vec![ + CallArg::Pure(bcs::to_bytes(¶ms.computation_charge_burned).unwrap()), + CallArg::IOTA_SYSTEM_MUT, + CallArg::Pure(bcs::to_bytes(¶ms.epoch).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.next_protocol_version.as_u64()).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.storage_rebate).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.non_refundable_storage_fee).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.reward_slashing_rate).unwrap()), + CallArg::Pure(bcs::to_bytes(¶ms.epoch_start_timestamp_ms).unwrap()), + ]; + construct_advance_epoch_pt_impl(builder, params, call_arg_vec) + } + + /// Advances the epoch by executing a `ProgrammableTransaction`. If the + /// transaction fails, it switches to safe mode and retries the epoch + /// advancement in a more controlled environment. The function also /// handles the publication and upgrade of system packages for the new /// epoch. If any system package is added or upgraded, it ensures the /// proper execution and storage of the changes. - fn advance_epoch( - builder: ProgrammableTransactionBuilder, - change_epoch: ChangeEpoch, + fn advance_epoch_impl( + advance_epoch_pt: ProgrammableTransaction, + params: AdvanceEpochParams, + system_packages: Vec<(SequenceNumber, Vec>, Vec)>, temporary_store: &mut TemporaryStore<'_>, tx_ctx: &mut TxContext, move_vm: &Arc, @@ -853,18 +894,6 @@ mod checked { protocol_config: &ProtocolConfig, metrics: Arc, ) -> Result<(), ExecutionError> { - let params = AdvanceEpochParams { - epoch: change_epoch.epoch, - next_protocol_version: change_epoch.protocol_version, - validator_target_reward: protocol_config.validator_target_reward(), - storage_charge: change_epoch.storage_charge, - computation_charge: change_epoch.computation_charge, - storage_rebate: change_epoch.storage_rebate, - non_refundable_storage_fee: change_epoch.non_refundable_storage_fee, - reward_slashing_rate: protocol_config.reward_slashing_rate(), - epoch_start_timestamp_ms: change_epoch.epoch_start_timestamp_ms, - }; - let advance_epoch_pt = construct_advance_epoch_pt(builder, ¶ms)?; let result = programmable_transactions::execution::execute::( protocol_config, metrics.clone(), @@ -876,14 +905,14 @@ mod checked { ); #[cfg(msim)] - let result = maybe_modify_result(result, change_epoch.epoch); + let result = maybe_modify_result(result, params.epoch); if result.is_err() { tracing::error!( - "Failed to execute advance epoch transaction. Switching to safe mode. Error: {:?}. Input objects: {:?}. Tx data: {:?}", + "Failed to execute advance epoch transaction. Switching to safe mode. Error: {:?}. Input objects: {:?}. Tx params: {:?}", result.as_ref().err(), temporary_store.objects(), - change_epoch, + params, ); temporary_store.drop_writes(); // Must reset the storage rebate since we are re-executing. @@ -900,7 +929,7 @@ mod checked { ) .expect("Failed to create new MoveVM"); process_system_packages( - change_epoch, + system_packages, temporary_store, tx_ctx, &new_vm, @@ -912,17 +941,96 @@ mod checked { Ok(()) } - fn process_system_packages( + /// Advances the epoch for the given `ChangeEpoch` transaction kind by + /// constructing a programmable transaction, executing it and processing the + /// system packages. + fn advance_epoch_v1( + builder: ProgrammableTransactionBuilder, change_epoch: ChangeEpoch, temporary_store: &mut TemporaryStore<'_>, tx_ctx: &mut TxContext, + move_vm: &Arc, + gas_charger: &mut GasCharger, + protocol_config: &ProtocolConfig, + metrics: Arc, + ) -> Result<(), ExecutionError> { + let params = AdvanceEpochParams { + epoch: change_epoch.epoch, + next_protocol_version: change_epoch.protocol_version, + validator_subsidy: protocol_config.validator_target_reward(), + storage_charge: change_epoch.storage_charge, + computation_charge: change_epoch.computation_charge, + // all computation charge is burned in v1 + computation_charge_burned: change_epoch.computation_charge, + storage_rebate: change_epoch.storage_rebate, + non_refundable_storage_fee: change_epoch.non_refundable_storage_fee, + reward_slashing_rate: protocol_config.reward_slashing_rate(), + epoch_start_timestamp_ms: change_epoch.epoch_start_timestamp_ms, + }; + let advance_epoch_pt = construct_advance_epoch_pt_v1(builder, ¶ms)?; + advance_epoch_impl( + advance_epoch_pt, + params, + change_epoch.system_packages, + temporary_store, + tx_ctx, + move_vm, + gas_charger, + protocol_config, + metrics, + ) + } + + /// Advances the epoch for the given `ChangeEpochV2` transaction kind by + /// constructing a programmable transaction, executing it and processing the + /// system packages. + fn advance_epoch_v2( + builder: ProgrammableTransactionBuilder, + change_epoch_v2: ChangeEpochV2, + temporary_store: &mut TemporaryStore<'_>, + tx_ctx: &mut TxContext, + move_vm: &Arc, + gas_charger: &mut GasCharger, + protocol_config: &ProtocolConfig, + metrics: Arc, + ) -> Result<(), ExecutionError> { + let params = AdvanceEpochParams { + epoch: change_epoch_v2.epoch, + next_protocol_version: change_epoch_v2.protocol_version, + validator_subsidy: protocol_config.validator_target_reward(), + storage_charge: change_epoch_v2.storage_charge, + computation_charge: change_epoch_v2.computation_charge, + computation_charge_burned: change_epoch_v2.computation_charge_burned, + storage_rebate: change_epoch_v2.storage_rebate, + non_refundable_storage_fee: change_epoch_v2.non_refundable_storage_fee, + reward_slashing_rate: protocol_config.reward_slashing_rate(), + epoch_start_timestamp_ms: change_epoch_v2.epoch_start_timestamp_ms, + }; + let advance_epoch_pt = construct_advance_epoch_pt_v2(builder, ¶ms)?; + advance_epoch_impl( + advance_epoch_pt, + params, + change_epoch_v2.system_packages, + temporary_store, + tx_ctx, + move_vm, + gas_charger, + protocol_config, + metrics, + ) + } + + fn process_system_packages( + system_packages: Vec<(SequenceNumber, Vec>, Vec)>, + temporary_store: &mut TemporaryStore<'_>, + tx_ctx: &mut TxContext, move_vm: &MoveVM, gas_charger: &mut GasCharger, protocol_config: &ProtocolConfig, metrics: Arc, ) { let binary_config = to_binary_config(protocol_config); - for (version, modules, dependencies) in change_epoch.system_packages.into_iter() { + for (version, modules, dependencies) in system_packages.into_iter() { let deserialized_modules: Vec<_> = modules .iter() .map(|m| CompiledModule::deserialize_with_config(m, &binary_config).unwrap())