Skip to content

Commit

Permalink
Merge pull request #1052 from iotaledger/sc-platform/issue-153
Browse files Browse the repository at this point in the history
feat: Staking reward generation through inflation
  • Loading branch information
lzpap authored Jul 23, 2024
2 parents 9cdfe10 + da19bbd commit d7da3bd
Show file tree
Hide file tree
Showing 46 changed files with 1,548 additions and 663 deletions.
91 changes: 44 additions & 47 deletions crates/iota-config/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use iota_types::{
deny_list::{get_coin_deny_list, PerTypeDenyList},
effects::{TransactionEffects, TransactionEvents},
error::IotaResult,
gas_coin::TOTAL_SUPPLY_NANOS,
iota_system_state::{
get_iota_system_state, get_iota_system_state_wrapper, IotaSystemState,
IotaSystemStateTrait, IotaSystemStateWrapper, IotaValidatorGenesis,
Expand Down Expand Up @@ -380,7 +379,7 @@ pub struct GenesisCeremonyParameters {
pub epoch_duration_ms: u64,

/// The starting epoch in which stake subsidies start being paid out.
#[serde(default)]
#[serde(default = "GenesisCeremonyParameters::default_stake_subsidy_start_epoch")]
pub stake_subsidy_start_epoch: u64,

/// The amount of stake subsidy to be drawn down per distribution.
Expand All @@ -407,7 +406,7 @@ impl GenesisCeremonyParameters {
chain_start_timestamp_ms: Self::default_timestamp_ms(),
protocol_version: ProtocolVersion::MAX,
allow_insertion_of_extra_objects: true,
stake_subsidy_start_epoch: 0,
stake_subsidy_start_epoch: Self::default_stake_subsidy_start_epoch(),
epoch_duration_ms: Self::default_epoch_duration_ms(),
stake_subsidy_initial_distribution_amount:
Self::default_initial_stake_subsidy_distribution_amount(),
Expand All @@ -432,19 +431,28 @@ impl GenesisCeremonyParameters {
24 * 60 * 60 * 1000
}

fn default_stake_subsidy_start_epoch() -> u64 {
// Set to highest possible value so that the stake subsidy fund never pays out
// rewards.
u64::MAX
}

fn default_initial_stake_subsidy_distribution_amount() -> u64 {
// 1M Iota
1_000_000 * iota_types::gas_coin::NANOS_PER_IOTA
// 0 IOTA in nanos
0
}

fn default_stake_subsidy_period_length() -> u64 {
// 10 distributions or epochs
10
// Set to highest possible value so that the "decrease stake subsidy amount"
// code path is never entered which makes it easier to reason about the
// stake subsidy fund.
u64::MAX
}

fn default_stake_subsidy_decrease_rate() -> u16 {
// 10% in basis points
1000
// Due to how stake_subsidy_period_length is set, this values is not important,
// since the distribution amount is never decreased.
0
}

pub fn to_genesis_chain_parameters(&self) -> GenesisChainParameters {
Expand Down Expand Up @@ -478,22 +486,18 @@ impl Default for GenesisCeremonyParameters {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct TokenDistributionSchedule {
pub stake_subsidy_fund_nanos: u64,
pub pre_minted_supply: u64,
pub allocations: Vec<TokenAllocation>,
}

impl TokenDistributionSchedule {
pub fn validate(&self) {
let mut total_nanos = self.stake_subsidy_fund_nanos;
let mut total_nanos = self.pre_minted_supply;

for allocation in &self.allocations {
total_nanos += allocation.amount_nanos;
}

if total_nanos != TOTAL_SUPPLY_NANOS {
panic!(
"TokenDistributionSchedule adds up to {total_nanos} and not expected {TOTAL_SUPPLY_NANOS}"
);
total_nanos = total_nanos
.checked_add(allocation.amount_nanos)
.expect("TokenDistributionSchedule allocates more than the maximum supply which equals u64::MAX", );
}
}

Expand Down Expand Up @@ -532,24 +536,20 @@ impl TokenDistributionSchedule {
pub fn new_for_validators_with_default_allocation<I: IntoIterator<Item = IotaAddress>>(
validators: I,
) -> Self {
let mut supply = TOTAL_SUPPLY_NANOS;
let default_allocation = iota_types::governance::VALIDATOR_LOW_STAKE_THRESHOLD_NANOS;

let allocations = validators
.into_iter()
.map(|a| {
supply -= default_allocation;
TokenAllocation {
recipient_address: a,
amount_nanos: default_allocation,
staked_with_validator: Some(a),
staked_with_timelock_expiration: None,
}
.map(|a| TokenAllocation {
recipient_address: a,
amount_nanos: default_allocation,
staked_with_validator: Some(a),
staked_with_timelock_expiration: None,
})
.collect();

let schedule = Self {
stake_subsidy_fund_nanos: supply,
pre_minted_supply: 0,
allocations,
};

Expand All @@ -563,33 +563,27 @@ impl TokenDistributionSchedule {
/// denote the allocation to the stake subsidy fund. It must be in the
/// following format:
/// `0x0000000000000000000000000000000000000000000000000000000000000000,
/// <amount to stake subsidy fund>,`
/// <pre>minted supply</pre>,`
///
/// All entries in a token distribution schedule must add up to 10B Iota.
pub fn from_csv<R: std::io::Read>(reader: R) -> Result<Self> {
let mut reader = csv::Reader::from_reader(reader);
let mut allocations: Vec<TokenAllocation> =
reader.deserialize().collect::<Result<_, _>>()?;
assert_eq!(
TOTAL_SUPPLY_NANOS,
allocations.iter().map(|a| a.amount_nanos).sum::<u64>(),
"Token Distribution Schedule must add up to 10B Iota",
);
let stake_subsidy_fund_allocation = allocations.pop().unwrap();

let pre_minted_supply = allocations.pop().unwrap();
assert_eq!(
IotaAddress::default(),
stake_subsidy_fund_allocation.recipient_address,
"Final allocation must be for stake subsidy fund",
pre_minted_supply.recipient_address,
"Final allocation must be for the pre-minted supply amount",
);
assert!(
stake_subsidy_fund_allocation
.staked_with_validator
.is_none(),
"Can't stake the stake subsidy fund",
pre_minted_supply.staked_with_validator.is_none(),
"Can't stake the pre-minted supply amount",
);

let schedule = Self {
stake_subsidy_fund_nanos: stake_subsidy_fund_allocation.amount_nanos,
pre_minted_supply: pre_minted_supply.amount_nanos,
allocations,
};

Expand All @@ -606,7 +600,7 @@ impl TokenDistributionSchedule {

writer.serialize(TokenAllocation {
recipient_address: IotaAddress::default(),
amount_nanos: self.stake_subsidy_fund_nanos,
amount_nanos: self.pre_minted_supply,
staked_with_validator: None,
staked_with_timelock_expiration: None,
})?;
Expand All @@ -631,19 +625,23 @@ pub struct TokenAllocation {

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenDistributionScheduleBuilder {
pool: u64,
pre_minted_supply: u64,
allocations: Vec<TokenAllocation>,
}

impl TokenDistributionScheduleBuilder {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
pool: TOTAL_SUPPLY_NANOS,
pre_minted_supply: 0,
allocations: vec![],
}
}

pub fn set_pre_minted_supply(&mut self, pre_minted_supply: u64) {
self.pre_minted_supply = pre_minted_supply;
}

pub fn default_allocation_for_validators<I: IntoIterator<Item = IotaAddress>>(
&mut self,
validators: I,
Expand All @@ -661,13 +659,12 @@ impl TokenDistributionScheduleBuilder {
}

pub fn add_allocation(&mut self, allocation: TokenAllocation) {
self.pool = self.pool.checked_sub(allocation.amount_nanos).unwrap();
self.allocations.push(allocation);
}

pub fn build(&self) -> TokenDistributionSchedule {
let schedule = TokenDistributionSchedule {
stake_subsidy_fund_nanos: self.pool,
pre_minted_supply: self.pre_minted_supply,
allocations: self.allocations.clone(),
};

Expand Down
51 changes: 29 additions & 22 deletions crates/iota-core/src/authority/authority_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1781,28 +1781,35 @@ impl AuthorityStore {
.expect("DB write cannot fail");
}

if let Some(expected_iota) = self
.perpetual_tables
.expected_network_iota_amount
.get(&())
.expect("DB read cannot fail")
{
fp_ensure!(
total_iota == expected_iota,
IotaError::from(
format!(
"Inconsistent state detected at epoch {}: total iota: {}, expecting {}",
system_state.epoch, total_iota, expected_iota
)
.as_str()
)
);
} else {
self.perpetual_tables
.expected_network_iota_amount
.insert(&(), &total_iota)
.expect("DB write cannot fail");
}
// TODO: Temporarily disabled since the inflation/deflation tokenomics changes
// violate this invariant. We need a deeper investigation whether we
// want to keep this check in some form or another. For instance, we
// could consider checking that the supply changes by at most X tokens
// per epoch (where X = validator_target_reward), but it's unclear whether that
// would really have any benefit.

// if let Some(expected_iota) = self
// .perpetual_tables
// .expected_network_iota_amount
// .get(&())
// .expect("DB read cannot fail")
// {
// fp_ensure!(
// total_iota == expected_iota,
// IotaError::from(
// format!(
// "Inconsistent state detected at epoch {}: total iota: {}, expecting {}",
// system_state.epoch, total_iota, expected_iota
// )
// .as_str()
// )
// );
// } else {
// self.perpetual_tables
// .expected_network_iota_amount
// .insert(&(), &total_iota)
// .expect("DB write cannot fail");
// }

Ok(())
}
Expand Down
20 changes: 12 additions & 8 deletions crates/iota-core/src/unit_tests/gas_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ async fn test_oog_computation_storage_ok_one_coin() -> IotaResult {
summary.computation_cost > 0
&& summary.storage_cost > 0
&& summary.storage_rebate > 0
&& summary.non_refundable_storage_fee > 0
&& summary.non_refundable_storage_fee == 0
);
assert!(initial_value - gas_used == final_value);
Ok(())
Expand Down Expand Up @@ -341,7 +341,7 @@ async fn test_oog_computation_storage_ok_multi_coins() -> IotaResult {
summary.computation_cost > 0
&& summary.storage_cost > 0
&& summary.storage_rebate > 0
&& summary.non_refundable_storage_fee > 0
&& summary.non_refundable_storage_fee == 0
);
assert!(initial_value - gas_used == final_value);
Ok(())
Expand All @@ -352,7 +352,9 @@ async fn test_oog_computation_storage_ok_multi_coins() -> IotaResult {

// OOG for computation, OOG for minimal storage (e.g. computation is entire
// budget)
// TODO: after fixes of #1206, should be unignore and fixed/removed.
#[tokio::test]
#[ignore]
async fn test_oog_computation_oog_storage_final_one_coin() -> IotaResult {
const GAS_PRICE: u64 = 1000;
const MAX_UNIT_BUDGET: u64 = 5_000_000;
Expand All @@ -369,8 +371,8 @@ async fn test_oog_computation_oog_storage_final_one_coin() -> IotaResult {
|summary, initial_value, final_value| {
let gas_used = summary.net_gas_usage() as u64;
assert!(summary.computation_cost > 0);
// currently when storage charges go out of gas, the storage data in the summary
// is zero
// currently when storage charges go out of gas, the storage data
// in the summary is zero
assert_eq!(summary.storage_cost, 0);
assert_eq!(summary.storage_rebate, 0);
assert_eq!(summary.non_refundable_storage_fee, 0);
Expand Down Expand Up @@ -404,7 +406,7 @@ async fn test_computation_ok_oog_storage_minimal_ok_one_coin() -> IotaResult {
summary.computation_cost > 0
&& summary.storage_cost > 0
&& summary.storage_rebate > 0
&& summary.non_refundable_storage_fee > 0
&& summary.non_refundable_storage_fee == 0
);
assert_eq!(initial_value - gas_used, final_value);
Ok(())
Expand Down Expand Up @@ -436,7 +438,7 @@ async fn test_computation_ok_oog_storage_minimal_ok_multi_coins() -> IotaResult
summary.computation_cost > 0
&& summary.storage_cost > 0
&& summary.storage_rebate > 0
&& summary.non_refundable_storage_fee > 0
&& summary.non_refundable_storage_fee == 0
);
assert_eq!(initial_value as i64 - gas_used, final_value as i64);
Ok(())
Expand All @@ -447,7 +449,9 @@ async fn test_computation_ok_oog_storage_minimal_ok_multi_coins() -> IotaResult

// - computation ok, OOG for storage, OOG for minimal storage (e.g. computation
// is entire budget)
// TODO: after fixes of #1206, should be unignore and fixed/removed.
#[tokio::test]
#[ignore]
async fn test_computation_ok_oog_storage_final_one_coin() -> IotaResult {
const GAS_PRICE: u64 = 1001;
const BUDGET: u64 = 1_002_000;
Expand All @@ -466,8 +470,8 @@ async fn test_computation_ok_oog_storage_final_one_coin() -> IotaResult {
|summary, initial_value, final_value| {
let gas_used = summary.net_gas_usage() as u64;
assert!(summary.computation_cost > 0);
// currently when storage charges go out of gas, the storage data in the summary
// is zero
// currently when storage charges go out of gas, the storage data
// in the summary is zero
assert_eq!(summary.storage_cost, 0);
assert_eq!(summary.storage_rebate, 0);
assert_eq!(summary.non_refundable_storage_fee, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ expression: common_costs_actual
"SharedCounterAssertValue": {
"computationCost": "1000000",
"storageCost": "2591600",
"storageRebate": "1587564",
"nonRefundableStorageFee": "16036"
"storageRebate": "1603600",
"nonRefundableStorageFee": "0"
},
"SharedCounterCreate": {
"computationCost": "1000000",
Expand All @@ -30,8 +30,8 @@ expression: common_costs_actual
"SharedCounterIncrement": {
"computationCost": "1000000",
"storageCost": "2591600",
"storageRebate": "1587564",
"nonRefundableStorageFee": "16036"
"storageRebate": "1603600",
"nonRefundableStorageFee": "0"
},
"SplitCoin": {
"computationCost": "1000000",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ module iota_system::genesis {
}

struct TokenDistributionSchedule has drop {
stake_subsidy_fund_nanos: u64,
pre_minted_supply: u64,
allocations: vector<TokenAllocation>,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ module iota_system::genesis {
}

struct TokenDistributionSchedule has drop {
stake_subsidy_fund_nanos: u64,
pre_minted_supply: u64,
allocations: vector<TokenAllocation>,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ module iota_system::genesis {
}

struct TokenDistributionSchedule has drop {
stake_subsidy_fund_nanos: u64,
pre_minted_supply: u64,
allocations: vector<TokenAllocation>,
}

Expand Down
Loading

0 comments on commit d7da3bd

Please sign in to comment.