use fil_actors_runtime::runtime::{ActorCode, Runtime};
use fil_actors_runtime::{
actor_error, cbor, ActorError, BURNT_FUNDS_ACTOR_ADDR, EXPECTED_LEADERS_PER_EPOCH,
STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR,
};
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::RawBytes;
use fvm_shared::address::Address;
use fvm_shared::bigint::bigint_ser::BigIntDe;
use fvm_shared::econ::TokenAmount;
use fvm_shared::sector::StoragePower;
use fvm_shared::{MethodNum, METHOD_CONSTRUCTOR, METHOD_SEND};
use log::{error, warn};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
pub use self::logic::*;
pub use self::state::{Reward, State, VestingFunction};
pub use self::types::*;
#[cfg(feature = "fil-actor")]
fil_actors_runtime::wasm_trampoline!(Actor);
pub(crate) mod expneg;
mod logic;
mod state;
pub mod testing;
mod types;
#[doc(hidden)]
pub mod ext;
pub const PENALTY_MULTIPLIER: u64 = 3;
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum Method {
Constructor = METHOD_CONSTRUCTOR,
AwardBlockReward = 2,
ThisEpochReward = 3,
UpdateNetworkKPI = 4,
}
pub struct Actor;
impl Actor {
fn constructor<BS, RT>(
rt: &mut RT,
curr_realized_power: Option<StoragePower>,
) -> Result<(), ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?;
if let Some(power) = curr_realized_power {
rt.create(&State::new(power))?;
Ok(())
} else {
Err(actor_error!(illegal_argument, "argument should not be nil"))
}
}
fn award_block_reward<BS, RT>(
rt: &mut RT,
params: AwardBlockRewardParams,
) -> Result<(), ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?;
let prior_balance = rt.current_balance();
if params.penalty.is_negative() {
return Err(actor_error!(illegal_argument, "negative penalty {}", params.penalty));
}
if params.gas_reward.is_negative() {
return Err(actor_error!(
illegal_argument,
"negative gas reward {}",
params.gas_reward
));
}
if prior_balance < params.gas_reward {
return Err(actor_error!(
illegal_state,
"actor current balance {} insufficient to pay gas reward {}",
prior_balance,
params.gas_reward
));
}
if params.win_count <= 0 {
return Err(actor_error!(illegal_argument, "invalid win count {}", params.win_count));
}
let miner_id = rt
.resolve_address(¶ms.miner)
.ok_or_else(|| actor_error!(not_found, "failed to resolve given owner address"))?;
let penalty: TokenAmount = ¶ms.penalty * PENALTY_MULTIPLIER;
let total_reward = rt.transaction(|st: &mut State, rt| {
let mut block_reward: TokenAmount =
(&st.this_epoch_reward * params.win_count).div_floor(EXPECTED_LEADERS_PER_EPOCH);
let mut total_reward = ¶ms.gas_reward + &block_reward;
let curr_balance = rt.current_balance();
if total_reward > curr_balance {
warn!(
"reward actor balance {} below totalReward expected {},\
paying out rest of balance",
curr_balance, total_reward
);
total_reward = curr_balance;
block_reward = &total_reward - ¶ms.gas_reward;
if block_reward.is_negative() {
return Err(actor_error!(
illegal_state,
"programming error, block reward {} below zero",
block_reward
));
}
}
st.total_storage_power_reward += block_reward;
Ok(total_reward)
})?;
if total_reward > prior_balance {
return Err(actor_error!(
illegal_state,
"reward {} exceeds balance {}",
total_reward,
prior_balance
));
}
let reward_params = ext::miner::ApplyRewardParams { reward: total_reward.clone(), penalty };
let res = rt.send(
&Address::new_id(miner_id),
ext::miner::APPLY_REWARDS_METHOD,
RawBytes::serialize(&reward_params)?,
total_reward.clone(),
);
if let Err(e) = res {
error!(
"failed to send ApplyRewards call to the miner actor with funds {}, code: {:?}",
total_reward,
e.exit_code()
);
let res =
rt.send(&BURNT_FUNDS_ACTOR_ADDR, METHOD_SEND, RawBytes::default(), total_reward);
if let Err(e) = res {
error!(
"failed to send unsent reward to the burnt funds actor, code: {:?}",
e.exit_code()
);
}
}
Ok(())
}
fn this_epoch_reward<BS, RT>(rt: &mut RT) -> Result<ThisEpochRewardReturn, ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
let st: State = rt.state()?;
Ok(ThisEpochRewardReturn {
this_epoch_baseline_power: st.this_epoch_baseline_power,
this_epoch_reward_smoothed: st.this_epoch_reward_smoothed,
})
}
fn update_network_kpi<BS, RT>(
rt: &mut RT,
curr_realized_power: Option<StoragePower>,
) -> Result<(), ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(std::iter::once(&STORAGE_POWER_ACTOR_ADDR))?;
let curr_realized_power = curr_realized_power
.ok_or_else(|| actor_error!(illegal_argument, "argument cannot be None"))?;
rt.transaction(|st: &mut State, rt| {
let prev = st.epoch;
while st.epoch < rt.curr_epoch() {
st.update_to_next_epoch(&curr_realized_power);
}
st.update_to_next_epoch_with_reward(&curr_realized_power);
st.update_smoothed_estimates(st.epoch - prev);
Ok(())
})?;
Ok(())
}
}
impl ActorCode for Actor {
fn invoke_method<BS, RT>(
rt: &mut RT,
method: MethodNum,
params: &RawBytes,
) -> Result<RawBytes, ActorError>
where
BS: Blockstore,
RT: Runtime<BS>,
{
match FromPrimitive::from_u64(method) {
Some(Method::Constructor) => {
let param: Option<BigIntDe> = cbor::deserialize_params(params)?;
Self::constructor(rt, param.map(|v| v.0))?;
Ok(RawBytes::default())
}
Some(Method::AwardBlockReward) => {
Self::award_block_reward(rt, cbor::deserialize_params(params)?)?;
Ok(RawBytes::default())
}
Some(Method::ThisEpochReward) => {
let res = Self::this_epoch_reward(rt)?;
Ok(RawBytes::serialize(&res)?)
}
Some(Method::UpdateNetworkKPI) => {
let param: Option<BigIntDe> = cbor::deserialize_params(params)?;
Self::update_network_kpi(rt, param.map(|v| v.0))?;
Ok(RawBytes::default())
}
None => Err(actor_error!(unhandled_message, "Invalid method")),
}
}
}