diff --git a/types/src/sector/mod.rs b/types/src/sector/mod.rs index 5887f18704b7..761925229267 100644 --- a/types/src/sector/mod.rs +++ b/types/src/sector/mod.rs @@ -23,6 +23,9 @@ pub type SectorNumber = u64; /// Unit of storage power (measured in bytes) pub type StoragePower = BigUint; +/// The unit of spacetime committed to the network +pub type Spacetime = BigUint; + pub type SectorQuality = BigUint; /// SectorSize indicates one of a set of possible sizes in the network. diff --git a/vm/actor/src/builtin/miner/mod.rs b/vm/actor/src/builtin/miner/mod.rs index b65c2136a066..9ee9bef2984f 100644 --- a/vm/actor/src/builtin/miner/mod.rs +++ b/vm/actor/src/builtin/miner/mod.rs @@ -15,7 +15,23 @@ use vm::{ActorError, ExitCode, MethodNum, Serialized, METHOD_CONSTRUCTOR}; #[repr(u64)] pub enum Method { Constructor = METHOD_CONSTRUCTOR, - // TODO include other methods on impl + ControlAddresses = 2, + ChangeWorkerAddress = 3, + ChangePeerID = 4, + SubmitWindowedPoSt = 5, + PreCommitSector = 6, + ProveCommitSector = 7, + ExtendSectorExpiration = 8, + TerminateSectors = 9, + DeclareFaults = 10, + DeclareFaultsRecovered = 11, + OnDeferredCronEvent = 12, + CheckSectorProven = 13, + AddLockedFund = 14, + ReportConsensusFault = 15, + WithdrawBalance = 16, + ConfirmSectorProofsValid = 17, + ChangeMultiaddrs = 18, } /// Miner Actor diff --git a/vm/actor/src/builtin/mod.rs b/vm/actor/src/builtin/mod.rs index a1e52b92f3c7..501c69e51a48 100644 --- a/vm/actor/src/builtin/mod.rs +++ b/vm/actor/src/builtin/mod.rs @@ -8,6 +8,7 @@ pub mod init; pub mod market; pub mod miner; pub mod multisig; +pub mod network; pub mod paych; pub mod power; pub mod reward; diff --git a/vm/actor/src/builtin/network.rs b/vm/actor/src/builtin/network.rs new file mode 100644 index 000000000000..de14517d3416 --- /dev/null +++ b/vm/actor/src/builtin/network.rs @@ -0,0 +1,13 @@ +// Copyright 2020 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +pub const EPOCH_DURATION_SECONDS: u64 = 25; +pub const SECONDS_IN_HOUR: u64 = 3600; +pub const SECONDS_IN_DAY: u64 = 86400; +pub const SECONDS_IN_YEAR: u64 = 31556925; +pub const EPOCHS_IN_HOUR: u64 = SECONDS_IN_HOUR / EPOCH_DURATION_SECONDS; +pub const EPOCHS_IN_DAY: u64 = SECONDS_IN_DAY / EPOCH_DURATION_SECONDS; +pub const EPOCHS_IN_YEAR: u64 = SECONDS_IN_YEAR / EPOCH_DURATION_SECONDS; + +/// The expected number of block producers in each epoch. +pub const EXPECTED_LEADERS_PER_EPOCH: u64 = 5; diff --git a/vm/actor/src/builtin/reward/mod.rs b/vm/actor/src/builtin/reward/mod.rs index 340e36dfb9f2..1093e590bb03 100644 --- a/vm/actor/src/builtin/reward/mod.rs +++ b/vm/actor/src/builtin/reward/mod.rs @@ -6,16 +6,24 @@ mod types; pub use self::state::{Reward, State, VestingFunction}; pub use self::types::*; -use crate::{check_empty_params, Multimap, BURNT_FUNDS_ACTOR_ADDR, SYSTEM_ACTOR_ADDR}; +use crate::network::EXPECTED_LEADERS_PER_EPOCH; +use crate::{ + check_empty_params, miner, BURNT_FUNDS_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, +}; +use clock::ChainEpoch; +use fil_types::StoragePower; use ipld_blockstore::BlockStore; -use num_bigint::biguint_ser::BigUintSer; +use num_bigint::biguint_ser::{BigUintDe, BigUintSer}; +use num_bigint::BigUint; use num_derive::FromPrimitive; -use num_traits::{FromPrimitive, Zero}; +use num_traits::{CheckedSub, FromPrimitive}; use runtime::{ActorCode, Runtime}; use vm::{ ActorError, ExitCode, MethodNum, Serialized, TokenAmount, METHOD_CONSTRUCTOR, METHOD_SEND, }; +// * Updated to specs-actors commit: 52599b21919df07f44d7e61cc028e265ec18f700 + /// Reward actor methods available #[derive(FromPrimitive)] #[repr(u64)] @@ -23,7 +31,7 @@ pub enum Method { Constructor = METHOD_CONSTRUCTOR, AwardBlockReward = 2, LastPerEpochReward = 3, - // TODO add UpdateNetworkKPI + UpdateNetworkKPI = 4, } /// Reward Actor @@ -37,18 +45,22 @@ impl Actor { { rt.validate_immediate_caller_is(std::iter::once(&*SYSTEM_ACTOR_ADDR))?; - let empty_root = Multimap::new(rt.store()).root().map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to construct state: {}", e), - ) - })?; + // TODO revisit based on issue: https://github.com/filecoin-project/specs-actors/issues/317 - rt.create(&State::new(empty_root))?; + rt.create(&State::new())?; Ok(()) } - /// Mints a reward and puts into state reward map + /// Awards a reward to a block producer. + /// This method is called only by the system actor, implicitly, as the last message in the evaluation of a block. + /// The system actor thus computes the parameters and attached value. + /// + /// The reward includes two components: + /// - the epoch block reward, computed and paid from the reward actor's balance, + /// - the block gas reward, expected to be transferred to the reward actor with this invocation. + /// + /// The reward is reduced before the residual is credited to the block producer, by: + /// - a penalty amount, provided as a parameter, which is burnt, fn award_block_reward( rt: &mut RT, params: AwardBlockRewardParams, @@ -59,66 +71,46 @@ impl Actor { { rt.validate_immediate_caller_is(std::iter::once(&*SYSTEM_ACTOR_ADDR))?; let balance = rt.current_balance()?; - if balance < params.gas_reward { - return Err(ActorError::new( - ExitCode::ErrInsufficientFunds, - format!( - "actor current balance {} insufficient to pay gas reward {}", - balance, params.gas_reward - ), - )); - } + assert!( + balance >= params.gas_reward, + "actor current balance {} insufficient to pay gas reward {}", + balance, + params.gas_reward + ); - if params.ticket_count == 0 { - return Err(ActorError::new( - ExitCode::ErrIllegalArgument, - "cannot give block reward for zero tickets".to_owned(), - )); - } + assert!( + params.ticket_count > 0, + "cannot give block reward for zero tickets" + ); + + let miner_addr = rt + .resolve_address(¶ms.miner) + // TODO revisit later if all address resolutions end up being the same exit code + .map_err(|e| ActorError::new(ExitCode::ErrIllegalState, e.msg().to_string()))?; + + let prior_balance = rt.current_balance()?; - let miner = rt.resolve_address(¶ms.miner)?; - - let prior_bal = rt.current_balance()?; - - let cur_epoch = rt.curr_epoch(); - let penalty: TokenAmount = rt - .transaction::<_, Result<_, String>, _>(|st: &mut State, rt| { - let block_rew = Self::compute_block_reward( - st, - &prior_bal - ¶ms.gas_reward, - params.ticket_count, - ); - let total_reward = block_rew + ¶ms.gas_reward; - - // Cap the penalty at the total reward value. - let penalty = std::cmp::min(params.penalty, total_reward.clone()); - // Reduce the payable reward by the penalty. - let rew_payable = total_reward - &penalty; - if (&rew_payable + &penalty) > prior_bal { - return Err(format!( - "reward payable {} + penalty {} exceeds balance {}", - rew_payable, penalty, prior_bal - )); - } - - // Record new reward into reward map. - if rew_payable > TokenAmount::zero() { - st.add_reward( - rt.store(), - &miner, - Reward { - start_epoch: cur_epoch, - end_epoch: cur_epoch + REWARD_VESTING_PERIOD, - value: rew_payable, - amount_withdrawn: TokenAmount::zero(), - vesting_function: REWARD_VESTING_FUNCTION, - }, - )?; - } - // - Ok(penalty) - })? - .map_err(|e| ActorError::new(ExitCode::ErrIllegalState, e))?; + let state: State = rt.state()?; + let block_reward = state.last_per_epoch_reward / EXPECTED_LEADERS_PER_EPOCH; + let total_reward = block_reward + params.gas_reward; + + // Cap the penalty at the total reward value. + let penalty = std::cmp::min(¶ms.penalty, &total_reward); + + // Reduce the payable reward by the penalty. + let reward_payable = total_reward.clone() - penalty; + + assert!( + reward_payable <= prior_balance - penalty, + "Total reward exceeds balance of actor" + ); + + rt.send( + &miner_addr, + miner::Method::AddLockedFund as u64, + &Serialized::serialize(&BigUintSer(&reward_payable)).unwrap(), + &reward_payable, + )?; // Burn the penalty rt.send( @@ -142,11 +134,64 @@ impl Actor { } /// Withdraw available funds from reward map - fn compute_block_reward(st: &State, balance: TokenAmount, ticket_count: u64) -> TokenAmount { - let treasury = balance - &st.reward_total; - let target_rew = BLOCK_REWARD_TARGET.clone() * ticket_count; + fn compute_per_epoch_reward(st: &mut State, _ticket_count: u64) -> TokenAmount { + // TODO update when finished in specs + let new_simple_supply = minting_function( + &SIMPLE_TOTAL, + &(BigUint::from(st.reward_epochs_paid) << MINTING_INPUT_FIXED_POINT), + ); + let new_baseline_supply = minting_function(&*BASELINE_TOTAL, &st.effective_network_time); + + let new_simple_minted = new_simple_supply + .checked_sub(&st.simple_supply) + .unwrap_or_default(); + let new_baseline_minted = new_baseline_supply + .checked_sub(&st.baseline_supply) + .unwrap_or_default(); + + st.simple_supply = new_simple_supply; + st.baseline_supply = new_baseline_supply; + + let per_epoch_reward = new_simple_minted + new_baseline_minted; + st.last_per_epoch_reward = per_epoch_reward.clone(); + per_epoch_reward + } - std::cmp::min(target_rew, treasury) + fn new_baseline_power(_st: &State, _reward_epochs_paid: ChainEpoch) -> StoragePower { + // TODO: this is not the final baseline function or value, PARAM_FINISH + BigUint::from(BASELINE_POWER) + } + + // Called at the end of each epoch by the power actor (in turn by its cron hook). + // This is only invoked for non-empty tipsets. The impact of this is that block rewards are paid out over + // a schedule defined by non-empty tipsets, not by elapsed time/epochs. + // This is not necessarily what we want, and may change. + fn update_network_kpi( + rt: &mut RT, + curr_realized_power: StoragePower, + ) -> Result<(), ActorError> + where + BS: BlockStore, + RT: Runtime, + { + rt.validate_immediate_caller_is(std::iter::once(&*STORAGE_POWER_ACTOR_ADDR))?; + + rt.transaction(|st: &mut State, _| { + // By the time this is called, the rewards for this epoch have been paid to miners. + st.reward_epochs_paid += 1; + st.realized_power = curr_realized_power; + + st.baseline_power = Self::new_baseline_power(st, st.reward_epochs_paid); + st.cumsum_baseline += &st.baseline_power; + + // Cap realized power in computing CumsumRealized so that progress is only relative to the current epoch. + let capped_realized_power = std::cmp::min(&st.baseline_power, &st.realized_power); + st.cumsum_realized += capped_realized_power; + st.effective_network_time = + st.get_effective_network_time(&st.cumsum_baseline, &st.cumsum_realized); + Self::compute_per_epoch_reward(st, 1); + })?; + Ok(()) } } @@ -175,6 +220,11 @@ impl ActorCode for Actor { let res = Self::last_per_epoch_reward(rt)?; Ok(Serialized::serialize(BigUintSer(&res))?) } + Some(Method::UpdateNetworkKPI) => { + let BigUintDe(param) = params.deserialize()?; + Self::update_network_kpi(rt, param)?; + Ok(Serialized::default()) + } _ => Err(rt.abort(ExitCode::SysErrInvalidMethod, "Invalid method")), } } diff --git a/vm/actor/src/builtin/reward/state.rs b/vm/actor/src/builtin/reward/state.rs index 67d74f427102..8718c6b26f8c 100644 --- a/vm/actor/src/builtin/reward/state.rs +++ b/vm/actor/src/builtin/reward/state.rs @@ -1,110 +1,59 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::Multimap; -use address::Address; -use cid::Cid; +use super::types::*; use clock::ChainEpoch; use encoding::{repr::*, tuple::*, Cbor}; -use ipld_blockstore::BlockStore; +use fil_types::{Spacetime, StoragePower}; use num_bigint::biguint_ser; use num_derive::FromPrimitive; -use num_traits::CheckedSub; use vm::TokenAmount; -// TODO update reward state /// Reward actor state -#[derive(Serialize_tuple, Deserialize_tuple)] +#[derive(Serialize_tuple, Deserialize_tuple, Default)] pub struct State { - /// Reward multimap indexing addresses. - pub reward_map: Cid, - /// Sum of un-withdrawn rewards. #[serde(with = "biguint_ser")] - pub reward_total: TokenAmount, - // The reward to be paid in total to block producers, if exactly the expected number of them produce a block. - // The actual reward total paid out depends on the number of winners in any round. - // This is computed at the end of the previous epoch, and should really be called ThisEpochReward. + pub baseline_power: StoragePower, #[serde(with = "biguint_ser")] - pub last_per_epoch_reward: TokenAmount, -} + pub realized_power: StoragePower, + #[serde(with = "biguint_ser")] + pub cumsum_baseline: Spacetime, + #[serde(with = "biguint_ser")] + pub cumsum_realized: Spacetime, + #[serde(with = "biguint_ser")] + pub effective_network_time: NetworkTime, -impl State { - pub fn new(empty_multimap: Cid) -> Self { - Self { - reward_map: empty_multimap, - reward_total: TokenAmount::default(), - last_per_epoch_reward: TokenAmount::default(), - } - } + #[serde(with = "biguint_ser")] + pub simple_supply: TokenAmount, + #[serde(with = "biguint_ser")] + pub baseline_supply: TokenAmount, - #[allow(dead_code)] - pub(super) fn add_reward( - &mut self, - store: &BS, - owner: &Address, - reward: Reward, - ) -> Result<(), String> { - let mut rewards = Multimap::from_root(store, &self.reward_map)?; - let value = reward.value.clone(); + /// The reward to be paid in total to block producers, if exactly the expected number of them produce a block. + /// The actual reward total paid out depends on the number of winners in any round. + /// This is computed at the end of the previous epoch, and should really be called ThisEpochReward. + #[serde(with = "biguint_ser")] + pub last_per_epoch_reward: TokenAmount, - rewards.add(owner.to_bytes().into(), reward)?; + /// The count of epochs for which a reward has been paid. + /// This should equal the number of non-empty tipsets after the genesis, aka "chain height". + pub reward_epochs_paid: ChainEpoch, +} - self.reward_map = rewards.root()?; - self.reward_total += value; - Ok(()) +impl State { + pub fn new() -> Self { + Self::default() } - /// Calculates and subtracts the total withdrawable reward for an owner. - #[allow(dead_code)] - pub(super) fn withdraw_reward( - &mut self, - store: &BS, - owner: &Address, - curr_epoch: ChainEpoch, - ) -> Result { - let mut rewards = Multimap::from_root(store, &self.reward_map)?; - let key = owner.to_bytes(); - - // Iterate rewards, accumulate total and remaining reward state - let mut remaining_rewards = Vec::new(); - let mut withdrawable_sum = TokenAmount::from(0u8); - rewards.for_each(&key, |_, reward: &Reward| { - let unlocked = reward.amount_vested(curr_epoch); - let withdrawable = unlocked - .checked_sub(&reward.amount_withdrawn) - .ok_or(format!( - "Unlocked amount {} less than amount withdrawn {} at epoch {}", - unlocked, reward.amount_withdrawn, curr_epoch - ))?; - - withdrawable_sum += withdrawable; - if unlocked < reward.value { - remaining_rewards.push(Reward { - vesting_function: reward.vesting_function, - start_epoch: reward.start_epoch, - end_epoch: reward.end_epoch, - value: reward.value.clone(), - amount_withdrawn: unlocked, - }); - } - Ok(()) - })?; - - assert!( - withdrawable_sum < self.reward_total, - "withdrawable amount cannot exceed previous total" - ); - - // Regenerate amt for multimap with updated rewards - rewards.remove_all(&key)?; - for rew in remaining_rewards { - rewards.add(key.clone().into(), rew)?; - } - - // Update rewards multimap root and total - self.reward_map = rewards.root()?; - self.reward_total -= &withdrawable_sum; - Ok(withdrawable_sum) + pub(super) fn get_effective_network_time( + &self, + _cumsum_baseline: &Spacetime, + cumsum_realized: &Spacetime, + ) -> NetworkTime { + // TODO: this function depends on the final baseline + // EffectiveNetworkTime is a fractional input with an implicit denominator of (2^MintingInputFixedPoint). + // realizedCumsum is thus left shifted by MintingInputFixedPoint before converted into a FixedPoint fraction + // through division (which is an inverse function for the integral of the baseline). + (cumsum_realized << MINTING_INPUT_FIXED_POINT) / BASELINE_POWER } } diff --git a/vm/actor/src/builtin/reward/types.rs b/vm/actor/src/builtin/reward/types.rs index 6d30cd5d9ce2..7d504a5e0a7a 100644 --- a/vm/actor/src/builtin/reward/types.rs +++ b/vm/actor/src/builtin/reward/types.rs @@ -1,25 +1,48 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use super::VestingFunction; +use crate::network::*; use address::Address; -use clock::ChainEpoch; use encoding::tuple::*; -use num_bigint::{biguint_ser, BigUint}; +use num_bigint::{biguint_ser, BigInt, BigUint, ToBigInt}; +use num_traits::{Pow, Zero}; +use std::ops::Neg; use vm::TokenAmount; +pub type NetworkTime = BigUint; + /// Number of token units in an abstract "FIL" token. /// The network works purely in the indivisible token amounts. This constant converts to a fixed decimal with more /// human-friendly scale. pub const TOKEN_PRECISION: u64 = 1_000_000_000_000_000_000; +/// Baseline power for the network +pub(super) const BASELINE_POWER: u64 = 1 << 50; // 1PiB for testnet, PARAM_FINISH + +/// Fixed-point precision (in bits) used for minting function's input "t" +pub(super) const MINTING_INPUT_FIXED_POINT: usize = 30; + +/// Fixed-point precision (in bits) used internally and for output +const MINTING_OUTPUT_FIXED_POINT: usize = 97; + lazy_static! { /// Target reward released to each block winner. pub static ref BLOCK_REWARD_TARGET: BigUint = BigUint::from(100u8) * TOKEN_PRECISION; -} -pub(super) const REWARD_VESTING_FUNCTION: VestingFunction = VestingFunction::None; -pub(super) const REWARD_VESTING_PERIOD: ChainEpoch = 0; + pub static ref LAMBDA_NUM: BigInt = BigInt::from(EPOCH_DURATION_SECONDS) * &*LN_TWO_NUM; + pub static ref LAMBDA_DEN: BigInt = BigInt::from(6*SECONDS_IN_YEAR) * &*LN_TWO_DEN; + + // These numbers are placeholders, but should be in units of attoFIL, 10^-18 FIL + /// 100M for testnet, PARAM_FINISH + pub static ref SIMPLE_TOTAL: BigInt = BigInt::from(100).pow(6u8) * BigInt::from(1).pow(18u8); + /// 900M for testnet, PARAM_FINISH + pub static ref BASELINE_TOTAL: BigInt = BigInt::from(900).pow(6u8) * BigInt::from(1).pow(18u8); + + // The following are the numerator and denominator of -ln(1/2)=ln(2), + // represented as a rational with sufficient precision. + pub static ref LN_TWO_NUM: BigInt = BigInt::from(6_931_471_805_599_453_094_172_321_215u128); + pub static ref LN_TWO_DEN: BigInt = BigInt::from(10_000_000_000_000_000_000_000_000_000u128); +} #[derive(Clone, Debug, PartialEq, Serialize_tuple, Deserialize_tuple)] pub struct AwardBlockRewardParams { @@ -30,3 +53,127 @@ pub struct AwardBlockRewardParams { pub gas_reward: TokenAmount, pub ticket_count: u64, } + +/// Minting Function: Taylor series expansion +/// +/// Intent +/// The intent of the following code is to compute the desired fraction of +/// coins that should have been minted at a given epoch according to the +/// simple exponential decay supply. This function is used both directly, +/// to compute simple minting, and indirectly, to compute baseline minting +/// by providing a synthetic "effective network time" instead of an actual +/// epoch number. The prose specification of the simple exponential decay is +/// that the unminted supply should decay exponentially with a half-life of +/// 6 years. +fn taylor_series_expansion(lambda_num: &BigInt, lambda_den: &BigInt, t: BigInt) -> BigInt { + // `numerator_base` is the numerator of the rational representation of (-λt). + let numerator_base = lambda_num.neg() * t; + + // The denominator of (-λt) is the denominator of λ times the denominator of t, + // which is a fixed 2^MintingInputFixedPoint. Multiplying by this is a left shift. + let denominator_base = lambda_den << MINTING_INPUT_FIXED_POINT; + + // `numerator` is the accumulator for numerators of the series terms. The + // first term is simply (-1)(-λt). To include that factor of (-1), which + // appears in every term, we introduce this negation into the numerator of + // the first term. (All terms will be negated by this, because each term is + // derived from the last by multiplying into it.) + let mut numerator = numerator_base.clone().neg(); + // `denominator` is the accumulator for denominators of the series terms. + let mut denominator = denominator_base.clone(); + + // `ret` is an _additive_ accumulator for partial sums of the series, and + // carries a _fixed-point_ representation rather than a rational + // representation. This just means it has an implicit denominator of + // 2^(FixedPoint). + let mut ret = BigInt::zero(); + + // The first term computed has order 1; the final term has order 24. + for n in 1..25 { + // Multiplying the denominator by `n` on every loop accounts for the + // `n!` (factorial) in the denominator of the series. + denominator *= n; + + // Left-shift and divide to convert rational into fixed-point. + let term = (numerator.clone() << MINTING_OUTPUT_FIXED_POINT) / &denominator; + + // Accumulate the fixed-point result into the return accumulator. + ret += term; + + // Multiply the rational representation of (-λt) into the term accumulators + // for the next iteration. Doing this here in the loop allows us to save a + // couple bigint operations by initializing numerator and denominator + // directly instead of multiplying by 1. + numerator *= &numerator_base; + denominator *= &denominator_base; + + // If the denominator has grown beyond the necessary precision, then we can + // truncate it by right-shifting. As long as we right-shift the numerator + // by the same number of bits, all we have done is lose unnecessary + // precision that would slow down the next iteration's multiplies. + let denominator_len = denominator.bits(); + let unnecessary_bits = denominator_len.saturating_sub(MINTING_OUTPUT_FIXED_POINT); + + numerator >>= unnecessary_bits; + denominator >>= unnecessary_bits; + } + + ret +} + +/// Minting Function Wrapper +/// +/// Intent +/// The necessary calling conventions for the function above are unwieldy: +/// the common case is to supply the canonical Lambda, multiply by some other +/// number, and right-shift down by MintingOutputFixedPoint. This convenience +/// wrapper implements those conventions. However, it does NOT implement +/// left-shifting the input by the MintingInputFixedPoint, because baseline +/// minting will actually supply a fractional input. +pub(super) fn minting_function(factor: &BigInt, t: &BigUint) -> BigUint { + let value = (factor + * taylor_series_expansion(&*LAMBDA_NUM, &*LAMBDA_DEN, t.to_bigint().unwrap())) + >> MINTING_OUTPUT_FIXED_POINT; + + // This conversion is safe because the minting function should always return a positive value + value.to_biguint().unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use super::*; + + const SECONDS_IN_YEAR: u64 = 31556925; + const TEST_EPOCH_DURATION_SECONDS: u64 = 30; + const MINTING_TEST_VECTOR_PRECISION: usize = 90; + + // Ported test from specs-actors + #[test] + fn minting_function_vectors() { + let test_lambda_num = BigInt::from(TEST_EPOCH_DURATION_SECONDS) * &*LN_TWO_NUM; + let test_lambda_den = BigInt::from(6 * SECONDS_IN_YEAR) * &*LN_TWO_DEN; + let vectors: &[(u64, &str)] = &[ + (1051897, "135060784589637453410950129"), + (2103794, "255386271058940593613485187"), + (3155691, "362584098600550296025821387"), + (4207588, "458086510989070493849325308"), + (5259485, "543169492437427724953202180"), + (6311382, "618969815707708523300124489"), + (7363279, "686500230252085183344830372"), + ]; + for v in vectors { + let ts_input = BigInt::from(v.0) << MINTING_INPUT_FIXED_POINT; + let ts_output = taylor_series_expansion(&test_lambda_num, &test_lambda_den, ts_input); + + let ts_truncated_fractional_part = + ts_output >> (MINTING_OUTPUT_FIXED_POINT - MINTING_TEST_VECTOR_PRECISION); + + let expected_truncated_fractional_part: BigInt = v.1.parse().unwrap(); + assert_eq!( + ts_truncated_fractional_part, expected_truncated_fractional_part, + "failed on input {}, computed: {}, expected: {}", + v.0, ts_truncated_fractional_part, expected_truncated_fractional_part + ); + } + } +} diff --git a/vm/actor/tests/reward_actor_test.rs b/vm/actor/tests/reward_actor_test.rs index ca051fdc7c40..e269c4b1f66c 100644 --- a/vm/actor/tests/reward_actor_test.rs +++ b/vm/actor/tests/reward_actor_test.rs @@ -12,7 +12,8 @@ use common::*; use db::MemoryDB; use ipld_blockstore::BlockStore; use message::UnsignedMessage; -use vm::{ExitCode, Serialized, TokenAmount, METHOD_CONSTRUCTOR}; +use std::panic; +use vm::{Serialized, TokenAmount, METHOD_CONSTRUCTOR}; fn construct_runtime(bs: &BS) -> MockRuntime<'_, BS> { let message = UnsignedMessage::builder() @@ -26,6 +27,7 @@ fn construct_runtime(bs: &BS) -> MockRuntime<'_, BS> { } #[test] +#[should_panic(expected = "actor current balance 0 insufficient to pay gas reward 10")] fn balance_less_than_reward() { let bs = MemoryDB::default(); let mut rt = construct_runtime(&bs); @@ -44,17 +46,12 @@ fn balance_less_than_reward() { }; //Expect call to fail because actor doesnt have enough tokens to reward - let call_result = rt.call( + let _res = rt.call( &*REWARD_ACTOR_CODE_ID, Method::AwardBlockReward as u64, &Serialized::serialize(¶ms).unwrap(), ); - assert_eq!( - ExitCode::ErrInsufficientFunds, - call_result.unwrap_err().exit_code() - ); - rt.verify() }