diff --git a/crates/iota-framework/docs/iota-framework/balance.mdx b/crates/iota-framework/docs/iota-framework/balance.mdx index 05c1ad05c10..ba5de1a5922 100644 --- a/crates/iota-framework/docs/iota-framework/balance.mdx +++ b/crates/iota-framework/docs/iota-framework/balance.mdx @@ -27,6 +27,7 @@ custom coins with - [Function `destroy_zero`](#0x2_balance_destroy_zero) - [Function `create_staking_rewards`](#0x2_balance_create_staking_rewards) - [Function `destroy_storage_rebates`](#0x2_balance_destroy_storage_rebates) +- [Function `destroy_genesis_supply`](#0x2_balance_destroy_genesis_supply) - [Function `destroy_supply`](#0x2_balance_destroy_supply) @@ -138,6 +139,17 @@ For when trying to withdraw more than there is. + + +Epoch is not 0 (the genesis epoch). + + +

+const ENotGenesisEpoch: u64 = 4;
+
+ + + For when an overflow is happening on Supply operations. @@ -498,6 +510,39 @@ and nowhere else. + + + + +## Function `destroy_genesis_supply` + +CAUTION: this function destroys a +Balance without decreasing the supply. +It should only be called by the genesis txn to destroy parts of the IOTA supply +which was created during the migration and for no other reason. + + +

+fun destroy_genesis_supply<T>(self: balance::Balance<T>, ctx: &tx_context::TxContext)
+
+ + + +
+Implementation + + +

+fun destroy_genesis_supply<T>(self: Balance<T>, ctx: &TxContext) \{
+    assert!(ctx.sender() == @0x0, ENotSystemAddress);
+    assert!(ctx.epoch() == 0, ENotGenesisEpoch);
+
+    let Balance \{ value: _ } = self;
+}
+
+ + +
diff --git a/crates/iota-framework/docs/iota-framework/iota.mdx b/crates/iota-framework/docs/iota-framework/iota.mdx index 84f7792df9f..ca455dba1be 100644 --- a/crates/iota-framework/docs/iota-framework/iota.mdx +++ b/crates/iota-framework/docs/iota-framework/iota.mdx @@ -9,9 +9,15 @@ It has 9 decimals, and the smallest unit (10^-9) is called "nano". - [Struct `IOTA`](#0x2_iota_IOTA) +- [Struct `IotaTreasuryCap`](#0x2_iota_IotaTreasuryCap) - [Constants](#@Constants_0) - [Function `new`](#0x2_iota_new) - [Function `transfer`](#0x2_iota_transfer) +- [Function `mint`](#0x2_iota_mint) +- [Function `mint_balance`](#0x2_iota_mint_balance) +- [Function `burn`](#0x2_iota_burn) +- [Function `burn_balance`](#0x2_iota_burn_balance) +- [Function `total_supply`](#0x2_iota_total_supply)

@@ -55,51 +61,59 @@ dummy_field: bool
 
 
 
-
-
-## Constants
+
 
+## Struct `IotaTreasuryCap`
 
-
-
-Sender is not @0x0 the system address.
+The IOTA token treasury capability.
+Protects the token from unauthorized changes.
 
 
 

-const ENotSystemAddress: u64 = 1;
+struct IotaTreasuryCap has store
 
- +
+Fields +
+
+ +inner: coin::TreasuryCap<iota::IOTA> +
+
-

-const EAlreadyMinted: u64 = 0;
-
+
+
+
- + + +## Constants -The amount of Nanos per IOTA token based on the fact that nano is -10^-9 of a IOTA token + + + +Sender is not @0x0 the system address.

-const NANO_PER_IOTA: u64 = 1000000000;
+const ENotSystemAddress: u64 = 1;
 
- + -The total supply of IOTA denominated in Nano (4.6 Billion * 10^9)

-const TOTAL_SUPPLY_NANO: u64 = 4600000000000000000;
+const EAlreadyMinted: u64 = 0;
 
@@ -109,13 +123,13 @@ The total supply of IOTA denominated in Nano (4.6 Billion * 10^9) ## Function `new` Register the -IOTA Coin to acquire its -Supply. +IOTA
Coin to acquire +IotaTreasuryCap. This should be called only once during genesis creation.

-fun new(ctx: &mut tx_context::TxContext): balance::Balance<iota::IOTA>
+fun new(ctx: &mut tx_context::TxContext): iota::IotaTreasuryCap
 
@@ -125,7 +139,7 @@ This should be called only once during genesis creation.

-fun new(ctx: &mut TxContext): Balance<IOTA> \{
+fun new(ctx: &mut TxContext): IotaTreasuryCap \{
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
     assert!(ctx.epoch() == 0, EAlreadyMinted);
 
@@ -138,11 +152,12 @@ This should be called only once during genesis creation.
         option::some(url::new_unsafe_from_bytes(b"https://iota.org/logo.png")),
         ctx
     );
+
     transfer::public_freeze_object(metadata);
-    let mut supply = treasury.treasury_into_supply();
-    let total_iota = supply.increase_supply(TOTAL_SUPPLY_NANO);
-    supply.destroy_supply();
-    total_iota
+
+    IotaTreasuryCap \{
+        inner: treasury,
+    }
 }
 
@@ -174,4 +189,158 @@ This should be called only once during genesis creation. + + + + +## Function `mint` + +Create an IOTA coin worth +value and increase the total supply in +cap accordingly. + + +

+public fun mint(cap: &mut iota::IotaTreasuryCap, value: u64, ctx: &mut tx_context::TxContext): coin::Coin<iota::IOTA>
+
+ + + +
+Implementation + + +

+public fun mint(cap: &mut IotaTreasuryCap, value: u64, ctx: &mut TxContext): Coin<IOTA> \{
+    assert!(ctx.sender() == @0x0, ENotSystemAddress);
+
+    cap.inner.mint(value, ctx)
+}
+
+ + + +
+ + + +## Function `mint_balance` + +Mint some amount of IOTA as a +Balance and increase the total supply in +cap accordingly. +Aborts if +value + +cap.inner.total_supply >= U64_MAX + + +

+public fun mint_balance(cap: &mut iota::IotaTreasuryCap, value: u64, ctx: &tx_context::TxContext): balance::Balance<iota::IOTA>
+
+ + + +
+Implementation + + +

+public fun mint_balance(cap: &mut IotaTreasuryCap, value: u64, ctx: &TxContext): Balance<IOTA> \{
+    assert!(ctx.sender() == @0x0, ENotSystemAddress);
+
+    cap.inner.mint_balance(value)
+}
+
+ + + +
+ + + +## Function `burn` + +Destroy the IOTA coin +c and decrease the total supply in +cap accordingly. + + +

+public fun burn(cap: &mut iota::IotaTreasuryCap, c: coin::Coin<iota::IOTA>, ctx: &tx_context::TxContext): u64
+
+ + + +
+Implementation + + +

+public fun burn(cap: &mut IotaTreasuryCap, c: Coin<IOTA>, ctx: &TxContext): u64 \{
+    assert!(ctx.sender() == @0x0, ENotSystemAddress);
+
+    cap.inner.burn(c)
+}
+
+ + + +
+ + + +## Function `burn_balance` + +Destroy the IOTA balance +b and decrease the total supply in +cap accordingly. + + +

+public fun burn_balance(cap: &mut iota::IotaTreasuryCap, b: balance::Balance<iota::IOTA>, ctx: &tx_context::TxContext): u64
+
+ + + +
+Implementation + + +

+public fun burn_balance(cap: &mut IotaTreasuryCap, b: Balance<IOTA>, ctx: &TxContext): u64 \{
+    assert!(ctx.sender() == @0x0, ENotSystemAddress);
+
+    cap.inner.supply_mut().decrease_supply(b)
+}
+
+ + + +
+ + + +## Function `total_supply` + +Return the total number of IOTA's in circulation. + + +

+public fun total_supply(cap: &iota::IotaTreasuryCap): u64
+
+ + + +
+Implementation + + +

+public fun total_supply(cap: &IotaTreasuryCap): u64 \{
+    cap.inner.total_supply()
+}
+
+ + +
diff --git a/crates/iota-framework/docs/iota-system/genesis.mdx b/crates/iota-framework/docs/iota-system/genesis.mdx index 426282e6869..c47301a1371 100644 --- a/crates/iota-framework/docs/iota-system/genesis.mdx +++ b/crates/iota-framework/docs/iota-system/genesis.mdx @@ -288,7 +288,7 @@ validator_low_stake_grace_period: u64
-stake_subsidy_fund_nanos: u64 +pre_minted_supply: u64
@@ -385,6 +385,18 @@ create function was called with duplicate validators. + + +The +create function was called with wrong pre-minted supply. + + +

+const EWrongPreMintedSupply: u64 = 2;
+
+ + + ## Function `create` @@ -395,7 +407,7 @@ all the information we need in the system.

-fun create(iota_system_state_id: object::UID, iota_supply: balance::Balance<iota::IOTA>, genesis_chain_parameters: genesis::GenesisChainParameters, genesis_validators: vector<genesis::GenesisValidatorMetadata>, token_distribution_schedule: genesis::TokenDistributionSchedule, timelock_genesis_label: option::Option<string::String>, system_timelock_cap: timelock::SystemTimelockCap, ctx: &mut tx_context::TxContext)
+fun create(iota_system_state_id: object::UID, iota_treasury_cap: iota::IotaTreasuryCap, genesis_chain_parameters: genesis::GenesisChainParameters, genesis_validators: vector<genesis::GenesisValidatorMetadata>, token_distribution_schedule: genesis::TokenDistributionSchedule, timelock_genesis_label: option::Option<string::String>, system_timelock_cap: timelock::SystemTimelockCap, ctx: &mut tx_context::TxContext)
 
@@ -407,7 +419,7 @@ all the information we need in the system.

 fun create(
     iota_system_state_id: UID,
-    mut iota_supply: Balance<IOTA>,
+    mut iota_treasury_cap: IotaTreasuryCap,
     genesis_chain_parameters: GenesisChainParameters,
     genesis_validators: vector<GenesisValidatorMetadata>,
     token_distribution_schedule: TokenDistributionSchedule,
@@ -419,11 +431,13 @@ all the information we need in the system.
     assert!(ctx.epoch() == 0, ENotCalledAtGenesis);
 
     let TokenDistributionSchedule \{
-        stake_subsidy_fund_nanos,
+        pre_minted_supply,
         allocations,
     } = token_distribution_schedule;
 
-    let subsidy_fund = iota_supply.split(stake_subsidy_fund_nanos);
+    assert!(iota_treasury_cap.total_supply() == pre_minted_supply, EWrongPreMintedSupply);
+
+    let subsidy_fund = balance::zero();
     let storage_fund = balance::zero();
 
     // Create all the `Validator` structs
@@ -481,7 +495,7 @@ all the information we need in the system.
 
     // Allocate tokens and staking operations
     allocate_tokens(
-        iota_supply,
+        &mut iota_treasury_cap,
         allocations,
         &mut validators,
         timelock_genesis_label,
@@ -515,6 +529,7 @@ all the information we need in the system.
 
     iota_system::create(
         iota_system_state_id,
+        iota_treasury_cap,
         validators,
         storage_fund,
         genesis_chain_parameters.protocol_version,
@@ -538,7 +553,7 @@ all the information we need in the system.
 
 
 

-fun allocate_tokens(iota_supply: balance::Balance<iota::IOTA>, allocations: vector<genesis::TokenAllocation>, validators: &mut vector<validator::Validator>, timelock_genesis_label: option::Option<string::String>, ctx: &mut tx_context::TxContext)
+fun allocate_tokens(iota_treasury_cap: &mut iota::IotaTreasuryCap, allocations: vector<genesis::TokenAllocation>, validators: &mut vector<validator::Validator>, timelock_genesis_label: option::Option<string::String>, ctx: &mut tx_context::TxContext)
 
@@ -549,7 +564,7 @@ all the information we need in the system.

 fun allocate_tokens(
-    mut iota_supply: Balance<IOTA>,
+    iota_treasury_cap: &mut IotaTreasuryCap,
     mut allocations: vector<TokenAllocation>,
     validators: &mut vector<Validator>,
     timelock_genesis_label: Option<String>,
@@ -564,7 +579,7 @@ all the information we need in the system.
             staked_with_timelock_expiration,
         } = allocations.pop_back();
 
-        let allocation_balance = iota_supply.split(amount_nanos);
+        let allocation_balance = iota_treasury_cap.mint_balance(amount_nanos, ctx);
 
         if (staked_with_validator.is_some()) \{
             let validator_address = staked_with_validator.destroy_some();
@@ -596,10 +611,6 @@ all the information we need in the system.
         };
     };
     allocations.destroy_empty();
-
-    // Provided allocations must fully allocate the iota_supply and there
-    // should be none left at this point.
-    iota_supply.destroy_zero();
 }
 
diff --git a/crates/iota-framework/docs/iota-system/iota_system.mdx b/crates/iota-framework/docs/iota-system/iota_system.mdx index 3a2d9ea8770..aa20a3cefd2 100644 --- a/crates/iota-framework/docs/iota-system/iota_system.mdx +++ b/crates/iota-framework/docs/iota-system/iota_system.mdx @@ -106,6 +106,7 @@ the IotaSystemStateInner version, or vice versa. - [Function `load_system_state_mut`](#0x3_iota_system_load_system_state_mut) - [Function `load_inner_maybe_upgrade`](#0x3_iota_system_load_inner_maybe_upgrade) - [Function `load_system_timelock_cap`](#0x3_iota_system_load_system_timelock_cap) +- [Function `get_total_iota_supply`](#0x3_iota_system_get_total_iota_supply)

@@ -208,7 +209,7 @@ This function will be called only once in genesis.
 
 
 

-public(friend) fun create(id: object::UID, validators: vector<validator::Validator>, storage_fund: balance::Balance<iota::IOTA>, protocol_version: u64, epoch_start_timestamp_ms: u64, parameters: iota_system_state_inner::SystemParameters, stake_subsidy: stake_subsidy::StakeSubsidy, system_timelock_cap: timelock::SystemTimelockCap, ctx: &mut tx_context::TxContext)
+public(friend) fun create(id: object::UID, iota_treasury_cap: iota::IotaTreasuryCap, validators: vector<validator::Validator>, storage_fund: balance::Balance<iota::IOTA>, protocol_version: u64, epoch_start_timestamp_ms: u64, parameters: iota_system_state_inner::SystemParameters, stake_subsidy: stake_subsidy::StakeSubsidy, system_timelock_cap: timelock::SystemTimelockCap, ctx: &mut tx_context::TxContext)
 
@@ -220,6 +221,7 @@ This function will be called only once in genesis.

 public(package) fun create(
     id: UID,
+    iota_treasury_cap: IotaTreasuryCap,
     validators: vector<Validator>,
     storage_fund: Balance<IOTA>,
     protocol_version: u64,
@@ -230,6 +232,7 @@ This function will be called only once in genesis.
     ctx: &mut TxContext,
 ) \{
     let system_state = iota_system_state_inner::create(
+        iota_treasury_cap,
         validators,
         storage_fund,
         protocol_version,
@@ -1481,15 +1484,18 @@ Getter returning addresses of the currently active validators.
 
 This function should be called at the end of an epoch, and advances the system to the next epoch.
 It does the following things:
-1. Add storage charge to the storage fund.
+1. Add storage reward to the storage fund.
 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's
 gas coins.
-3. Distribute computation charge to validator stake.
-4. Update all validators.
+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.
+5. Burn any leftover rewards.
+6. Update all validators.
 
 
 

-fun advance_epoch(storage_reward: balance::Balance<iota::IOTA>, computation_reward: balance::Balance<iota::IOTA>, wrapper: &mut iota_system::IotaSystemState, new_epoch: u64, next_protocol_version: u64, storage_rebate: u64, non_refundable_storage_fee: u64, storage_fund_reinvest_rate: u64, reward_slashing_rate: u64, epoch_start_timestamp_ms: u64, ctx: &mut tx_context::TxContext): balance::Balance<iota::IOTA>
+fun advance_epoch(validator_target_reward: u64, storage_reward: balance::Balance<iota::IOTA>, computation_reward: balance::Balance<iota::IOTA>, wrapper: &mut iota_system::IotaSystemState, new_epoch: u64, next_protocol_version: u64, storage_rebate: u64, non_refundable_storage_fee: u64, reward_slashing_rate: u64, epoch_start_timestamp_ms: u64, ctx: &mut tx_context::TxContext): balance::Balance<iota::IOTA>
 
@@ -1500,6 +1506,7 @@ gas coins.

 fun advance_epoch(
+    validator_target_reward: u64,
     storage_reward: Balance<IOTA>,
     computation_reward: Balance<IOTA>,
     wrapper: &mut IotaSystemState,
@@ -1507,8 +1514,6 @@ gas coins.
     next_protocol_version: u64,
     storage_rebate: u64,
     non_refundable_storage_fee: u64,
-    storage_fund_reinvest_rate: u64, // share of storage fund's rewards that's reinvested
-                                     // into storage fund, in basis point.
     reward_slashing_rate: u64, // how much rewards are slashed to punish a validator, in bps.
     epoch_start_timestamp_ms: u64, // Timestamp of the epoch start
     ctx: &mut TxContext,
@@ -1519,11 +1524,11 @@ gas coins.
     let storage_rebate = self.advance_epoch(
         new_epoch,
         next_protocol_version,
+        validator_target_reward,
         storage_reward,
         computation_reward,
         storage_rebate,
         non_refundable_storage_fee,
-        storage_fund_reinvest_rate,
         reward_slashing_rate,
         epoch_start_timestamp_ms,
         ctx,
@@ -1654,4 +1659,32 @@ gas coins.
 
 
 
+
+
+
+
+## Function `get_total_iota_supply`
+
+Returns the total iota supply.
+
+
+

+public fun get_total_iota_supply(wrapper: &mut iota_system::IotaSystemState): u64
+
+ + + +
+Implementation + + +

+public fun get_total_iota_supply(wrapper: &mut IotaSystemState): u64 \{
+    let self = load_system_state(wrapper);
+    self.get_total_iota_supply()
+}
+
+ + +
diff --git a/crates/iota-framework/docs/iota-system/iota_system_state_inner.mdx b/crates/iota-framework/docs/iota-system/iota_system_state_inner.mdx index b4adc5b3d69..350bd75391c 100644 --- a/crates/iota-framework/docs/iota-system/iota_system_state_inner.mdx +++ b/crates/iota-framework/docs/iota-system/iota_system_state_inner.mdx @@ -50,6 +50,7 @@ import Link from '@docusaurus/Link'; - [Function `update_validator_next_epoch_network_pubkey`](#0x3_iota_system_state_inner_update_validator_next_epoch_network_pubkey) - [Function `update_candidate_validator_network_pubkey`](#0x3_iota_system_state_inner_update_candidate_validator_network_pubkey) - [Function `advance_epoch`](#0x3_iota_system_state_inner_advance_epoch) +- [Function `match_computation_reward_to_target_reward`](#0x3_iota_system_state_inner_match_computation_reward_to_target_reward) - [Function `epoch`](#0x3_iota_system_state_inner_epoch) - [Function `protocol_version`](#0x3_iota_system_state_inner_protocol_version) - [Function `system_state_version`](#0x3_iota_system_state_inner_system_state_version) @@ -58,6 +59,7 @@ import Link from '@docusaurus/Link'; - [Function `validator_stake_amount`](#0x3_iota_system_state_inner_validator_stake_amount) - [Function `validator_staking_pool_id`](#0x3_iota_system_state_inner_validator_staking_pool_id) - [Function `validator_staking_pool_mappings`](#0x3_iota_system_state_inner_validator_staking_pool_mappings) +- [Function `get_total_iota_supply`](#0x3_iota_system_state_inner_get_total_iota_supply) - [Function `get_reporters_of`](#0x3_iota_system_state_inner_get_reporters_of) - [Function `get_storage_fund_total_balance`](#0x3_iota_system_state_inner_get_storage_fund_total_balance) - [Function `get_storage_fund_object_rebates`](#0x3_iota_system_state_inner_get_storage_fund_object_rebates) @@ -316,6 +318,13 @@ system_state_version: u64
+iota_treasury_cap: iota::IotaTreasuryCap +
+
+ The IOTA's TreasuryCap. +
+
+ validators: validator_set::ValidatorSet
@@ -466,6 +475,13 @@ system_state_version: u64
+iota_treasury_cap: iota::IotaTreasuryCap +
+
+ The IOTA's TreasuryCap. +
+
+ validators: validator_set::ValidatorSet
@@ -835,7 +851,7 @@ This function will be called only once in genesis.

-public(friend) fun create(validators: vector<validator::Validator>, initial_storage_fund: balance::Balance<iota::IOTA>, protocol_version: u64, epoch_start_timestamp_ms: u64, parameters: iota_system_state_inner::SystemParameters, stake_subsidy: stake_subsidy::StakeSubsidy, ctx: &mut tx_context::TxContext): iota_system_state_inner::IotaSystemStateInner
+public(friend) fun create(iota_treasury_cap: iota::IotaTreasuryCap, validators: vector<validator::Validator>, initial_storage_fund: balance::Balance<iota::IOTA>, protocol_version: u64, epoch_start_timestamp_ms: u64, parameters: iota_system_state_inner::SystemParameters, stake_subsidy: stake_subsidy::StakeSubsidy, ctx: &mut tx_context::TxContext): iota_system_state_inner::IotaSystemStateInner
 
@@ -846,6 +862,7 @@ This function will be called only once in genesis.

 public(package) fun create(
+    iota_treasury_cap: IotaTreasuryCap,
     validators: vector<Validator>,
     initial_storage_fund: Balance<IOTA>,
     protocol_version: u64,
@@ -861,6 +878,7 @@ This function will be called only once in genesis.
         epoch: 0,
         protocol_version,
         system_state_version: genesis_system_state_version(),
+        iota_treasury_cap,
         validators,
         storage_fund: storage_fund::new(initial_storage_fund),
         parameters,
@@ -951,6 +969,7 @@ This function will be called only once in genesis.
         epoch,
         protocol_version,
         system_state_version: _,
+        iota_treasury_cap,
         validators,
         storage_fund,
         parameters,
@@ -979,6 +998,7 @@ This function will be called only once in genesis.
         epoch,
         protocol_version,
         system_state_version: 2,
+        iota_treasury_cap,
         validators,
         storage_fund,
         parameters: SystemParametersV2 \{
@@ -2230,15 +2250,18 @@ Update candidate validator's public key of network key.
 
 This function should be called at the end of an epoch, and advances the system to the next epoch.
 It does the following things:
-1. Add storage charge to the storage fund.
+1. Add storage reward to the storage fund.
 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's
 gas coins.
-3. Distribute computation charge to validator stake.
-4. Update all validators.
+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.
+5. Burn any leftover rewards.
+6. Update all validators.
 
 
 

-public(friend) fun advance_epoch(self: &mut iota_system_state_inner::IotaSystemStateInnerV2, new_epoch: u64, next_protocol_version: u64, storage_reward: balance::Balance<iota::IOTA>, computation_reward: balance::Balance<iota::IOTA>, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64, storage_fund_reinvest_rate: u64, reward_slashing_rate: u64, epoch_start_timestamp_ms: u64, ctx: &mut tx_context::TxContext): balance::Balance<iota::IOTA>
+public(friend) fun advance_epoch(self: &mut iota_system_state_inner::IotaSystemStateInnerV2, new_epoch: u64, next_protocol_version: u64, validator_target_reward: u64, storage_reward: balance::Balance<iota::IOTA>, computation_reward: balance::Balance<iota::IOTA>, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64, reward_slashing_rate: u64, epoch_start_timestamp_ms: u64, ctx: &mut tx_context::TxContext): balance::Balance<iota::IOTA>
 
@@ -2252,12 +2275,11 @@ gas coins. self: &mut IotaSystemStateInnerV2, new_epoch: u64, next_protocol_version: u64, + validator_target_reward: u64, mut storage_reward: Balance<IOTA>, mut computation_reward: Balance<IOTA>, mut storage_rebate_amount: u64, mut non_refundable_storage_fee_amount: u64, - storage_fund_reinvest_rate: u64, // share of storage fund's rewards that's reinvested - // into storage fund, in basis point. reward_slashing_rate: u64, // how much rewards are slashed to punish a validator, in bps. epoch_start_timestamp_ms: u64, // Timestamp of the epoch start ctx: &mut TxContext, @@ -2267,16 +2289,7 @@ gas coins. let bps_denominator_u64 = BASIS_POINT_DENOMINATOR as u64; // Rates can't be higher than 100%. - assert!( - storage_fund_reinvest_rate <= bps_denominator_u64 - && reward_slashing_rate <= bps_denominator_u64, - EBpsTooLarge, - ); - - // TODO: remove this in later upgrade. - if (self.parameters.stake_subsidy_start_epoch > 0) \{ - self.parameters.stake_subsidy_start_epoch = 20; - }; + assert!(reward_slashing_rate <= bps_denominator_u64, EBpsTooLarge); // Accumulate the gas summary during safe_mode before processing any rewards: let safe_mode_storage_rewards = self.safe_mode_storage_rewards.withdraw_all(); @@ -2288,10 +2301,6 @@ gas coins. 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 total_validators_stake = self.validators.total_stake(); - let storage_fund_balance = self.storage_fund.total_balance(); - let total_stake = storage_fund_balance + total_validators_stake; - let storage_charge = storage_reward.value(); let computation_charge = computation_reward.value(); @@ -2307,30 +2316,26 @@ gas coins. balance::zero() }; + // The stake subsidy fund is disabled through parameter choices in GenesisCeremonyParameters, + // so it is always a zero balance now. It will be fully removed in a later step. let stake_subsidy_amount = stake_subsidy.value(); computation_reward.join(stake_subsidy); - let total_stake_u128 = total_stake as u128; - let computation_charge_u128 = computation_charge as u128; - - let storage_fund_reward_amount = storage_fund_balance as u128 * computation_charge_u128 / total_stake_u128; - let mut storage_fund_reward = computation_reward.split(storage_fund_reward_amount as u64); - let storage_fund_reinvestment_amount = - storage_fund_reward_amount * (storage_fund_reinvest_rate as u128) / BASIS_POINT_DENOMINATOR; - let storage_fund_reinvestment = storage_fund_reward.split( - storage_fund_reinvestment_amount as u64, + let mut total_validator_rewards = match_computation_reward_to_target_reward( + validator_target_reward, + computation_reward, + &mut self.iota_treasury_cap, + ctx ); self.epoch = self.epoch + 1; // Sanity check to make sure we are advancing to the right epoch. assert!(new_epoch == self.epoch, EAdvancedToWrongEpoch); - let computation_reward_amount_before_distribution = computation_reward.value(); - let storage_fund_reward_amount_before_distribution = storage_fund_reward.value(); + let total_validator_rewards_amount_before_distribution = total_validator_rewards.value(); self.validators.advance_epoch( - &mut computation_reward, - &mut storage_fund_reward, + &mut total_validator_rewards, &mut self.validator_report_records, reward_slashing_rate, self.parameters.validator_low_stake_threshold, @@ -2341,44 +2346,42 @@ gas coins. let new_total_stake = self.validators.total_stake(); - let computation_reward_amount_after_distribution = computation_reward.value(); - let storage_fund_reward_amount_after_distribution = storage_fund_reward.value(); - let computation_reward_distributed = computation_reward_amount_before_distribution - computation_reward_amount_after_distribution; - let storage_fund_reward_distributed = storage_fund_reward_amount_before_distribution - storage_fund_reward_amount_after_distribution; + let remaining_validator_rewards_amount_after_distribution = total_validator_rewards.value(); + let total_validator_rewards_distributed = total_validator_rewards_amount_before_distribution - remaining_validator_rewards_amount_after_distribution; self.protocol_version = next_protocol_version; // Derive the reference gas price for the new epoch self.reference_gas_price = self.validators.derive_reference_gas_price(); // Because of precision issues with integer divisions, we expect that there will be some - // remaining balance in `storage_fund_reward` and `computation_reward`. - // All of these go to the storage fund. - let mut leftover_staking_rewards = storage_fund_reward; - leftover_staking_rewards.join(computation_reward); + // remaining balance in `total_validator_rewards`. + let leftover_staking_rewards = total_validator_rewards; let leftover_storage_fund_inflow = leftover_staking_rewards.value(); + // Burning leftover rewards + self.iota_treasury_cap.burn_balance(leftover_staking_rewards, ctx); + let refunded_storage_rebate = self.storage_fund.advance_epoch( storage_reward, - storage_fund_reinvestment, - leftover_staking_rewards, storage_rebate_amount, non_refundable_storage_fee_amount, ); event::emit( + //TODO: Add additional information (e.g., how much was burned, how much was leftover, etc.) SystemEpochInfoEvent \{ epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, total_stake: new_total_stake, storage_charge, - storage_fund_reinvestment: storage_fund_reinvestment_amount as u64, + storage_fund_reinvestment: 0, storage_rebate: storage_rebate_amount, storage_fund_balance: self.storage_fund.total_balance(), stake_subsidy_amount, total_gas_fees: computation_charge, - total_stake_rewards_distributed: computation_reward_distributed + storage_fund_reward_distributed, + total_stake_rewards_distributed: total_validator_rewards_distributed, leftover_storage_fund_inflow, } ); @@ -2396,6 +2399,51 @@ gas coins. + + + + +## Function `match_computation_reward_to_target_reward` + +Mint or burn IOTA tokens depending on the given target reward per validator +and the amount of computation fees burned in this epoch. + + +

+fun match_computation_reward_to_target_reward(validator_target_reward: u64, computation_reward: balance::Balance<iota::IOTA>, iota_treasury_cap: &mut iota::IotaTreasuryCap, ctx: &tx_context::TxContext): balance::Balance<iota::IOTA>
+
+ + + +
+Implementation + + +

+fun match_computation_reward_to_target_reward(
+    validator_target_reward: u64,
+    mut computation_reward: Balance<IOTA>,
+    iota_treasury_cap: &mut iota::iota::IotaTreasuryCap,
+    ctx: &TxContext,
+): Balance<IOTA> \{
+    if (computation_reward.value() < validator_target_reward) \{
+        let tokens_to_mint = validator_target_reward - computation_reward.value();
+        let new_tokens = iota_treasury_cap.mint_balance(tokens_to_mint, ctx);
+        computation_reward.join(new_tokens);
+        computation_reward
+    } else if (computation_reward.value() > validator_target_reward) \{
+        let tokens_to_burn = computation_reward.value() - validator_target_reward;
+        let rewards_to_burn = computation_reward.split(tokens_to_burn);
+        iota_treasury_cap.burn_balance(rewards_to_burn, ctx);
+        computation_reward
+    } else \{
+        computation_reward
+    }
+}
+
+ + +
@@ -2619,6 +2667,33 @@ Returns reference to the staking pool mappings that map pool ids to active valid + + + + +## Function `get_total_iota_supply` + +Returns the total iota supply. + + +

+public(friend) fun get_total_iota_supply(self: &iota_system_state_inner::IotaSystemStateInnerV2): u64
+
+ + + +
+Implementation + + +

+public(package) fun get_total_iota_supply(self: &IotaSystemStateInnerV2): u64 \{
+    self.iota_treasury_cap.total_supply()
+}
+
+ + +
diff --git a/crates/iota-framework/docs/iota-system/storage_fund.mdx b/crates/iota-framework/docs/iota-system/storage_fund.mdx index 52aa571b366..b4687209959 100644 --- a/crates/iota-framework/docs/iota-system/storage_fund.mdx +++ b/crates/iota-framework/docs/iota-system/storage_fund.mdx @@ -110,7 +110,7 @@ Called by

-public(friend) fun advance_epoch(self: &mut storage_fund::StorageFund, storage_charges: balance::Balance<iota::IOTA>, storage_fund_reinvestment: balance::Balance<iota::IOTA>, leftover_staking_rewards: balance::Balance<iota::IOTA>, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64): balance::Balance<iota::IOTA>
+public(friend) fun advance_epoch(self: &mut storage_fund::StorageFund, storage_charges: balance::Balance<iota::IOTA>, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64): balance::Balance<iota::IOTA>
 
@@ -123,15 +123,9 @@ Called by public(package) fun advance_epoch( self: &mut StorageFund, storage_charges: Balance<IOTA>, - storage_fund_reinvestment: Balance<IOTA>, - leftover_staking_rewards: Balance<IOTA>, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64, ) : Balance<IOTA> \{ - // Both the reinvestment and leftover rewards are not to be refunded so they go to the non-refundable balance. - self.non_refundable_balance.join(storage_fund_reinvestment); - self.non_refundable_balance.join(leftover_staking_rewards); - // The storage charges for the epoch come from the storage rebate of the new objects created // and the new storage rebates of the objects modified during the epoch so we put the charges // into `total_object_storage_rebates`. diff --git a/crates/iota-framework/docs/iota-system/validator_set.mdx b/crates/iota-framework/docs/iota-system/validator_set.mdx index 266651efb9c..661f19a9d41 100644 --- a/crates/iota-framework/docs/iota-system/validator_set.mdx +++ b/crates/iota-framework/docs/iota-system/validator_set.mdx @@ -256,13 +256,6 @@ pool_staking_reward: u64
-
-
- -storage_fund_staking_reward: u64 -
-
-
@@ -356,13 +349,6 @@ pool_staking_reward: u64
-
-
- -storage_fund_staking_reward: u64 -
-
-
@@ -1095,7 +1081,7 @@ adjust_stake).

-public(friend) fun advance_epoch(self: &mut validator_set::ValidatorSet, computation_reward: &mut balance::Balance<iota::IOTA>, storage_fund_reward: &mut balance::Balance<iota::IOTA>, validator_report_records: &mut vec_map::VecMap<address, vec_set::VecSet<address>>, reward_slashing_rate: u64, low_stake_threshold: u64, very_low_stake_threshold: u64, low_stake_grace_period: u64, ctx: &mut tx_context::TxContext)
+public(friend) fun advance_epoch(self: &mut validator_set::ValidatorSet, total_validator_rewards: &mut balance::Balance<iota::IOTA>, validator_report_records: &mut vec_map::VecMap<address, vec_set::VecSet<address>>, reward_slashing_rate: u64, low_stake_threshold: u64, very_low_stake_threshold: u64, low_stake_grace_period: u64, ctx: &mut tx_context::TxContext)
 
@@ -1107,8 +1093,7 @@ adjust_stake
).

 public(package) fun advance_epoch(
     self: &mut ValidatorSet,
-    computation_reward: &mut Balance<IOTA>,
-    storage_fund_reward: &mut Balance<IOTA>,
+    total_validator_rewards: &mut Balance<IOTA>,
     validator_report_records: &mut VecMap<address, VecSet<address>>,
     reward_slashing_rate: u64,
     low_stake_threshold: u64,
@@ -1120,11 +1105,10 @@ adjust_stake).
     let total_voting_power = voting_power::total_voting_power();
 
     // Compute the reward distribution without taking into account the tallying rule slashing.
-    let (unadjusted_staking_reward_amounts, unadjusted_storage_fund_reward_amounts) = compute_unadjusted_reward_distribution(
+    let unadjusted_staking_reward_amounts = compute_unadjusted_reward_distribution(
         &self.active_validators,
         total_voting_power,
-        computation_reward.value(),
-        storage_fund_reward.value(),
+        total_validator_rewards.value(),
     );
 
     // Use the tallying rule report records for the epoch to compute validators that will be
@@ -1135,30 +1119,24 @@ adjust_stake).
 
     // Compute the reward adjustments of slashed validators, to be taken into
     // account in adjusted reward computation.
-    let (total_staking_reward_adjustment, individual_staking_reward_adjustments,
-         total_storage_fund_reward_adjustment, individual_storage_fund_reward_adjustments
-        ) =
+    let (total_staking_reward_adjustment, individual_staking_reward_adjustments) =
         compute_reward_adjustments(
             get_validator_indices(&self.active_validators, &slashed_validators),
             reward_slashing_rate,
             &unadjusted_staking_reward_amounts,
-            &unadjusted_storage_fund_reward_amounts,
         );
 
     // Compute the adjusted amounts of stake each validator should get given the tallying rule
     // reward adjustments we computed before.
     // `compute_adjusted_reward_distribution` must be called before `distribute_reward` and `adjust_stake_and_gas_price` to
     // make sure we are using the current epoch's stake information to compute reward distribution.
-    let (adjusted_staking_reward_amounts, adjusted_storage_fund_reward_amounts) = compute_adjusted_reward_distribution(
+    let adjusted_staking_reward_amounts = compute_adjusted_reward_distribution(
         &self.active_validators,
         total_voting_power,
         total_slashed_validator_voting_power,
         unadjusted_staking_reward_amounts,
-        unadjusted_storage_fund_reward_amounts,
         total_staking_reward_adjustment,
         individual_staking_reward_adjustments,
-        total_storage_fund_reward_adjustment,
-        individual_storage_fund_reward_adjustments
     );
 
     // Distribute the rewards before adjusting stake so that we immediately start compounding
@@ -1166,9 +1144,7 @@ adjust_stake).
     distribute_reward(
         &mut self.active_validators,
         &adjusted_staking_reward_amounts,
-        &adjusted_storage_fund_reward_amounts,
-        computation_reward,
-        storage_fund_reward,
+        total_validator_rewards,
         ctx
     );
 
@@ -1178,7 +1154,7 @@ adjust_stake).
 
     // Emit events after we have processed all the rewards distribution and pending stakes.
     emit_validator_epoch_events(new_epoch, &self.active_validators, &adjusted_staking_reward_amounts,
-        &adjusted_storage_fund_reward_amounts, validator_report_records, &slashed_validators);
+        validator_report_records, &slashed_validators);
 
     // Note that all their staged next epoch metadata will be effectuated below.
     process_pending_validators(self, new_epoch);
@@ -2579,12 +2555,11 @@ Process the pending stake changes for each validator.
 
 ## Function `compute_reward_adjustments`
 
-Compute both the individual reward adjustments and total reward adjustment for staking rewards
-as well as storage fund rewards.
+Compute both the individual reward adjustments and total reward adjustment for staking rewards.
 
 
 

-fun compute_reward_adjustments(slashed_validator_indices: vector<u64>, reward_slashing_rate: u64, unadjusted_staking_reward_amounts: &vector<u64>, unadjusted_storage_fund_reward_amounts: &vector<u64>): (u64, vec_map::VecMap<u64, u64>, u64, vec_map::VecMap<u64, u64>)
+fun compute_reward_adjustments(slashed_validator_indices: vector<u64>, reward_slashing_rate: u64, unadjusted_staking_reward_amounts: &vector<u64>): (u64, vec_map::VecMap<u64, u64>)
 
@@ -2598,17 +2573,12 @@ as well as storage fund rewards. mut slashed_validator_indices: vector<u64>, reward_slashing_rate: u64, unadjusted_staking_reward_amounts: &vector<u64>, - unadjusted_storage_fund_reward_amounts: &vector<u64>, ): ( u64, // sum of staking reward adjustments VecMap<u64, u64>, // mapping of individual validator's staking reward adjustment from index -> amount - u64, // sum of storage fund reward adjustments - VecMap<u64, u64>, // mapping of individual validator's storage fund reward adjustment from index -> amount ) \{ let mut total_staking_reward_adjustment = 0; let mut individual_staking_reward_adjustments = vec_map::empty(); - let mut total_storage_fund_reward_adjustment = 0; - let mut individual_storage_fund_reward_adjustments = vec_map::empty(); while (!slashed_validator_indices.is_empty()) \{ let validator_index = slashed_validator_indices.pop_back(); @@ -2622,20 +2592,9 @@ as well as storage fund rewards. // Insert into individual mapping and record into the total adjustment sum. individual_staking_reward_adjustments.insert(validator_index, staking_reward_adjustment_u128 as u64); total_staking_reward_adjustment = total_staking_reward_adjustment + (staking_reward_adjustment_u128 as u64); - - // Do the same thing for storage fund rewards. - let unadjusted_storage_fund_reward = unadjusted_storage_fund_reward_amounts[validator_index]; - let storage_fund_reward_adjustment_u128 = - unadjusted_storage_fund_reward as u128 * (reward_slashing_rate as u128) - / BASIS_POINT_DENOMINATOR; - individual_storage_fund_reward_adjustments.insert(validator_index, storage_fund_reward_adjustment_u128 as u64); - total_storage_fund_reward_adjustment = total_storage_fund_reward_adjustment + (storage_fund_reward_adjustment_u128 as u64); }; - ( - total_staking_reward_adjustment, individual_staking_reward_adjustments, - total_storage_fund_reward_adjustment, individual_storage_fund_reward_adjustments - ) + (total_staking_reward_adjustment, individual_staking_reward_adjustments) }
@@ -2695,11 +2654,11 @@ non-performant validators according to the input threshold. Given the current list of active validators, the total stake and total reward, calculate the amount of reward each validator should get, without taking into account the tallying rule results. -Returns the unadjusted amounts of staking reward and storage fund reward for each validator. +Returns the unadjusted amounts of staking reward for each validator.

-fun compute_unadjusted_reward_distribution(validators: &vector<validator::Validator>, total_voting_power: u64, total_staking_reward: u64, total_storage_fund_reward: u64): (vector<u64>, vector<u64>)
+fun compute_unadjusted_reward_distribution(validators: &vector<validator::Validator>, total_voting_power: u64, total_staking_reward: u64): vector<u64>
 
@@ -2713,12 +2672,9 @@ Returns the unadjusted amounts of staking reward and storage fund reward for eac validators: &vector<Validator>, total_voting_power: u64, total_staking_reward: u64, - total_storage_fund_reward: u64, -): (vector<u64>, vector<u64>) \{ +): vector<u64> \{ let mut staking_reward_amounts = vector[]; - let mut storage_fund_reward_amounts = vector[]; let length = validators.length(); - let storage_fund_reward_per_validator = total_storage_fund_reward / length; let mut i = 0; while (i < length) \{ let validator = &validators[i]; @@ -2728,11 +2684,9 @@ Returns the unadjusted amounts of staking reward and storage fund reward for eac let voting_power: u128 = validator.voting_power() as u128; let reward_amount = voting_power * (total_staking_reward as u128) / (total_voting_power as u128); staking_reward_amounts.push_back(reward_amount as u64); - // Storage fund's share of the rewards are equally distributed among validators. - storage_fund_reward_amounts.push_back(storage_fund_reward_per_validator); i = i + 1; }; - (staking_reward_amounts, storage_fund_reward_amounts) + staking_reward_amounts }
@@ -2745,12 +2699,12 @@ Returns the unadjusted amounts of staking reward and storage fund reward for eac ## Function `compute_adjusted_reward_distribution` Use the reward adjustment info to compute the adjusted rewards each validator should get. -Returns the staking rewards each validator gets and the storage fund rewards each validator gets. -The staking rewards are shared with the stakers while the storage fund ones are not. +Returns the staking rewards each validator gets. +The staking rewards are shared with the stakers.

-fun compute_adjusted_reward_distribution(validators: &vector<validator::Validator>, total_voting_power: u64, total_slashed_validator_voting_power: u64, unadjusted_staking_reward_amounts: vector<u64>, unadjusted_storage_fund_reward_amounts: vector<u64>, total_staking_reward_adjustment: u64, individual_staking_reward_adjustments: vec_map::VecMap<u64, u64>, total_storage_fund_reward_adjustment: u64, individual_storage_fund_reward_adjustments: vec_map::VecMap<u64, u64>): (vector<u64>, vector<u64>)
+fun compute_adjusted_reward_distribution(validators: &vector<validator::Validator>, total_voting_power: u64, total_slashed_validator_voting_power: u64, unadjusted_staking_reward_amounts: vector<u64>, total_staking_reward_adjustment: u64, individual_staking_reward_adjustments: vec_map::VecMap<u64, u64>): vector<u64>
 
@@ -2765,18 +2719,13 @@ The staking rewards are shared with the stakers while the storage fund ones are total_voting_power: u64, total_slashed_validator_voting_power: u64, unadjusted_staking_reward_amounts: vector<u64>, - unadjusted_storage_fund_reward_amounts: vector<u64>, total_staking_reward_adjustment: u64, individual_staking_reward_adjustments: VecMap<u64, u64>, - total_storage_fund_reward_adjustment: u64, - individual_storage_fund_reward_adjustments: VecMap<u64, u64>, -): (vector<u64>, vector<u64>) \{ +): vector<u64> \{ let total_unslashed_validator_voting_power = total_voting_power - total_slashed_validator_voting_power; let mut adjusted_staking_reward_amounts = vector[]; - let mut adjusted_storage_fund_reward_amounts = vector[]; let length = validators.length(); - let num_unslashed_validators = length - individual_staking_reward_adjustments.size(); let mut i = 0; while (i < length) \{ @@ -2802,24 +2751,10 @@ The staking rewards are shared with the stakers while the storage fund ones are }; adjusted_staking_reward_amounts.push_back(adjusted_staking_reward_amount); - // Compute adjusted storage fund reward. - let unadjusted_storage_fund_reward_amount = unadjusted_storage_fund_reward_amounts[i]; - let adjusted_storage_fund_reward_amount = - // If the validator is one of the slashed ones, then subtract the adjustment. - if (individual_storage_fund_reward_adjustments.contains(&i)) \{ - let adjustment = individual_storage_fund_reward_adjustments[&i]; - unadjusted_storage_fund_reward_amount - adjustment - } else \{ - // Otherwise the slashed rewards should be equally distributed among the unslashed validators. - let adjustment = total_storage_fund_reward_adjustment / num_unslashed_validators; - unadjusted_storage_fund_reward_amount + adjustment - }; - adjusted_storage_fund_reward_amounts.push_back(adjusted_storage_fund_reward_amount); - i = i + 1; }; - (adjusted_staking_reward_amounts, adjusted_storage_fund_reward_amounts) + adjusted_staking_reward_amounts }
@@ -2834,7 +2769,7 @@ The staking rewards are shared with the stakers while the storage fund ones are

-fun distribute_reward(validators: &mut vector<validator::Validator>, adjusted_staking_reward_amounts: &vector<u64>, adjusted_storage_fund_reward_amounts: &vector<u64>, staking_rewards: &mut balance::Balance<iota::IOTA>, storage_fund_reward: &mut balance::Balance<iota::IOTA>, ctx: &mut tx_context::TxContext)
+fun distribute_reward(validators: &mut vector<validator::Validator>, adjusted_staking_reward_amounts: &vector<u64>, staking_rewards: &mut balance::Balance<iota::IOTA>, ctx: &mut tx_context::TxContext)
 
@@ -2847,9 +2782,7 @@ The staking rewards are shared with the stakers while the storage fund ones are fun distribute_reward( validators: &mut vector<Validator>, adjusted_staking_reward_amounts: &vector<u64>, - adjusted_storage_fund_reward_amounts: &vector<u64>, staking_rewards: &mut Balance<IOTA>, - storage_fund_reward: &mut Balance<IOTA>, ctx: &mut TxContext ) \{ let length = validators.length(); @@ -2863,13 +2796,8 @@ The staking rewards are shared with the stakers while the storage fund ones are // Validator takes a cut of the rewards as commission. let validator_commission_amount = (staking_reward_amount as u128) * (validator.commission_rate() as u128) / BASIS_POINT_DENOMINATOR; - // The validator reward = storage_fund_reward + commission. - let mut validator_reward = staker_reward.split(validator_commission_amount as u64); - - // Add storage fund rewards to the validator's reward. - validator_reward.join( - storage_fund_reward.split(adjusted_storage_fund_reward_amounts[i]) - ); + // The validator reward = commission. + let validator_reward = staker_reward.split(validator_commission_amount as u64); // Add rewards to the validator. Don't try and distribute rewards though if the payout is zero. if (validator_reward.value() > 0) \{ @@ -2900,7 +2828,7 @@ including stakes, rewards, performance, etc.

-fun emit_validator_epoch_events(new_epoch: u64, vs: &vector<validator::Validator>, pool_staking_reward_amounts: &vector<u64>, storage_fund_staking_reward_amounts: &vector<u64>, report_records: &vec_map::VecMap<address, vec_set::VecSet<address>>, slashed_validators: &vector<address>)
+fun emit_validator_epoch_events(new_epoch: u64, vs: &vector<validator::Validator>, pool_staking_reward_amounts: &vector<u64>, report_records: &vec_map::VecMap<address, vec_set::VecSet<address>>, slashed_validators: &vector<address>)
 
@@ -2914,7 +2842,6 @@ including stakes, rewards, performance, etc. new_epoch: u64, vs: &vector<Validator>, pool_staking_reward_amounts: &vector<u64>, - storage_fund_staking_reward_amounts: &vector<u64>, report_records: &VecMap<address, VecSet<address>>, slashed_validators: &vector<address>, ) \{ @@ -2941,7 +2868,6 @@ including stakes, rewards, performance, etc. voting_power: v.voting_power(), commission_rate: v.commission_rate(), pool_staking_reward: pool_staking_reward_amounts[i], - storage_fund_staking_reward: storage_fund_staking_reward_amounts[i], pool_token_exchange_rate: v.pool_token_exchange_rate_at_epoch(new_epoch), tallying_rule_reporters, tallying_rule_global_score, diff --git a/crates/iota-framework/packages/iota-framework/sources/balance.move b/crates/iota-framework/packages/iota-framework/sources/balance.move index 456e4eef3f0..b40939c956e 100644 --- a/crates/iota-framework/packages/iota-framework/sources/balance.move +++ b/crates/iota-framework/packages/iota-framework/sources/balance.move @@ -20,6 +20,8 @@ module iota::balance { const ENotEnough: u64 = 2; /// Sender is not @0x0 the system address. const ENotSystemAddress: u64 = 3; + /// Epoch is not 0 (the genesis epoch). + const ENotGenesisEpoch: u64 = 4; /// A Supply of T. Used for minting and burning. /// Wrapped into a `TreasuryCap` in the `Coin` module. @@ -112,6 +114,17 @@ module iota::balance { let Balance { value: _ } = self; } + #[allow(unused_function)] + /// CAUTION: this function destroys a `Balance` without decreasing the supply. + /// It should only be called by the genesis txn to destroy parts of the IOTA supply + /// which was created during the migration and for no other reason. + fun destroy_genesis_supply(self: Balance, ctx: &TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(ctx.epoch() == 0, ENotGenesisEpoch); + + let Balance { value: _ } = self; + } + /// Destroy a `Supply` preventing any further minting and burning. public(package) fun destroy_supply(self: Supply): u64 { let Supply { value } = self; diff --git a/crates/iota-framework/packages/iota-framework/sources/iota.move b/crates/iota-framework/packages/iota-framework/sources/iota.move index 51f5e238991..d05e0e7e02a 100644 --- a/crates/iota-framework/packages/iota-framework/sources/iota.move +++ b/crates/iota-framework/packages/iota-framework/sources/iota.move @@ -6,28 +6,26 @@ /// It has 9 decimals, and the smallest unit (10^-9) is called "nano". module iota::iota { use iota::balance::Balance; - use iota::coin; + use iota::coin::{Self, Coin, TreasuryCap}; use iota::url; const EAlreadyMinted: u64 = 0; /// Sender is not @0x0 the system address. const ENotSystemAddress: u64 = 1; - #[allow(unused_const)] - /// The amount of Nanos per IOTA token based on the fact that nano is - /// 10^-9 of a IOTA token - const NANO_PER_IOTA: u64 = 1_000_000_000; - - /// The total supply of IOTA denominated in Nano (4.6 Billion * 10^9) - const TOTAL_SUPPLY_NANO: u64 = 4_600_000_000_000_000_000; - /// Name of the coin public struct IOTA has drop {} + /// The IOTA token treasury capability. + /// Protects the token from unauthorized changes. + public struct IotaTreasuryCap has store { + inner: TreasuryCap, + } + #[allow(unused_function)] - /// Register the `IOTA` Coin to acquire its `Supply`. + /// Register the `IOTA` Coin to acquire `IotaTreasuryCap`. /// This should be called only once during genesis creation. - fun new(ctx: &mut TxContext): Balance { + fun new(ctx: &mut TxContext): IotaTreasuryCap { assert!(ctx.sender() == @0x0, ENotSystemAddress); assert!(ctx.epoch() == 0, EAlreadyMinted); @@ -40,14 +38,56 @@ module iota::iota { option::some(url::new_unsafe_from_bytes(b"https://iota.org/logo.png")), ctx ); + transfer::public_freeze_object(metadata); - let mut supply = treasury.treasury_into_supply(); - let total_iota = supply.increase_supply(TOTAL_SUPPLY_NANO); - supply.destroy_supply(); - total_iota + + IotaTreasuryCap { + inner: treasury, + } } public entry fun transfer(c: coin::Coin, recipient: address) { transfer::public_transfer(c, recipient) } + + /// Create an IOTA coin worth `value` and increase the total supply in `cap` accordingly. + public fun mint(cap: &mut IotaTreasuryCap, value: u64, ctx: &mut TxContext): Coin { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + cap.inner.mint(value, ctx) + } + + /// Mint some amount of IOTA as a `Balance` and increase the total supply in `cap` accordingly. + /// Aborts if `value` + `cap.inner.total_supply` >= U64_MAX + public fun mint_balance(cap: &mut IotaTreasuryCap, value: u64, ctx: &TxContext): Balance { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + cap.inner.mint_balance(value) + } + + /// Destroy the IOTA coin `c` and decrease the total supply in `cap` accordingly. + public fun burn(cap: &mut IotaTreasuryCap, c: Coin, ctx: &TxContext): u64 { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + cap.inner.burn(c) + } + + /// Destroy the IOTA balance `b` and decrease the total supply in `cap` accordingly. + public fun burn_balance(cap: &mut IotaTreasuryCap, b: Balance, ctx: &TxContext): u64 { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + cap.inner.supply_mut().decrease_supply(b) + } + + /// Return the total number of IOTA's in circulation. + public fun total_supply(cap: &IotaTreasuryCap): u64 { + cap.inner.total_supply() + } + + #[test_only] + public fun create_for_testing(ctx: &mut TxContext): IotaTreasuryCap { + // The `new` function must be called here to be sure that the test function + // contains all the important checks. + new(ctx) + } } diff --git a/crates/iota-framework/packages/iota-framework/tests/iota_tests.move b/crates/iota-framework/packages/iota-framework/tests/iota_tests.move new file mode 100644 index 00000000000..3aff62dbb99 --- /dev/null +++ b/crates/iota-framework/packages/iota-framework/tests/iota_tests.move @@ -0,0 +1,166 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module iota::iota_tests { + + use iota::iota; + use iota::test_scenario; + use iota::test_utils::{Self, assert_eq}; + + #[test] + #[expected_failure(abort_code = iota::ENotSystemAddress)] + fun test_create_iota_from_wrong_address() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0xA); + + // Create an IOTA treasury capability. + let iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = iota::EAlreadyMinted)] + fun test_create_iota_during_wrong_epoch() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0x0); + + // Advance the scenario to a new epoch. + scenario.next_epoch(@0x0); + + // Create an IOTA treasury capability. + let iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + fun test_mint_burn_flow() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0x0); + let ctx = scenario.ctx(); + + // Create an IOTA treasury capability. + let mut iota_treasury_cap = iota::create_for_testing(ctx); + + // Mint some IOTA. + let iota_coin = iota_treasury_cap.mint(100, ctx); + + assert_eq(iota_treasury_cap.total_supply(), 100); + + let iota_balance = iota_treasury_cap.mint_balance(200, ctx); + + assert_eq(iota_treasury_cap.total_supply(), 300); + + // Burn some IOTA. + iota_treasury_cap.burn(iota_coin, ctx); + + assert_eq(iota_treasury_cap.total_supply(), 200); + + iota_treasury_cap.burn_balance(iota_balance, ctx); + + assert_eq(iota_treasury_cap.total_supply(), 0); + + // Cleanup. + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = iota::ENotSystemAddress)] + fun test_mint_coins_by_wrong_address() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0xA); + + // Create an IOTA treasury capability. + let mut iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Mint some IOTA coins. + let iota = iota_treasury_cap.mint(100, scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota); + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = iota::ENotSystemAddress)] + fun test_burn_coins_by_wrong_address() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0x0); + + // Create an IOTA treasury capability. + let mut iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Mint some IOTA coins. + let iota = iota_treasury_cap.mint(100, scenario.ctx()); + + assert_eq(iota_treasury_cap.total_supply(), 100); + + // Switch to a wrong address. + scenario.next_tx(@0xA); + + // Burn some IOTA coins. + iota_treasury_cap.burn(iota, scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = iota::ENotSystemAddress)] + fun test_mint_balance_by_wrong_address() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0xA); + + // Create an IOTA treasury capability. + let mut iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Mint some IOTA balance. + let iota = iota_treasury_cap.mint_balance(100, scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota); + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = iota::ENotSystemAddress)] + fun test_burn_balance_by_custom_address() { + // Set up a test environment. + let mut scenario = test_scenario::begin(@0x0); + + // Create an IOTA treasury capability. + let mut iota_treasury_cap = iota::create_for_testing(scenario.ctx()); + + // Mint some IOTA balance. + let iota = iota_treasury_cap.mint_balance(100, scenario.ctx()); + + assert_eq(iota_treasury_cap.total_supply(), 100); + + // Switch to a wrong address. + scenario.next_tx(@0xA); + + // Burn some IOTA balance. + iota_treasury_cap.burn_balance(iota, scenario.ctx()); + + // Cleanup. + test_utils::destroy(iota_treasury_cap); + + scenario.end(); + } +} diff --git a/crates/iota-framework/packages/iota-system/sources/genesis.move b/crates/iota-framework/packages/iota-system/sources/genesis.move index 35479e396e0..fd0260adc15 100644 --- a/crates/iota-framework/packages/iota-system/sources/genesis.move +++ b/crates/iota-framework/packages/iota-system/sources/genesis.move @@ -6,8 +6,8 @@ module iota_system::genesis { use std::string::String; - use iota::balance::{Self, Balance}; - use iota::iota::{Self, IOTA}; + use iota::balance; + use iota::iota::{Self, IotaTreasuryCap}; use iota::timelock::SystemTimelockCap; use iota_system::iota_system; use iota_system::validator::{Self, Validator}; @@ -59,7 +59,7 @@ module iota_system::genesis { } public struct TokenDistributionSchedule { - stake_subsidy_fund_nanos: u64, + pre_minted_supply: u64, allocations: vector, } @@ -79,6 +79,8 @@ module iota_system::genesis { const ENotCalledAtGenesis: u64 = 0; /// The `create` function was called with duplicate validators. const EDuplicateValidator: u64 = 1; + /// The `create` function was called with wrong pre-minted supply. + const EWrongPreMintedSupply: u64 = 2; #[allow(unused_function)] /// This function will be explicitly called once at genesis. @@ -86,7 +88,7 @@ module iota_system::genesis { /// all the information we need in the system. fun create( iota_system_state_id: UID, - mut iota_supply: Balance, + mut iota_treasury_cap: IotaTreasuryCap, genesis_chain_parameters: GenesisChainParameters, genesis_validators: vector, token_distribution_schedule: TokenDistributionSchedule, @@ -98,11 +100,13 @@ module iota_system::genesis { assert!(ctx.epoch() == 0, ENotCalledAtGenesis); let TokenDistributionSchedule { - stake_subsidy_fund_nanos, + pre_minted_supply, allocations, } = token_distribution_schedule; - let subsidy_fund = iota_supply.split(stake_subsidy_fund_nanos); + assert!(iota_treasury_cap.total_supply() == pre_minted_supply, EWrongPreMintedSupply); + + let subsidy_fund = balance::zero(); let storage_fund = balance::zero(); // Create all the `Validator` structs @@ -160,7 +164,7 @@ module iota_system::genesis { // Allocate tokens and staking operations allocate_tokens( - iota_supply, + &mut iota_treasury_cap, allocations, &mut validators, timelock_genesis_label, @@ -194,6 +198,7 @@ module iota_system::genesis { iota_system::create( iota_system_state_id, + iota_treasury_cap, validators, storage_fund, genesis_chain_parameters.protocol_version, @@ -206,7 +211,7 @@ module iota_system::genesis { } fun allocate_tokens( - mut iota_supply: Balance, + iota_treasury_cap: &mut IotaTreasuryCap, mut allocations: vector, validators: &mut vector, timelock_genesis_label: Option, @@ -221,7 +226,7 @@ module iota_system::genesis { staked_with_timelock_expiration, } = allocations.pop_back(); - let allocation_balance = iota_supply.split(amount_nanos); + let allocation_balance = iota_treasury_cap.mint_balance(amount_nanos, ctx); if (staked_with_validator.is_some()) { let validator_address = staked_with_validator.destroy_some(); @@ -253,10 +258,6 @@ module iota_system::genesis { }; }; allocations.destroy_empty(); - - // Provided allocations must fully allocate the iota_supply and there - // should be none left at this point. - iota_supply.destroy_zero(); } fun activate_validators(validators: &mut vector) { 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 96c26402096..3cf78780446 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system.move @@ -44,7 +44,7 @@ module iota_system::iota_system { use iota::coin::Coin; use iota_system::staking_pool::StakedIota; - use iota::iota::IOTA; + use iota::iota::{IOTA, IotaTreasuryCap}; use iota::table::Table; use iota::timelock::SystemTimelockCap; use iota_system::validator::Validator; @@ -81,6 +81,7 @@ module iota_system::iota_system { /// This function will be called only once in genesis. public(package) fun create( id: UID, + iota_treasury_cap: IotaTreasuryCap, validators: vector, storage_fund: Balance, protocol_version: u64, @@ -91,6 +92,7 @@ module iota_system::iota_system { ctx: &mut TxContext, ) { let system_state = iota_system_state_inner::create( + iota_treasury_cap, validators, storage_fund, protocol_version, @@ -535,12 +537,16 @@ module iota_system::iota_system { #[allow(unused_function)] /// This function should be called at the end of an epoch, and advances the system to the next epoch. /// It does the following things: - /// 1. Add storage charge to the storage fund. + /// 1. Add storage reward to the storage fund. /// 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's /// gas coins. - /// 3. Distribute computation charge to validator stake. - /// 4. Update all validators. + /// 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. + /// 5. Burn any leftover rewards. + /// 6. Update all validators. fun advance_epoch( + validator_target_reward: u64, storage_reward: Balance, computation_reward: Balance, wrapper: &mut IotaSystemState, @@ -548,8 +554,6 @@ module iota_system::iota_system { next_protocol_version: u64, storage_rebate: u64, non_refundable_storage_fee: u64, - storage_fund_reinvest_rate: u64, // share of storage fund's rewards that's reinvested - // into storage fund, in basis point. reward_slashing_rate: u64, // how much rewards are slashed to punish a validator, in bps. epoch_start_timestamp_ms: u64, // Timestamp of the epoch start ctx: &mut TxContext, @@ -560,11 +564,11 @@ module iota_system::iota_system { let storage_rebate = self.advance_epoch( new_epoch, next_protocol_version, + validator_target_reward, storage_reward, computation_reward, storage_rebate, non_refundable_storage_fee, - storage_fund_reinvest_rate, reward_slashing_rate, epoch_start_timestamp_ms, ctx, @@ -708,6 +712,12 @@ module iota_system::iota_system { self.get_stake_subsidy_distribution_counter() } + /// Returns the total iota supply. + public fun get_total_iota_supply(wrapper: &mut IotaSystemState): u64 { + let self = load_system_state(wrapper); + self.get_total_iota_supply() + } + // CAUTION: THIS CODE IS ONLY FOR TESTING AND THIS MACRO MUST NEVER EVER BE REMOVED. Creates a // candidate validator - bypassing the proof of possession check and other metadata validation // in the process. @@ -756,11 +766,11 @@ module iota_system::iota_system { wrapper: &mut IotaSystemState, new_epoch: u64, next_protocol_version: u64, + validator_target_reward: u64, storage_charge: u64, computation_charge: u64, storage_rebate: u64, non_refundable_storage_fee: u64, - storage_fund_reinvest_rate: u64, reward_slashing_rate: u64, epoch_start_timestamp_ms: u64, ctx: &mut TxContext, @@ -768,6 +778,7 @@ module iota_system::iota_system { let storage_reward = balance::create_for_testing(storage_charge); let computation_reward = balance::create_for_testing(computation_charge); let storage_rebate = advance_epoch( + validator_target_reward, storage_reward, computation_reward, wrapper, @@ -775,7 +786,6 @@ module iota_system::iota_system { next_protocol_version, storage_rebate, non_refundable_storage_fee, - storage_fund_reinvest_rate, reward_slashing_rate, epoch_start_timestamp_ms, ctx, 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 f4270501b06..4fefa63d98a 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 @@ -6,7 +6,7 @@ module iota_system::iota_system_state_inner { use iota::balance::{Self, Balance}; use iota::coin::Coin; use iota_system::staking_pool::{stake_activation_epoch, StakedIota}; - use iota::iota::IOTA; + use iota::iota::{IOTA, IotaTreasuryCap}; use iota_system::validator::{Self, Validator}; use iota_system::validator_set::{Self, ValidatorSet}; use iota_system::validator_cap::{UnverifiedValidatorOperationCap, ValidatorOperationCap}; @@ -110,6 +110,8 @@ module iota_system::iota_system_state_inner { /// This is always the same as IotaSystemState.version. Keeping a copy here so that /// we know what version it is by inspecting IotaSystemStateInner as well. system_state_version: u64, + /// The IOTA's TreasuryCap. + iota_treasury_cap: IotaTreasuryCap, /// Contains all information about the validators. validators: ValidatorSet, /// The storage fund. @@ -158,6 +160,8 @@ module iota_system::iota_system_state_inner { /// This is always the same as IotaSystemState.version. Keeping a copy here so that /// we know what version it is by inspecting IotaSystemStateInner as well. system_state_version: u64, + /// The IOTA's TreasuryCap. + iota_treasury_cap: IotaTreasuryCap, /// Contains all information about the validators. validators: ValidatorSet, /// The storage fund. @@ -232,6 +236,7 @@ module iota_system::iota_system_state_inner { /// Create a new IotaSystemState object and make it shared. /// This function will be called only once in genesis. public(package) fun create( + iota_treasury_cap: IotaTreasuryCap, validators: vector, initial_storage_fund: Balance, protocol_version: u64, @@ -247,6 +252,7 @@ module iota_system::iota_system_state_inner { epoch: 0, protocol_version, system_state_version: genesis_system_state_version(), + iota_treasury_cap, validators, storage_fund: storage_fund::new(initial_storage_fund), parameters, @@ -293,6 +299,7 @@ module iota_system::iota_system_state_inner { epoch, protocol_version, system_state_version: _, + iota_treasury_cap, validators, storage_fund, parameters, @@ -321,6 +328,7 @@ module iota_system::iota_system_state_inner { epoch, protocol_version, system_state_version: 2, + iota_treasury_cap, validators, storage_fund, parameters: SystemParametersV2 { @@ -810,21 +818,23 @@ module iota_system::iota_system_state_inner { /// This function should be called at the end of an epoch, and advances the system to the next epoch. /// It does the following things: - /// 1. Add storage charge to the storage fund. + /// 1. Add storage reward to the storage fund. /// 2. Burn the storage rebates from the storage fund. These are already refunded to transaction sender's /// gas coins. - /// 3. Distribute computation charge to validator stake. - /// 4. Update all validators. + /// 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. + /// 5. Burn any leftover rewards. + /// 6. Update all validators. public(package) fun advance_epoch( self: &mut IotaSystemStateInnerV2, new_epoch: u64, next_protocol_version: u64, + validator_target_reward: u64, mut storage_reward: Balance, mut computation_reward: Balance, mut storage_rebate_amount: u64, mut non_refundable_storage_fee_amount: u64, - storage_fund_reinvest_rate: u64, // share of storage fund's rewards that's reinvested - // into storage fund, in basis point. reward_slashing_rate: u64, // how much rewards are slashed to punish a validator, in bps. epoch_start_timestamp_ms: u64, // Timestamp of the epoch start ctx: &mut TxContext, @@ -834,16 +844,7 @@ module iota_system::iota_system_state_inner { let bps_denominator_u64 = BASIS_POINT_DENOMINATOR as u64; // Rates can't be higher than 100%. - assert!( - storage_fund_reinvest_rate <= bps_denominator_u64 - && reward_slashing_rate <= bps_denominator_u64, - EBpsTooLarge, - ); - - // TODO: remove this in later upgrade. - if (self.parameters.stake_subsidy_start_epoch > 0) { - self.parameters.stake_subsidy_start_epoch = 20; - }; + assert!(reward_slashing_rate <= bps_denominator_u64, EBpsTooLarge); // Accumulate the gas summary during safe_mode before processing any rewards: let safe_mode_storage_rewards = self.safe_mode_storage_rewards.withdraw_all(); @@ -855,10 +856,6 @@ module iota_system::iota_system_state_inner { 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 total_validators_stake = self.validators.total_stake(); - let storage_fund_balance = self.storage_fund.total_balance(); - let total_stake = storage_fund_balance + total_validators_stake; - let storage_charge = storage_reward.value(); let computation_charge = computation_reward.value(); @@ -874,30 +871,26 @@ module iota_system::iota_system_state_inner { balance::zero() }; + // The stake subsidy fund is disabled through parameter choices in GenesisCeremonyParameters, + // so it is always a zero balance now. It will be fully removed in a later step. let stake_subsidy_amount = stake_subsidy.value(); computation_reward.join(stake_subsidy); - let total_stake_u128 = total_stake as u128; - let computation_charge_u128 = computation_charge as u128; - - let storage_fund_reward_amount = storage_fund_balance as u128 * computation_charge_u128 / total_stake_u128; - let mut storage_fund_reward = computation_reward.split(storage_fund_reward_amount as u64); - let storage_fund_reinvestment_amount = - storage_fund_reward_amount * (storage_fund_reinvest_rate as u128) / BASIS_POINT_DENOMINATOR; - let storage_fund_reinvestment = storage_fund_reward.split( - storage_fund_reinvestment_amount as u64, + let mut total_validator_rewards = match_computation_reward_to_target_reward( + validator_target_reward, + computation_reward, + &mut self.iota_treasury_cap, + ctx ); self.epoch = self.epoch + 1; // Sanity check to make sure we are advancing to the right epoch. assert!(new_epoch == self.epoch, EAdvancedToWrongEpoch); - let computation_reward_amount_before_distribution = computation_reward.value(); - let storage_fund_reward_amount_before_distribution = storage_fund_reward.value(); + let total_validator_rewards_amount_before_distribution = total_validator_rewards.value(); self.validators.advance_epoch( - &mut computation_reward, - &mut storage_fund_reward, + &mut total_validator_rewards, &mut self.validator_report_records, reward_slashing_rate, self.parameters.validator_low_stake_threshold, @@ -908,44 +901,42 @@ module iota_system::iota_system_state_inner { let new_total_stake = self.validators.total_stake(); - let computation_reward_amount_after_distribution = computation_reward.value(); - let storage_fund_reward_amount_after_distribution = storage_fund_reward.value(); - let computation_reward_distributed = computation_reward_amount_before_distribution - computation_reward_amount_after_distribution; - let storage_fund_reward_distributed = storage_fund_reward_amount_before_distribution - storage_fund_reward_amount_after_distribution; + let remaining_validator_rewards_amount_after_distribution = total_validator_rewards.value(); + let total_validator_rewards_distributed = total_validator_rewards_amount_before_distribution - remaining_validator_rewards_amount_after_distribution; self.protocol_version = next_protocol_version; // Derive the reference gas price for the new epoch self.reference_gas_price = self.validators.derive_reference_gas_price(); // Because of precision issues with integer divisions, we expect that there will be some - // remaining balance in `storage_fund_reward` and `computation_reward`. - // All of these go to the storage fund. - let mut leftover_staking_rewards = storage_fund_reward; - leftover_staking_rewards.join(computation_reward); + // remaining balance in `total_validator_rewards`. + let leftover_staking_rewards = total_validator_rewards; let leftover_storage_fund_inflow = leftover_staking_rewards.value(); + // Burning leftover rewards + self.iota_treasury_cap.burn_balance(leftover_staking_rewards, ctx); + let refunded_storage_rebate = self.storage_fund.advance_epoch( storage_reward, - storage_fund_reinvestment, - leftover_staking_rewards, storage_rebate_amount, non_refundable_storage_fee_amount, ); event::emit( + //TODO: Add additional information (e.g., how much was burned, how much was leftover, etc.) SystemEpochInfoEvent { epoch: self.epoch, protocol_version: self.protocol_version, reference_gas_price: self.reference_gas_price, total_stake: new_total_stake, storage_charge, - storage_fund_reinvestment: storage_fund_reinvestment_amount as u64, + storage_fund_reinvestment: 0, storage_rebate: storage_rebate_amount, storage_fund_balance: self.storage_fund.total_balance(), stake_subsidy_amount, total_gas_fees: computation_charge, - total_stake_rewards_distributed: computation_reward_distributed + storage_fund_reward_distributed, + total_stake_rewards_distributed: total_validator_rewards_distributed, leftover_storage_fund_inflow, } ); @@ -960,6 +951,29 @@ module iota_system::iota_system_state_inner { refunded_storage_rebate } + /// Mint or burn IOTA tokens depending on the given target reward per validator + /// and the amount of computation fees burned in this epoch. + fun match_computation_reward_to_target_reward( + validator_target_reward: u64, + mut computation_reward: Balance, + iota_treasury_cap: &mut iota::iota::IotaTreasuryCap, + ctx: &TxContext, + ): Balance { + if (computation_reward.value() < validator_target_reward) { + let tokens_to_mint = validator_target_reward - computation_reward.value(); + let new_tokens = iota_treasury_cap.mint_balance(tokens_to_mint, ctx); + computation_reward.join(new_tokens); + computation_reward + } else if (computation_reward.value() > validator_target_reward) { + let tokens_to_burn = computation_reward.value() - validator_target_reward; + let rewards_to_burn = computation_reward.split(tokens_to_burn); + iota_treasury_cap.burn_balance(rewards_to_burn, ctx); + computation_reward + } else { + computation_reward + } + } + /// 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: &IotaSystemStateInnerV2): u64 { @@ -1004,6 +1018,11 @@ module iota_system::iota_system_state_inner { self.validators.staking_pool_mappings() } + /// Returns the total iota supply. + public(package) fun get_total_iota_supply(self: &IotaSystemStateInnerV2): u64 { + self.iota_treasury_cap.total_supply() + } + /// Returns all the validators who are currently reporting `addr` public(package) fun get_reporters_of(self: &IotaSystemStateInnerV2, addr: address): VecSet
{ diff --git a/crates/iota-framework/packages/iota-system/sources/storage_fund.move b/crates/iota-framework/packages/iota-system/sources/storage_fund.move index 02b1187b07d..ff1891ebc02 100644 --- a/crates/iota-framework/packages/iota-system/sources/storage_fund.move +++ b/crates/iota-framework/packages/iota-system/sources/storage_fund.move @@ -34,15 +34,9 @@ module iota_system::storage_fund { public(package) fun advance_epoch( self: &mut StorageFund, storage_charges: Balance, - storage_fund_reinvestment: Balance, - leftover_staking_rewards: Balance, storage_rebate_amount: u64, non_refundable_storage_fee_amount: u64, ) : Balance { - // Both the reinvestment and leftover rewards are not to be refunded so they go to the non-refundable balance. - self.non_refundable_balance.join(storage_fund_reinvestment); - self.non_refundable_balance.join(leftover_staking_rewards); - // The storage charges for the epoch come from the storage rebate of the new objects created // and the new storage rebates of the objects modified during the epoch so we put the charges // into `total_object_storage_rebates`. diff --git a/crates/iota-framework/packages/iota-system/sources/validator_set.move b/crates/iota-framework/packages/iota-system/sources/validator_set.move index 6ef543868b1..cc2e8f6a34d 100644 --- a/crates/iota-framework/packages/iota-system/sources/validator_set.move +++ b/crates/iota-framework/packages/iota-system/sources/validator_set.move @@ -77,7 +77,6 @@ module iota_system::validator_set { stake: u64, commission_rate: u64, pool_staking_reward: u64, - storage_fund_staking_reward: u64, pool_token_exchange_rate: PoolTokenExchangeRate, tallying_rule_reporters: vector
, tallying_rule_global_score: u64, @@ -92,7 +91,6 @@ module iota_system::validator_set { voting_power: u64, commission_rate: u64, pool_staking_reward: u64, - storage_fund_staking_reward: u64, pool_token_exchange_rate: PoolTokenExchangeRate, tallying_rule_reporters: vector
, tallying_rule_global_score: u64, @@ -343,8 +341,7 @@ module iota_system::validator_set { /// 5. At the end, we calculate the total stake for the new epoch. public(package) fun advance_epoch( self: &mut ValidatorSet, - computation_reward: &mut Balance, - storage_fund_reward: &mut Balance, + total_validator_rewards: &mut Balance, validator_report_records: &mut VecMap>, reward_slashing_rate: u64, low_stake_threshold: u64, @@ -356,11 +353,10 @@ module iota_system::validator_set { let total_voting_power = voting_power::total_voting_power(); // Compute the reward distribution without taking into account the tallying rule slashing. - let (unadjusted_staking_reward_amounts, unadjusted_storage_fund_reward_amounts) = compute_unadjusted_reward_distribution( + let unadjusted_staking_reward_amounts = compute_unadjusted_reward_distribution( &self.active_validators, total_voting_power, - computation_reward.value(), - storage_fund_reward.value(), + total_validator_rewards.value(), ); // Use the tallying rule report records for the epoch to compute validators that will be @@ -371,30 +367,24 @@ module iota_system::validator_set { // Compute the reward adjustments of slashed validators, to be taken into // account in adjusted reward computation. - let (total_staking_reward_adjustment, individual_staking_reward_adjustments, - total_storage_fund_reward_adjustment, individual_storage_fund_reward_adjustments - ) = + let (total_staking_reward_adjustment, individual_staking_reward_adjustments) = compute_reward_adjustments( get_validator_indices(&self.active_validators, &slashed_validators), reward_slashing_rate, &unadjusted_staking_reward_amounts, - &unadjusted_storage_fund_reward_amounts, ); // Compute the adjusted amounts of stake each validator should get given the tallying rule // reward adjustments we computed before. // `compute_adjusted_reward_distribution` must be called before `distribute_reward` and `adjust_stake_and_gas_price` to // make sure we are using the current epoch's stake information to compute reward distribution. - let (adjusted_staking_reward_amounts, adjusted_storage_fund_reward_amounts) = compute_adjusted_reward_distribution( + let adjusted_staking_reward_amounts = compute_adjusted_reward_distribution( &self.active_validators, total_voting_power, total_slashed_validator_voting_power, unadjusted_staking_reward_amounts, - unadjusted_storage_fund_reward_amounts, total_staking_reward_adjustment, individual_staking_reward_adjustments, - total_storage_fund_reward_adjustment, - individual_storage_fund_reward_adjustments ); // Distribute the rewards before adjusting stake so that we immediately start compounding @@ -402,9 +392,7 @@ module iota_system::validator_set { distribute_reward( &mut self.active_validators, &adjusted_staking_reward_amounts, - &adjusted_storage_fund_reward_amounts, - computation_reward, - storage_fund_reward, + total_validator_rewards, ctx ); @@ -414,7 +402,7 @@ module iota_system::validator_set { // Emit events after we have processed all the rewards distribution and pending stakes. emit_validator_epoch_events(new_epoch, &self.active_validators, &adjusted_staking_reward_amounts, - &adjusted_storage_fund_reward_amounts, validator_report_records, &slashed_validators); + validator_report_records, &slashed_validators); // Note that all their staged next epoch metadata will be effectuated below. process_pending_validators(self, new_epoch); @@ -970,23 +958,17 @@ module iota_system::validator_set { } } - /// Compute both the individual reward adjustments and total reward adjustment for staking rewards - /// as well as storage fund rewards. + /// Compute both the individual reward adjustments and total reward adjustment for staking rewards. fun compute_reward_adjustments( mut slashed_validator_indices: vector, reward_slashing_rate: u64, unadjusted_staking_reward_amounts: &vector, - unadjusted_storage_fund_reward_amounts: &vector, ): ( u64, // sum of staking reward adjustments VecMap, // mapping of individual validator's staking reward adjustment from index -> amount - u64, // sum of storage fund reward adjustments - VecMap, // mapping of individual validator's storage fund reward adjustment from index -> amount ) { let mut total_staking_reward_adjustment = 0; let mut individual_staking_reward_adjustments = vec_map::empty(); - let mut total_storage_fund_reward_adjustment = 0; - let mut individual_storage_fund_reward_adjustments = vec_map::empty(); while (!slashed_validator_indices.is_empty()) { let validator_index = slashed_validator_indices.pop_back(); @@ -1000,20 +982,9 @@ module iota_system::validator_set { // Insert into individual mapping and record into the total adjustment sum. individual_staking_reward_adjustments.insert(validator_index, staking_reward_adjustment_u128 as u64); total_staking_reward_adjustment = total_staking_reward_adjustment + (staking_reward_adjustment_u128 as u64); - - // Do the same thing for storage fund rewards. - let unadjusted_storage_fund_reward = unadjusted_storage_fund_reward_amounts[validator_index]; - let storage_fund_reward_adjustment_u128 = - unadjusted_storage_fund_reward as u128 * (reward_slashing_rate as u128) - / BASIS_POINT_DENOMINATOR; - individual_storage_fund_reward_adjustments.insert(validator_index, storage_fund_reward_adjustment_u128 as u64); - total_storage_fund_reward_adjustment = total_storage_fund_reward_adjustment + (storage_fund_reward_adjustment_u128 as u64); }; - ( - total_staking_reward_adjustment, individual_staking_reward_adjustments, - total_storage_fund_reward_adjustment, individual_storage_fund_reward_adjustments - ) + (total_staking_reward_adjustment, individual_staking_reward_adjustments) } /// Process the validator report records of the epoch and return the addresses of the @@ -1042,17 +1013,14 @@ module iota_system::validator_set { /// Given the current list of active validators, the total stake and total reward, /// calculate the amount of reward each validator should get, without taking into /// account the tallying rule results. - /// Returns the unadjusted amounts of staking reward and storage fund reward for each validator. + /// Returns the unadjusted amounts of staking reward for each validator. fun compute_unadjusted_reward_distribution( validators: &vector, total_voting_power: u64, total_staking_reward: u64, - total_storage_fund_reward: u64, - ): (vector, vector) { + ): vector { let mut staking_reward_amounts = vector[]; - let mut storage_fund_reward_amounts = vector[]; let length = validators.length(); - let storage_fund_reward_per_validator = total_storage_fund_reward / length; let mut i = 0; while (i < length) { let validator = &validators[i]; @@ -1062,33 +1030,26 @@ module iota_system::validator_set { let voting_power: u128 = validator.voting_power() as u128; let reward_amount = voting_power * (total_staking_reward as u128) / (total_voting_power as u128); staking_reward_amounts.push_back(reward_amount as u64); - // Storage fund's share of the rewards are equally distributed among validators. - storage_fund_reward_amounts.push_back(storage_fund_reward_per_validator); i = i + 1; }; - (staking_reward_amounts, storage_fund_reward_amounts) + staking_reward_amounts } /// Use the reward adjustment info to compute the adjusted rewards each validator should get. - /// Returns the staking rewards each validator gets and the storage fund rewards each validator gets. - /// The staking rewards are shared with the stakers while the storage fund ones are not. + /// Returns the staking rewards each validator gets. + /// The staking rewards are shared with the stakers. fun compute_adjusted_reward_distribution( validators: &vector, total_voting_power: u64, total_slashed_validator_voting_power: u64, unadjusted_staking_reward_amounts: vector, - unadjusted_storage_fund_reward_amounts: vector, total_staking_reward_adjustment: u64, individual_staking_reward_adjustments: VecMap, - total_storage_fund_reward_adjustment: u64, - individual_storage_fund_reward_adjustments: VecMap, - ): (vector, vector) { + ): vector { let total_unslashed_validator_voting_power = total_voting_power - total_slashed_validator_voting_power; let mut adjusted_staking_reward_amounts = vector[]; - let mut adjusted_storage_fund_reward_amounts = vector[]; let length = validators.length(); - let num_unslashed_validators = length - individual_staking_reward_adjustments.size(); let mut i = 0; while (i < length) { @@ -1114,32 +1075,16 @@ module iota_system::validator_set { }; adjusted_staking_reward_amounts.push_back(adjusted_staking_reward_amount); - // Compute adjusted storage fund reward. - let unadjusted_storage_fund_reward_amount = unadjusted_storage_fund_reward_amounts[i]; - let adjusted_storage_fund_reward_amount = - // If the validator is one of the slashed ones, then subtract the adjustment. - if (individual_storage_fund_reward_adjustments.contains(&i)) { - let adjustment = individual_storage_fund_reward_adjustments[&i]; - unadjusted_storage_fund_reward_amount - adjustment - } else { - // Otherwise the slashed rewards should be equally distributed among the unslashed validators. - let adjustment = total_storage_fund_reward_adjustment / num_unslashed_validators; - unadjusted_storage_fund_reward_amount + adjustment - }; - adjusted_storage_fund_reward_amounts.push_back(adjusted_storage_fund_reward_amount); - i = i + 1; }; - (adjusted_staking_reward_amounts, adjusted_storage_fund_reward_amounts) + adjusted_staking_reward_amounts } fun distribute_reward( validators: &mut vector, adjusted_staking_reward_amounts: &vector, - adjusted_storage_fund_reward_amounts: &vector, staking_rewards: &mut Balance, - storage_fund_reward: &mut Balance, ctx: &mut TxContext ) { let length = validators.length(); @@ -1153,13 +1098,8 @@ module iota_system::validator_set { // Validator takes a cut of the rewards as commission. let validator_commission_amount = (staking_reward_amount as u128) * (validator.commission_rate() as u128) / BASIS_POINT_DENOMINATOR; - // The validator reward = storage_fund_reward + commission. - let mut validator_reward = staker_reward.split(validator_commission_amount as u64); - - // Add storage fund rewards to the validator's reward. - validator_reward.join( - storage_fund_reward.split(adjusted_storage_fund_reward_amounts[i]) - ); + // The validator reward = commission. + let validator_reward = staker_reward.split(validator_commission_amount as u64); // Add rewards to the validator. Don't try and distribute rewards though if the payout is zero. if (validator_reward.value() > 0) { @@ -1182,7 +1122,6 @@ module iota_system::validator_set { new_epoch: u64, vs: &vector, pool_staking_reward_amounts: &vector, - storage_fund_staking_reward_amounts: &vector, report_records: &VecMap>, slashed_validators: &vector
, ) { @@ -1209,7 +1148,6 @@ module iota_system::validator_set { voting_power: v.voting_power(), commission_rate: v.commission_rate(), pool_staking_reward: pool_staking_reward_amounts[i], - storage_fund_staking_reward: storage_fund_staking_reward_amounts[i], pool_token_exchange_rate: v.pool_token_exchange_rate_at_epoch(new_epoch), tallying_rule_reporters, tallying_rule_global_score, 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 5cd1118b95f..15ee319bdf1 100644 --- a/crates/iota-framework/packages/iota-system/tests/delegation_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/delegation_tests.move @@ -232,7 +232,7 @@ module iota_system::stake_tests { ); if (should_distribute_rewards) { - // Each validator pool gets 30 MICROS and each validator gets an additional 10 MICROS. + // Each validator pool gets 40 IOTA. advance_epoch_with_reward_amounts(0, 80, scenario); } else { advance_epoch(scenario); @@ -242,8 +242,7 @@ module iota_system::stake_tests { advance_epoch(scenario); - let reward_amt = if (should_distribute_rewards) 15 * MICROS_PER_IOTA else 0; - let validator_reward_amt = if (should_distribute_rewards) 10 * MICROS_PER_IOTA else 0; + let reward_amt = if (should_distribute_rewards) 20 * MICROS_PER_IOTA else 0; // Make sure stake withdrawal happens scenario.next_tx(STAKER_ADDR_1); @@ -270,10 +269,9 @@ module iota_system::stake_tests { // Validator unstakes now. assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 0); unstake(VALIDATOR_ADDR_1, 0, scenario); - if (should_distribute_rewards) unstake(VALIDATOR_ADDR_1, 0, scenario); // Make sure have all of their stake. NB there is no epoch change. This is immediate. - assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt + validator_reward_amt); + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt); scenario_val.end(); } @@ -294,10 +292,8 @@ module iota_system::stake_tests { // this epoch, they should get the rewards from this epoch. advance_epoch_with_reward_amounts(0, 80, scenario); - // Each validator pool gets 30 MICROS and validators shares the 20 MICROS from the storage fund - // so validator gets another 10 MICROS. - let reward_amt = 15 * MICROS_PER_IOTA; - let validator_reward_amt = 10 * MICROS_PER_IOTA; + // Each validator pool gets 40 IOTA. + let reward_amt = 20 * MICROS_PER_IOTA; // Make sure stake withdrawal happens scenario.next_tx(STAKER_ADDR_1); @@ -322,10 +318,9 @@ module iota_system::stake_tests { // Validator unstakes now. assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 0); unstake(VALIDATOR_ADDR_1, 0, scenario); - unstake(VALIDATOR_ADDR_1, 0, scenario); // Make sure have all of their stake. NB there is no epoch change. This is immediate. - assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt + validator_reward_amt); + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt); scenario_val.end(); } @@ -407,14 +402,19 @@ module iota_system::stake_tests { set_up_iota_system_state_with_storage_fund(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; + // At this point we got the following distribution of stake: + // V1: 100, V2: 100, storage fund: 100 add_validator_candidate(NEW_VALIDATOR_ADDR, b"name3", b"/ip4/127.0.0.1/udp/83", NEW_VALIDATOR_PUBKEY, NEW_VALIDATOR_POP, scenario); // Delegate 100 IOTA to the preactive validator stake_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, scenario); + // 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); // At this point we got the following distribution of stake: - // V1: 250, V2: 250, storage fund: 100 + // V1: 250, V2: 250, V3: 100, storage fund: 100 stake_with(STAKER_ADDR_2, NEW_VALIDATOR_ADDR, 50, scenario); stake_with(STAKER_ADDR_3, NEW_VALIDATOR_ADDR, 100, scenario); @@ -422,26 +422,28 @@ module iota_system::stake_tests { // Now the preactive becomes active add_validator(NEW_VALIDATOR_ADDR, scenario); advance_epoch(scenario); - // 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); + // 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 - // staker 1 and 3 unstake from the validator and earns about 2/5 * (85 - 10) * 1/3 = 10 IOTA each. + // staker 1 and 3 unstake from the validator and earns about 2/5 * 85 * 1/3 = 11.33 IOTA each. // Although they stake in different epochs, they earn the same rewards as long as they unstake // in the same epoch because the validator was preactive when they staked. - // So they will both get slightly more than 110 IOTA in total balance. + // So they will both get slightly more than 111 IOTA in total balance. unstake(STAKER_ADDR_1, 0, scenario); - assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 110002000000); + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 111_335_600_000); unstake(STAKER_ADDR_3, 0, scenario); - assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 110002000000); + assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 111_335_600_000); advance_epoch_with_reward_amounts(0, 85, scenario); unstake(STAKER_ADDR_2, 0, scenario); - // staker 2 earns about 5 IOTA from the previous epoch and 24-ish from this one - // so in total she has about 50 + 5 + 24 = 79 IOTA. - assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 78862939078); + // 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 + // so in total she has about 50 + 5.66 + 28.33 = 83.99 IOTA. + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 84_006_800_000); scenario_val.end(); } 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 8456f98147b..31118abc210 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 @@ -6,7 +6,7 @@ module iota_system::governance_test_utils { use iota::address; use iota::balance; - use iota::iota::IOTA; + use iota::iota::{Self, IOTA}; use iota::coin::{Self, Coin}; use iota_system::staking_pool::{StakedIota, StakingPool}; use iota::test_utils::assert_eq; @@ -64,7 +64,7 @@ module iota_system::governance_test_utils { ) { let system_parameters = iota_system_state_inner::create_system_parameters( 42, // epoch_duration_ms, doesn't matter what number we put here - 0, // stake_subsidy_start_epoch + 18446744073709551615, // stake_subsidy_start_epoch 150, // max_validator_count 1, // min_validator_joining_stake @@ -74,18 +74,34 @@ module iota_system::governance_test_utils { ctx, ); + let mut iota_treasury_cap = iota::create_for_testing(ctx); + + // We mint the given amount so the system appears to have a total supply of iota_supply_amount, + // but we don't put it in the subsidy fund. + let iota_total_supply_balance = iota_treasury_cap.mint_balance( + iota_supply_amount * MICROS_PER_IOTA, + ctx, + ); + iota_total_supply_balance.destroy_for_testing(); + let stake_subsidy = stake_subsidy::create( - balance::create_for_testing(iota_supply_amount * MICROS_PER_IOTA), // iota_supply - 0, // stake subsidy initial distribution amount - 10, // stake_subsidy_period_length - 0, // stake_subsidy_decrease_rate + balance::zero(), + 0, // stake subsidy initial distribution amount + 18446744073709551615, // stake_subsidy_period_length + 0, // stake_subsidy_decrease_rate + ctx, + ); + + let storage_fund = iota_treasury_cap.mint_balance( + storage_fund_amount * MICROS_PER_IOTA, ctx, ); iota_system::create( object::new(ctx), // it doesn't matter what ID iota system state has in tests + iota_treasury_cap, validators, - balance::create_for_testing(storage_fund_amount * MICROS_PER_IOTA), // storage_fund + storage_fund, // storage_fund 1, // protocol version 0, // chain_start_timestamp_ms system_parameters, @@ -115,7 +131,7 @@ module iota_system::governance_test_utils { } public fun advance_epoch_with_reward_amounts_return_rebate( - storage_charge: u64, computation_charge: u64, stoarge_rebate: u64, non_refundable_storage_rebate: u64, scenario: &mut Scenario, + validator_target_reward: u64, storage_charge: u64, computation_charge: u64, stoarge_rebate: u64, non_refundable_storage_rebate: u64, scenario: &mut Scenario, ): Balance { scenario.next_tx(@0x0); let new_epoch = scenario.ctx().epoch() + 1; @@ -124,17 +140,25 @@ module iota_system::governance_test_utils { let ctx = scenario.ctx(); let storage_rebate = system_state.advance_epoch_for_testing( - new_epoch, 1, storage_charge, computation_charge, stoarge_rebate, non_refundable_storage_rebate, 0, 0, 0, ctx, + new_epoch, 1, validator_target_reward, storage_charge, computation_charge, stoarge_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 ) { - let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(storage_charge * MICROS_PER_IOTA, computation_charge * MICROS_PER_IOTA, 0, 0, scenario); + advance_epoch_with_target_reward_amounts(computation_charge, storage_charge, computation_charge, 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 + ) { + let storage_rebate = advance_epoch_with_reward_amounts_return_rebate(validator_target_reward * MICROS_PER_IOTA, storage_charge * MICROS_PER_IOTA, computation_charge * MICROS_PER_IOTA, 0, 0, scenario); test_utils::destroy(storage_rebate) } @@ -150,8 +174,9 @@ module iota_system::governance_test_utils { let ctx = scenario.ctx(); + let validator_target_reward = computation_charge; let storage_rebate = system_state.advance_epoch_for_testing( - new_epoch, 1, storage_charge * MICROS_PER_IOTA, computation_charge * MICROS_PER_IOTA, 0, 0, 0, reward_slashing_rate, 0, ctx + new_epoch, 1, validator_target_reward * MICROS_PER_IOTA, storage_charge * MICROS_PER_IOTA, computation_charge * MICROS_PER_IOTA, 0, 0, reward_slashing_rate, 0, ctx ); test_utils::destroy(storage_rebate); test_scenario::return_shared(system_state); @@ -342,4 +367,12 @@ module iota_system::governance_test_utils { }; sum } + + /// Returns the total IOTA supply in the system state. + public fun total_supply(scenario: &mut Scenario): u64 { + let mut system_state = scenario.take_shared(); + let total_supply = system_state.get_total_iota_supply(); + test_scenario::return_shared(system_state); + total_supply + } } diff --git a/crates/iota-framework/packages/iota-system/tests/iota_system_tests.move b/crates/iota-framework/packages/iota-system/tests/iota_system_tests.move index 0761eab8301..94534f5984e 100644 --- a/crates/iota-framework/packages/iota-system/tests/iota_system_tests.move +++ b/crates/iota-framework/packages/iota-system/tests/iota_system_tests.move @@ -17,7 +17,7 @@ module iota_system::iota_system_tests { use iota_system::validator_set; use iota_system::validator_cap::UnverifiedValidatorOperationCap; use iota::balance; - use iota::test_utils::{assert_eq, destroy}; + use iota::test_utils::assert_eq; use iota::url; #[test] @@ -578,7 +578,7 @@ module iota_system::iota_system_tests { let new_pubkey1 = x"91b8de031e0b60861c655c8168596d98b065d57f26f287f8c810590b06a636eff13c4055983e95b2f60a4d6ba5484fa4176923d1f7807cc0b222ddf6179c1db099dba0433f098aae82542b3fd27b411d64a0a35aad01b2c07ac67f7d0a1d2c11"; let new_pop1 = x"b61913eb4dc7ea1d92f174e1a3c6cad3f49ae8de40b13b69046ce072d8d778bfe87e734349c7394fd1543fff0cb6e2d0"; - let mut scenario_val = test_scenario::begin(validator_addr); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; // Set up IotaSystemState with an active validator @@ -920,7 +920,7 @@ module iota_system::iota_system_tests { let new_pubkey = x"96d19c53f1bee2158c3fcfb5bb2f06d3a8237667529d2d8f0fbb22fe5c3b3e64748420b4103674490476d98530d063271222d2a59b0f7932909cc455a30f00c69380e6885375e94243f7468e9563aad29330aca7ab431927540e9508888f0e1c"; let new_pop = x"932336c35a8c393019c63eb0f7d385dd4e0bd131f04b54cf45aa9544f14dca4dab53bd70ffcb8e0b34656e4388309720"; - let mut scenario_val = test_scenario::begin(validator_addr); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; // Set up IotaSystemState with an active validator @@ -973,34 +973,4 @@ module iota_system::iota_system_tests { test_scenario::return_shared(system_state); scenario_val.end(); } - - #[test] - fun test_skip_stake_subsidy() { - let mut scenario_val = test_scenario::begin(@0x0); - let scenario = &mut scenario_val; - // Epoch duration is set to be 42 here. - set_up_iota_system_state(vector[@0x1, @0x2]); - - // If the epoch length is less than 42 then the stake subsidy distribution counter should not be incremented. Otherwise it should. - advance_epoch_and_check_distribution_counter(scenario, 42, true); - advance_epoch_and_check_distribution_counter(scenario, 32, false); - advance_epoch_and_check_distribution_counter(scenario, 52, true); - scenario_val.end(); - } - - fun advance_epoch_and_check_distribution_counter(scenario: &mut Scenario, epoch_length: u64, should_increment_counter: bool) { - scenario.next_tx(@0x0); - let new_epoch = scenario.ctx().epoch() + 1; - let mut system_state = scenario.take_shared(); - let prev_epoch_time = system_state.epoch_start_timestamp_ms(); - let prev_counter = system_state.get_stake_subsidy_distribution_counter(); - - let rebate = system_state.advance_epoch_for_testing( - new_epoch, 1, 0, 0, 0, 0, 0, 0, prev_epoch_time + epoch_length, scenario.ctx() - ); - destroy(rebate); - assert_eq(system_state.get_stake_subsidy_distribution_counter(), prev_counter + (if (should_increment_counter) 1 else 0)); - test_scenario::return_shared(system_state); - scenario.next_epoch(@0x0); - } } 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 11c6fa0d8c1..3f5d4bfaa05 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 @@ -10,16 +10,19 @@ module iota_system::rewards_distribution_tests { use iota_system::governance_test_utils::{ advance_epoch, advance_epoch_with_reward_amounts, + advance_epoch_with_reward_amounts_return_rebate, advance_epoch_with_reward_amounts_and_slashing_rates, + advance_epoch_with_target_reward_amounts, assert_validator_total_stake_amounts, assert_validator_non_self_stake_amounts, assert_validator_self_stake_amounts, create_validator_for_testing, create_iota_system_state_for_testing, stake_with, - total_iota_balance, unstake + total_iota_balance, total_supply, + unstake }; - use iota::test_utils::assert_eq; + use iota::test_utils::{assert_eq, destroy}; use iota::address; const VALIDATOR_ADDR_1: address = @0x1; @@ -61,6 +64,7 @@ module iota_system::rewards_distribution_tests { vector[150 * MICROS_PER_IOTA, 970 * MICROS_PER_IOTA, 350 * MICROS_PER_IOTA, 450 * MICROS_PER_IOTA], scenario ); + scenario_val.end(); } @@ -78,6 +82,175 @@ module iota_system::rewards_distribution_tests { scenario_val.end(); } + #[test] + fun test_validator_target_reward_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; + + // 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); + + let new_supply = total_supply(scenario); + // Since the target reward and computation reward are the same, no new tokens should + // have been minted, so the supply should stay constant. + assert!(prev_supply == new_supply, 0); + + scenario_val.end(); + } + + #[test] + fun test_validator_target_reward_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; + + // 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); + + let new_supply = total_supply(scenario); + // The difference between target reward and computation reward should have been burned. + assert!(prev_supply - (computation_reward - validator_target_reward) * MICROS_PER_IOTA == new_supply, 0); + + scenario_val.end(); + } + + #[test] + fun test_validator_target_reward_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; + + // 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); + + let new_supply = total_supply(scenario); + // The difference between target reward and computation reward should have been minted. + assert!(prev_supply + (validator_target_reward - computation_reward) * MICROS_PER_IOTA == new_supply, 0); + + scenario_val.end(); + } + + #[test] + fun test_validator_target_reward_higher_than_computation_reward() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + advance_epoch(scenario); + // V1: 100, V2: 200, V3: 300, V4: 400 + + advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + + // The computation reward is lower than the target reward, so 400 IOTA should be minted. + // Each validator pool has 25% of the voting power and thus gets 25% of the reward (200 IOTA). + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (100 + 200) * MICROS_PER_IOTA, + (200 + 200) * MICROS_PER_IOTA, + (300 + 200) * MICROS_PER_IOTA, + (400 + 200) * MICROS_PER_IOTA, + ], + scenario + ); + + unstake(VALIDATOR_ADDR_1, 0, scenario); + + // Validator should get the entire reward of 200 plus its initially staked 100 IOTA. + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), (100+200) * MICROS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + fun test_validator_target_reward_lower_than_computation_reward() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + advance_epoch(scenario); + // V1: 100, V2: 200, V3: 300, V4: 400 + + advance_epoch_with_target_reward_amounts(800, 0, 1000, scenario); + + // The computation reward is higher than the target reward, so 200 IOTA should be burned. + // Each validator pool has 25% of the voting power and thus gets 25% of the reward (200 IOTA). + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (100 + 200) * MICROS_PER_IOTA, + (200 + 200) * MICROS_PER_IOTA, + (300 + 200) * MICROS_PER_IOTA, + (400 + 200) * MICROS_PER_IOTA, + ], + scenario + ); + + unstake(VALIDATOR_ADDR_1, 0, scenario); + + // Validator should get the entire reward of 200 plus its initially staked 100 IOTA. + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), (100+200) * MICROS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + fun test_validator_target_reward_higher_than_computation_reward_with_commission() { + 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); + advance_epoch(scenario); + // V1: 200, V2: 250, V3: 300, V4: 400 + + set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 500, scenario); // 5% commission + + advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + + // The computation reward is lower than the target reward, so 400 IOTA should be minted. + // Each validator pool has 25% of the voting power and thus gets 25% of the reward (200 IOTA each). + assert_validator_total_stake_amounts( + validator_addrs(), + vector[ + (200 + 200) * MICROS_PER_IOTA, + (250 + 200) * MICROS_PER_IOTA, + (300 + 200) * MICROS_PER_IOTA, + (400 + 200) * MICROS_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) * MICROS_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) * MICROS_PER_IOTA); + + scenario_val.end(); + } + #[test] fun test_stake_rewards() { set_up_iota_system_state(); @@ -153,7 +326,7 @@ module iota_system::rewards_distribution_tests { advance_epoch(scenario); // V1: 200, V2: 300, V3: 300, V4: 400 - set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_2, 2000, scenario); // 50% commission + set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_2, 2000, scenario); // 20% commission advance_epoch_with_reward_amounts(0, 120, scenario); // V1: 230, V2: 330, V3: 330, V4: 430 // 2 IOTA, or 20 % of staker_2's rewards, goes to validator_2 @@ -176,6 +349,48 @@ module iota_system::rewards_distribution_tests { scenario_val.end(); } + #[test] + fun test_validator_commission_with_staking() { + 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); + advance_epoch(scenario); + // V1: 200, V2: 200, V3: 300, V4: 400 + + // Validator 1: 10% commission. + set_commission_rate_and_advance_epoch(VALIDATOR_ADDR_1, 1000, scenario); + + advance_epoch_with_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( + validator_addrs(), + vector[ + (200 + 200) * MICROS_PER_IOTA, + (200 + 200) * MICROS_PER_IOTA, + (300 + 200) * MICROS_PER_IOTA, + (400 + 200) * MICROS_PER_IOTA, + ], + scenario + ); + + // Unstakes the initially created StakedIota of value 100 IOTA. + unstake(VALIDATOR_ADDR_1, 0, scenario); + // The validator should have received a 10% commission on the reward of 200 IOTA (= 20 IOTA) + // in the form of a StakedIota. + unstake(VALIDATOR_ADDR_1, 0, scenario); + unstake(STAKER_ADDR_1, 0, scenario); + + // The remaining 200 - 20 = 180 should be distributed equally between validator + // and staker since both have equivalent stake. + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), (100+90+20) * MICROS_PER_IOTA); + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), (100+90) * MICROS_PER_IOTA); + + scenario_val.end(); + } + #[test] fun test_rewards_slashing() { set_up_iota_system_state(); @@ -286,21 +501,20 @@ module iota_system::rewards_distribution_tests { 1000, 1500, 2000, scenario ); - // Each unslashed validator staking pool gets 300 IOTA of computation rewards + 75 IOTA of storage fund rewards + - // 20 IOTA (1/3) of validator 4's slashed computation reward and 5 IOTA (1/3) of validator 4's slashed - // storage fund reward, so in total it gets 400 IOTA of rewards. - // Validator 3 has a delegator with her so she gets 320 * 3/4 + 75 + 5 = 320 IOTA of rewards. - // Validator 4's should get 300 * 4/5 * (1 - 20%) = 192 in computation rewards and 75 * (1 - 20%) = 60 in storage rewards. - assert_validator_self_stake_amounts(validator_addrs(), vector[500 * MICROS_PER_IOTA, 600 * MICROS_PER_IOTA, 620 * MICROS_PER_IOTA, 652 * MICROS_PER_IOTA], scenario); + // Each unslashed validator staking pool gets 375 IOTA of computation rewards + 25 IOTA (1/3) of validator 4's slashed computation reward, + // so in total it gets 400 IOTA of rewards. + // Validator 3's should get (375 + 25) * 3/4 = 300 in computation rewards. + // Validator 4's should get (375 - 75) * 4/5 = 240 in computation rewards. + assert_validator_self_stake_amounts(validator_addrs(), vector[500 * MICROS_PER_IOTA, 600 * MICROS_PER_IOTA, 600 * MICROS_PER_IOTA, 640 * MICROS_PER_IOTA], scenario); // Unstake so we can check the stake rewards as well. unstake(STAKER_ADDR_1, 0, scenario); unstake(STAKER_ADDR_2, 0, scenario); - // Staker 1 gets 320 * 1/4 = 80 IOTA of rewards. - assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), (100 + 80) * MICROS_PER_IOTA); - // Staker 2 gets 300 * 1/5 * (1 - 20%) = 48 IOTA of rewards. - assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), (100 + 48) * MICROS_PER_IOTA); + // Staker 1 gets (375 + 25) * 1/4 = 100 IOTA of rewards. + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), (100 + 100) * MICROS_PER_IOTA); + // Staker 2 gets (375 - 75) * 1/5 = 60 IOTA of rewards. + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), (100 + 60) * MICROS_PER_IOTA); scenario_val.end(); } @@ -308,35 +522,24 @@ module iota_system::rewards_distribution_tests { #[test] fun test_everyone_slashed() { // This test is to make sure that if everyone is slashed, our protocol works as expected without aborting - // and all rewards go to the storage fund. + // and rewards are burned, and no tokens go to the storage fund. set_up_iota_system_state(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; - report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_4, scenario); - report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_4, scenario); - report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_4, scenario); - report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_3, scenario); - report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_3, scenario); - report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_3, scenario); - report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_2, scenario); - report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_2, scenario); - report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_2, scenario); - report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_1, scenario); - report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_1, scenario); - report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_1, scenario); + slash_all_validators(scenario); advance_epoch_with_reward_amounts_and_slashing_rates( - 1000, 3000, 10_000, scenario + 1000, 500, 10_000, scenario ); // All validators should have 0 rewards added so their stake stays the same. assert_validator_self_stake_amounts(validator_addrs(), vector[100 * MICROS_PER_IOTA, 200 * MICROS_PER_IOTA, 300 * MICROS_PER_IOTA, 400 * MICROS_PER_IOTA], scenario); scenario.next_tx(@0x0); - // Storage fund balance should increase by 4000 IOTA. + // Storage fund balance should be the same as before. let mut system_state = scenario.take_shared(); - assert_eq(system_state.get_storage_fund_total_balance(), 4000 * MICROS_PER_IOTA); + assert_eq(system_state.get_storage_fund_total_balance(), 1000 * MICROS_PER_IOTA); // The entire 1000 IOTA of storage rewards should go to the object rebate portion of the storage fund. assert_eq(system_state.get_storage_fund_object_rebates(), 1000 * MICROS_PER_IOTA); @@ -441,6 +644,65 @@ module iota_system::rewards_distribution_tests { scenario_val.end(); } + #[test] + fun test_slashed_validators_leftover_burning() { + set_up_iota_system_state(); + 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. + slash_all_validators(scenario); + + // Pass 700 IOTA as computation reward(for an instance). + advance_epoch_with_reward_amounts_and_slashing_rates( + 1000, 700, 10_000, scenario + ); + + scenario.next_tx(@0x0); + // The total supply of 1000 IOTA should be reduced by 700 IOTA because the 700 IOTA becomes leftover and should be burned. + assert_eq(total_supply(scenario), 300 * MICROS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + #[expected_failure(abort_code = iota::balance::EOverflow)] + fun test_leftover_is_larger_than_supply() { + set_up_iota_system_state(); + 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. + slash_all_validators(scenario); + + // Pass 1700 IOTA as computation reward which is larger than the total supply of 1000 IOTA. + advance_epoch_with_reward_amounts_and_slashing_rates( + 1000, 1700, 10_000, scenario + ); + + scenario_val.end(); + } + + #[test] + fun test_leftover_burning_after_reward_distribution() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + // 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); + destroy(storage_rebate); + + scenario.next_tx(@0x0); + + // Total supply after leftover has burned. + // The 999,999,999,999 is obtained by subtracting the leftover from the total supply: 1,000,000,000,000 - 1 = 999,999,999,999. + assert_eq(total_supply(scenario), 999_999_999_999); + + scenario_val.end(); + } + fun set_up_iota_system_state() { let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; @@ -492,4 +754,19 @@ module iota_system::rewards_distribution_tests { scenario.return_to_sender(cap); test_scenario::return_shared(system_state); } + + fun slash_all_validators(scenario: &mut Scenario) { + report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_4, scenario); + report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_4, scenario); + report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_4, scenario); + report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_3, scenario); + report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_3, scenario); + report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_3, scenario); + report_validator(VALIDATOR_ADDR_1, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_2, scenario); + report_validator(VALIDATOR_ADDR_2, VALIDATOR_ADDR_1, scenario); + report_validator(VALIDATOR_ADDR_3, VALIDATOR_ADDR_1, scenario); + report_validator(VALIDATOR_ADDR_4, VALIDATOR_ADDR_1, scenario); + } } 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 0c6d97d80ed..a7eee1ce02b 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 @@ -21,6 +21,7 @@ module iota_system::timelocked_stake_tests { add_validator_candidate, advance_epoch, advance_epoch_with_reward_amounts, + advance_epoch_with_target_reward_amounts, assert_validator_total_stake_amounts, create_validator_for_testing, create_iota_system_state_for_testing, @@ -602,15 +603,14 @@ module iota_system::timelocked_stake_tests { scenario ); - // Each validator pool gets 30 MICROS and each validator gets an additional 10 MICROS. + // Each validator pool gets 40 IOTA. advance_epoch_with_reward_amounts(0, 80, scenario); remove_validator(VALIDATOR_ADDR_1, scenario); advance_epoch(scenario); - let reward_amt = 15 * MICROS_PER_IOTA; - let validator_reward_amt = 10 * MICROS_PER_IOTA; + let reward_amt = 20 * MICROS_PER_IOTA; // Make sure stake withdrawal happens scenario.next_tx(STAKER_ADDR_1); @@ -638,10 +638,9 @@ module iota_system::timelocked_stake_tests { // Validator unstakes now. assert!(!has_iota_coins(VALIDATOR_ADDR_1, scenario), 2); unstake(VALIDATOR_ADDR_1, 0, scenario); - unstake(VALIDATOR_ADDR_1, 0, scenario); // Make sure have all of their stake. NB there is no epoch change. This is immediate. - assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt + validator_reward_amt); + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt); scenario_val.end(); } @@ -662,10 +661,8 @@ module iota_system::timelocked_stake_tests { // this epoch, they should get the rewards from this epoch. advance_epoch_with_reward_amounts(0, 80, scenario); - // Each validator pool gets 30 MICROS and validators shares the 20 MICROS from the storage fund - // so validator gets another 10 MICROS. - let reward_amt = 15 * MICROS_PER_IOTA; - let validator_reward_amt = 10 * MICROS_PER_IOTA; + // Each validator pool gets 40 IOTA. + let reward_amt = 20 * MICROS_PER_IOTA; // Make sure stake withdrawal happens scenario.next_tx(STAKER_ADDR_1); @@ -692,10 +689,9 @@ module iota_system::timelocked_stake_tests { // Validator unstakes now. assert!(!has_iota_coins(VALIDATOR_ADDR_1, scenario), 2); unstake(VALIDATOR_ADDR_1, 0, scenario); - unstake(VALIDATOR_ADDR_1, 0, scenario); // Make sure have all of their stake. NB there is no epoch change. This is immediate. - assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt + validator_reward_amt); + assert_eq(total_iota_balance(VALIDATOR_ADDR_1, scenario), 100 * MICROS_PER_IOTA + reward_amt); scenario_val.end(); } @@ -778,14 +774,19 @@ module iota_system::timelocked_stake_tests { set_up_iota_system_state_with_storage_fund(); let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); let scenario = &mut scenario_val; + // At this point we got the following distribution of stake: + // V1: 100, V2: 100, storage fund: 100 add_validator_candidate(NEW_VALIDATOR_ADDR, b"name3", b"/ip4/127.0.0.1/udp/83", NEW_VALIDATOR_PUBKEY, NEW_VALIDATOR_POP, scenario); // Delegate 100 IOTA to the preactive validator stake_timelocked_with(STAKER_ADDR_1, NEW_VALIDATOR_ADDR, 100, 10, scenario); + // 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); // At this point we got the following distribution of stake: - // V1: 250, V2: 250, storage fund: 100 + // V1: 250, V2: 250, V3: 100, storage fund: 100 stake_timelocked_with(STAKER_ADDR_2, NEW_VALIDATOR_ADDR, 50, 10, scenario); stake_timelocked_with(STAKER_ADDR_3, NEW_VALIDATOR_ADDR, 100, 10, scenario); @@ -793,31 +794,33 @@ module iota_system::timelocked_stake_tests { // Now the preactive becomes active add_validator(NEW_VALIDATOR_ADDR, scenario); advance_epoch(scenario); - // 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); + // 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 - // staker 1 and 3 unstake from the validator and earns about 2/5 * (85 - 10) * 1/3 = 10 IOTA each. + // staker 1 and 3 unstake from the validator and earns about 2/5 * 85 * 1/3 = 11.33 IOTA each. // Although they stake in different epochs, they earn the same rewards as long as they unstake // in the same epoch because the validator was preactive when they staked. - // So they will both get slightly more than 110 IOTA in total balance. + // So they will both get slightly more than 111 IOTA in total balance. unstake_timelocked(STAKER_ADDR_1, 0, scenario); assert_eq(total_timelocked_iota_balance(STAKER_ADDR_1, scenario), 100 * MICROS_PER_IOTA); - assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 10_002_000_000); + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 11_335_600_000); unstake_timelocked(STAKER_ADDR_3, 0, scenario); assert_eq(total_timelocked_iota_balance(STAKER_ADDR_3, scenario), 100 * MICROS_PER_IOTA); - assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 10_002_000_000); + assert_eq(total_iota_balance(STAKER_ADDR_3, scenario), 11_335_600_000); advance_epoch_with_reward_amounts(0, 85, scenario); unstake_timelocked(STAKER_ADDR_2, 0, scenario); - // staker 2 earns about 5 IOTA from the previous epoch and 24-ish from this one - // so in total she has about 50 + 5 + 24 = 79 IOTA. + // 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 + // so in total she has about 50 + 5.66 + 28.33 = 83.99 IOTA. assert_eq(total_timelocked_iota_balance(STAKER_ADDR_2, scenario), 50 * MICROS_PER_IOTA); - assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 28_862_939_078); + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 34_006_800_000); scenario_val.end(); } @@ -906,6 +909,83 @@ module iota_system::timelocked_stake_tests { scenario_val.end(); } + #[test] + fun test_timelock_validator_target_reward_higher_than_computation_reward() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + stake_timelocked_with(STAKER_ADDR_1, VALIDATOR_ADDR_1, 100, 60, scenario); + stake_timelocked_with(STAKER_ADDR_2, VALIDATOR_ADDR_2, 100, 60, scenario); + advance_epoch(scenario); + // V1: 200, V2: 200 + + advance_epoch_with_target_reward_amounts(800, 0, 400, scenario); + + // The computation reward is lower than the target reward, 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(), + vector[ + (200 + 400) * MICROS_PER_IOTA, + (200 + 400) * MICROS_PER_IOTA, + ], + scenario + ); + + unstake_timelocked(STAKER_ADDR_1, 0, scenario); + unstake_timelocked(STAKER_ADDR_2, 0, scenario); + + // Both stakers should get half the reward (= 200). + // Both should still have their original timelocked 100 IOTA that they staked. + assert_eq(total_timelocked_iota_balance(STAKER_ADDR_1, scenario), 100 * MICROS_PER_IOTA); + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 200 * MICROS_PER_IOTA); + + assert_eq(total_timelocked_iota_balance(STAKER_ADDR_2, scenario), 100 * MICROS_PER_IOTA); + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 200 * MICROS_PER_IOTA); + + scenario_val.end(); + } + + #[test] + fun test_timelock_validator_target_reward_lower_than_computation_reward() { + set_up_iota_system_state(); + let mut scenario_val = test_scenario::begin(VALIDATOR_ADDR_1); + let scenario = &mut scenario_val; + + stake_timelocked_with(STAKER_ADDR_1, VALIDATOR_ADDR_1, 100, 60, scenario); + stake_timelocked_with(STAKER_ADDR_2, VALIDATOR_ADDR_2, 150, 60, scenario); + advance_epoch(scenario); + // V1: 200, V2: 250 + + advance_epoch_with_target_reward_amounts(800, 0, 1000, scenario); + + // The computation reward is higher than the target reward, 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(), + vector[ + (200 + 400) * MICROS_PER_IOTA, + (250 + 400) * MICROS_PER_IOTA, + ], + scenario + ); + + unstake_timelocked(STAKER_ADDR_1, 0, scenario); + unstake_timelocked(STAKER_ADDR_2, 0, scenario); + + // Both stakers should have their original timelocked IOTA that they staked. + // Staker 1 should get half the reward (= 200) of its staking pool. + assert_eq(total_timelocked_iota_balance(STAKER_ADDR_1, scenario), 100 * MICROS_PER_IOTA); + assert_eq(total_iota_balance(STAKER_ADDR_1, scenario), 200 * MICROS_PER_IOTA); + + // Staker 1 should get 150 / 250 * 400 of the reward (= 240) of its staking pool. + assert_eq(total_timelocked_iota_balance(STAKER_ADDR_2, scenario), 150 * MICROS_PER_IOTA); + assert_eq(total_iota_balance(STAKER_ADDR_2, scenario), 240 * MICROS_PER_IOTA); + + scenario_val.end(); + } + fun assert_exchange_rate_eq( rates: &Table, epoch: u64, iota_amount: u64, pool_token_amount: u64 ) { @@ -914,6 +994,10 @@ module iota_system::timelocked_stake_tests { assert_eq(rate.pool_token_amount(), pool_token_amount * MICROS_PER_IOTA); } + fun validator_addrs() : vector
{ + vector[VALIDATOR_ADDR_1, VALIDATOR_ADDR_2] + } + fun set_up_iota_system_state() { let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; @@ -923,7 +1007,7 @@ module iota_system::timelocked_stake_tests { create_validator_for_testing(VALIDATOR_ADDR_1, 100, ctx), create_validator_for_testing(VALIDATOR_ADDR_2, 100, ctx) ]; - create_iota_system_state_for_testing(validators, 0, 0, ctx); + create_iota_system_state_for_testing(validators, 500, 0, ctx); scenario_val.end(); } 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 87461e3a91f..978a895afcd 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 @@ -433,11 +433,9 @@ module iota_system::validator_set_tests { fun advance_epoch_with_dummy_rewards(validator_set: &mut ValidatorSet, scenario: &mut Scenario) { scenario.next_epoch(@0x0); let mut dummy_computation_reward = balance::zero(); - let mut dummy_storage_fund_reward = balance::zero(); validator_set.advance_epoch( &mut dummy_computation_reward, - &mut dummy_storage_fund_reward, &mut vec_map::empty(), 0, // reward_slashing_rate 0, // low_stake_threshold @@ -447,7 +445,6 @@ module iota_system::validator_set_tests { ); dummy_computation_reward.destroy_zero(); - dummy_storage_fund_reward.destroy_zero(); } fun advance_epoch_with_low_stake_params( @@ -459,10 +456,8 @@ module iota_system::validator_set_tests { ) { scenario.next_epoch(@0x0); let mut dummy_computation_reward = balance::zero(); - let mut dummy_storage_fund_reward = balance::zero(); validator_set.advance_epoch( &mut dummy_computation_reward, - &mut dummy_storage_fund_reward, &mut vec_map::empty(), 0, // reward_slashing_rate low_stake_threshold * MICROS_PER_IOTA, @@ -472,7 +467,6 @@ module iota_system::validator_set_tests { ); dummy_computation_reward.destroy_zero(); - dummy_storage_fund_reward.destroy_zero(); } fun add_and_activate_validator(validator_set: &mut ValidatorSet, validator: Validator, scenario: &mut Scenario) { diff --git a/crates/iota-genesis-builder/src/lib.rs b/crates/iota-genesis-builder/src/lib.rs index 3762cca39c6..4db8bb360d4 100644 --- a/crates/iota-genesis-builder/src/lib.rs +++ b/crates/iota-genesis-builder/src/lib.rs @@ -24,7 +24,7 @@ use iota_framework::{BuiltInFramework, SystemPackage}; use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion}; use iota_sdk::{types::block::address::Address, Url}; use iota_types::{ - balance::Balance, + balance::{Balance, BALANCE_MODULE_NAME}, base_types::{ ExecutionDigests, IotaAddress, ObjectID, ObjectRef, SequenceNumber, TransactionDigest, TxContext, @@ -58,7 +58,7 @@ use iota_types::{ CallArg, CheckedInputObjects, Command, InputObjectKind, ObjectArg, ObjectReadResult, Transaction, }, - IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, + IOTA_FRAMEWORK_PACKAGE_ID, IOTA_SYSTEM_ADDRESS, }; use move_binary_format::CompiledModule; use move_core_types::ident_str; @@ -277,17 +277,6 @@ impl Builder { let mut objects = self.migration_objects.take_objects(); objects.extend(self.objects.values().cloned()); - // Use stake_subsidy_start_epoch to mimic the burn of newly minted IOTA coins - if !self.is_vanilla() { - // Because we set the `stake_subsidy_fund` as non-zero - // we need to effectively disable subsidy rewards - // to avoid the respective inflation effects. - // - // TODO: Handle properly during new tokenomics - // implementation. - self.parameters.stake_subsidy_start_epoch = u64::MAX; - } - // Finally build the genesis data self.built_genesis = Some(build_unsigned_genesis_data( &self.parameters, @@ -569,9 +558,16 @@ impl Builder { // Check distribution is correct let token_distribution_schedule = self.token_distribution_schedule.clone().unwrap(); + + let allocations_amount: u64 = token_distribution_schedule + .allocations + .iter() + .map(|allocation| allocation.amount_nanos) + .sum(); + assert_eq!( - system_state.stake_subsidy.balance.value(), - token_distribution_schedule.stake_subsidy_fund_nanos + system_state.iota_treasury_cap.total_supply().value, + token_distribution_schedule.pre_minted_supply + allocations_amount ); let mut gas_objects: BTreeMap = unsigned_genesis @@ -1213,7 +1209,7 @@ pub fn generate_genesis_system_object( let mut builder = ProgrammableTransactionBuilder::new(); // Step 1: Create the IotaSystemState UID let iota_system_state_uid = builder.programmable_move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("object").to_owned(), ident_str!("iota_system_state").to_owned(), vec![], @@ -1222,7 +1218,7 @@ pub fn generate_genesis_system_object( // Step 2: Create and share the Clock. builder.move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("clock").to_owned(), ident_str!("create").to_owned(), vec![], @@ -1233,7 +1229,7 @@ pub fn generate_genesis_system_object( // (which only happens in tests). if protocol_config.create_authenticator_state_in_genesis() { builder.move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("authenticator_state").to_owned(), ident_str!("create").to_owned(), vec![], @@ -1242,7 +1238,7 @@ pub fn generate_genesis_system_object( } if protocol_config.random_beacon() { builder.move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("random").to_owned(), ident_str!("create").to_owned(), vec![], @@ -1251,7 +1247,7 @@ pub fn generate_genesis_system_object( } if protocol_config.enable_coin_deny_list() { builder.move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, DENY_LIST_MODULE.to_owned(), DENY_LIST_CREATE_FUNC.to_owned(), vec![], @@ -1259,18 +1255,37 @@ pub fn generate_genesis_system_object( )?; } - // Step 4: Mint the supply of IOTA. - let iota_supply = builder.programmable_move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + // Step 4: Create the IOTA Coin Treasury Cap. + let iota_treasury_cap = builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("iota").to_owned(), ident_str!("new").to_owned(), vec![], vec![], ); + let pre_minted_supply_amount = builder + .pure(token_distribution_schedule.pre_minted_supply) + .expect("serialization of u64 should succeed"); + let pre_minted_supply = builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, + ident_str!("iota").to_owned(), + ident_str!("mint_balance").to_owned(), + vec![], + vec![iota_treasury_cap, pre_minted_supply_amount], + ); + + builder.programmable_move_call( + IOTA_FRAMEWORK_PACKAGE_ID, + BALANCE_MODULE_NAME.to_owned(), + ident_str!("destroy_genesis_supply").to_owned(), + vec![GAS::type_tag()], + vec![pre_minted_supply], + ); + // Step 5: Create System Timelock Cap. let system_timelock_cap = builder.programmable_move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("timelock").to_owned(), ident_str!("new_system_timelock_cap").to_owned(), vec![], @@ -1279,8 +1294,8 @@ pub fn generate_genesis_system_object( // Step 6: Run genesis. // The first argument is the system state uid we got from step 1 and the second - // one is the IOTA supply we got from step 3. - let mut arguments = vec![iota_system_state_uid, iota_supply]; + // one is the IOTA `TreasuryCap` we got from step 4. + let mut arguments = vec![iota_system_state_uid, iota_treasury_cap]; let mut call_arg_arguments = vec![ CallArg::Pure(bcs::to_bytes(&genesis_chain_parameters).unwrap()), CallArg::Pure(bcs::to_bytes(&genesis_validators).unwrap()), @@ -1354,7 +1369,7 @@ pub fn split_timelocks( builder.pure(surplus_amount)?, ]; let surplus_timelock = builder.programmable_move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("timelock").to_owned(), ident_str!("split").to_owned(), vec![GAS::type_tag()], @@ -1362,7 +1377,7 @@ pub fn split_timelocks( ); let arguments = vec![surplus_timelock, builder.pure(*recipient)?]; builder.programmable_move_call( - IOTA_FRAMEWORK_ADDRESS.into(), + IOTA_FRAMEWORK_PACKAGE_ID, ident_str!("timelock").to_owned(), ident_str!("transfer").to_owned(), vec![Balance::type_tag(GAS::type_tag())], diff --git a/crates/iota-genesis-builder/src/stake.rs b/crates/iota-genesis-builder/src/stake.rs index a0f88d6d6c7..cad5095a5fb 100644 --- a/crates/iota-genesis-builder/src/stake.rs +++ b/crates/iota-genesis-builder/src/stake.rs @@ -7,6 +7,7 @@ use iota_config::genesis::{ }; use iota_types::{ base_types::{IotaAddress, ObjectRef}, + gas_coin::TOTAL_SUPPLY_NANOS, object::Object, stardust::coin_kind::get_gas_balance_maybe, }; @@ -64,6 +65,11 @@ impl GenesisStake { /// inner token allocations. pub fn to_token_distribution_schedule(&self) -> TokenDistributionSchedule { let mut builder = TokenDistributionScheduleBuilder::new(); + + let pre_minted_supply = self.calculate_pre_minted_supply(); + + builder.set_pre_minted_supply(pre_minted_supply); + for allocation in self.token_allocation.clone() { builder.add_allocation(allocation); } @@ -83,10 +89,15 @@ impl GenesisStake { vanilla_schedule .allocations .extend(self.token_allocation.clone()); - vanilla_schedule.stake_subsidy_fund_nanos -= self.sum_token_allocation(); + vanilla_schedule.pre_minted_supply = self.calculate_pre_minted_supply(); vanilla_schedule.validate(); vanilla_schedule } + + /// Calculates the part of the IOTA supply that is pre-minted. + fn calculate_pre_minted_supply(&self) -> u64 { + TOTAL_SUPPLY_NANOS - self.sum_token_allocation() + } } /// The objects picked for token allocation during genesis