1use std::sync::Arc;
5
6use crate::chain::*;
7use crate::networks::{ChainConfig, Height};
8use crate::rpc::types::CirculatingSupply;
9use crate::shim::actors::{
10 MarketActorStateLoad as _, MinerActorStateLoad as _, MultisigActorStateLoad as _,
11 PowerActorStateLoad as _, is_account_actor, is_ethaccount_actor, is_evm_actor, is_miner_actor,
12 is_multisig_actor, is_paymentchannel_actor, is_placeholder_actor,
13};
14use crate::shim::actors::{market, miner, multisig, power, reward};
15use crate::shim::version::NetworkVersion;
16use crate::shim::{
17 address::Address,
18 clock::{ChainEpoch, EPOCHS_IN_DAY},
19 econ::{TOTAL_FILECOIN, TokenAmount},
20 state_tree::{ActorState, StateTree},
21};
22use anyhow::{Context as _, bail};
23use cid::Cid;
24use fvm_ipld_blockstore::Blockstore;
25use num_traits::Zero;
26
27const EPOCHS_IN_YEAR: ChainEpoch = 365 * EPOCHS_IN_DAY;
28const PRE_CALICO_VESTING: [(ChainEpoch, usize); 5] = [
29 (183 * EPOCHS_IN_DAY, 82_717_041),
30 (EPOCHS_IN_YEAR, 22_421_712),
31 (2 * EPOCHS_IN_YEAR, 7_223_364),
32 (3 * EPOCHS_IN_YEAR, 87_637_883),
33 (6 * EPOCHS_IN_YEAR, 400_000_000),
34];
35const CALICO_VESTING: [(ChainEpoch, usize); 6] = [
36 (0, 10_632_000),
37 (183 * EPOCHS_IN_DAY, 19_015_887 + 32_787_700),
38 (EPOCHS_IN_YEAR, 22_421_712 + 9_400_000),
39 (2 * EPOCHS_IN_YEAR, 7_223_364),
40 (3 * EPOCHS_IN_YEAR, 87_637_883 + 898_958),
41 (6 * EPOCHS_IN_YEAR, 100_000_000 + 300_000_000 + 9_805_053),
42];
43
44#[derive(Default, Clone)]
46pub struct GenesisInfo {
47 vesting: GenesisInfoVesting,
48
49 genesis_pledge: TokenAmount,
51 genesis_market_funds: TokenAmount,
52
53 chain_config: Arc<ChainConfig>,
54}
55
56impl GenesisInfo {
57 pub fn from_chain_config(chain_config: Arc<ChainConfig>) -> Self {
58 let liftoff_height = chain_config.epoch(Height::Liftoff);
59 Self {
60 vesting: GenesisInfoVesting::new(liftoff_height),
61 chain_config,
62 ..GenesisInfo::default()
63 }
64 }
65
66 pub fn get_vm_circulating_supply<DB: Blockstore>(
72 &self,
73 height: ChainEpoch,
74 db: &Arc<DB>,
75 root: &Cid,
76 ) -> Result<TokenAmount, anyhow::Error> {
77 let detailed = self.get_vm_circulating_supply_detailed(height, db, root)?;
78
79 Ok(detailed.fil_circulating)
80 }
81
82 pub fn get_vm_circulating_supply_detailed<DB: Blockstore>(
85 &self,
86 height: ChainEpoch,
87 db: &Arc<DB>,
88 root: &Cid,
89 ) -> anyhow::Result<CirculatingSupply> {
90 let state_tree = StateTree::new_from_root(Arc::clone(db), root)?;
91
92 let fil_vested = get_fil_vested(self, height);
93 let fil_mined = get_fil_mined(&state_tree)?;
94 let fil_burnt = get_fil_burnt(&state_tree)?;
95
96 let network_version = self.chain_config.network_version(height);
97 let fil_locked = get_fil_locked(&state_tree, network_version)?;
98 let fil_reserve_disbursed = if height > self.chain_config.epoch(Height::Assembly) {
99 get_fil_reserve_disbursed(&self.chain_config, height, &state_tree)?
100 } else {
101 TokenAmount::default()
102 };
103 let fil_circulating = TokenAmount::max(
104 &fil_vested + &fil_mined + &fil_reserve_disbursed - &fil_burnt - &fil_locked,
105 TokenAmount::default(),
106 );
107 Ok(CirculatingSupply {
108 fil_vested,
109 fil_mined,
110 fil_burnt,
111 fil_locked,
112 fil_circulating,
113 fil_reserve_disbursed,
114 })
115 }
116
117 pub fn get_state_circulating_supply<DB: Blockstore>(
123 &self,
124 height: ChainEpoch,
125 db: &Arc<DB>,
126 root: &Cid,
127 ) -> Result<TokenAmount, anyhow::Error> {
128 let mut circ = TokenAmount::default();
129 let mut un_circ = TokenAmount::default();
130
131 let state_tree = StateTree::new_from_root(Arc::clone(db), root)?;
132
133 state_tree.for_each(|addr: Address, actor: &ActorState| {
134 let actor_balance = TokenAmount::from(actor.balance.clone());
135 if !actor_balance.is_zero() {
136 match addr {
137 Address::INIT_ACTOR
138 | Address::REWARD_ACTOR
139 | Address::VERIFIED_REGISTRY_ACTOR
140 | Address::POWER_ACTOR
142 | Address::SYSTEM_ACTOR
143 | Address::CRON_ACTOR
144 | Address::BURNT_FUNDS_ACTOR
145 | Address::SAFT_ACTOR
146 | Address::RESERVE_ACTOR
147 | Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR => {
148 un_circ += actor_balance;
149 }
150 Address::MARKET_ACTOR => {
151 let network_version = self.chain_config.network_version(height);
152 if network_version >= NetworkVersion::V23 {
153 circ += actor_balance;
154 } else {
155 let ms = market::State::load(&db, actor.code, actor.state)?;
156 let locked_balance: TokenAmount = ms.total_locked().into();
157 circ += actor_balance - &locked_balance;
158 un_circ += locked_balance;
159 }
160 }
161 _ if is_account_actor(&actor.code)
162 || is_paymentchannel_actor(&actor.code)
163 || is_ethaccount_actor(&actor.code)
164 || is_evm_actor(&actor.code)
165 || is_placeholder_actor(&actor.code) => {
166 circ += actor_balance;
167 },
168 _ if is_miner_actor(&actor.code) => {
169 let ms = miner::State::load(&db, actor.code, actor.state)?;
170
171 if let Ok(avail_balance) = ms.available_balance(actor.balance.atto()) {
172 let avail_balance = TokenAmount::from(avail_balance);
173 circ += avail_balance.clone();
174 un_circ += actor_balance.clone() - &avail_balance;
175 } else {
176 un_circ += actor_balance;
179 }
180 }
181 _ if is_multisig_actor(&actor.code) => {
182 let ms = multisig::State::load(&db, actor.code, actor.state)?;
183
184 let locked_balance: TokenAmount = ms.locked_balance(height)?.into();
185 let avail_balance = actor_balance.clone() - &locked_balance;
186 circ += avail_balance.max(TokenAmount::zero());
187 un_circ += actor_balance.min(locked_balance);
188 }
189 _ => bail!("unexpected actor: {:?}", actor),
190 }
191 } else {
192 }
194 Ok(())
195 })?;
196
197 let total = circ.clone() + un_circ;
198 if total != *TOTAL_FILECOIN {
199 bail!(
200 "total filecoin didn't add to expected amount: {} != {}",
201 total,
202 *TOTAL_FILECOIN
203 );
204 }
205
206 Ok(circ)
207 }
208}
209
210#[derive(Default, Clone)]
213struct GenesisInfoVesting {
214 genesis: Vec<(ChainEpoch, TokenAmount)>,
215 ignition: Vec<(ChainEpoch, ChainEpoch, TokenAmount)>,
216 calico: Vec<(ChainEpoch, ChainEpoch, TokenAmount)>,
217}
218
219impl GenesisInfoVesting {
220 fn new(liftoff_height: i64) -> Self {
221 Self {
222 genesis: setup_genesis_vesting_schedule(),
223 ignition: setup_ignition_vesting_schedule(liftoff_height),
224 calico: setup_calico_vesting_schedule(liftoff_height),
225 }
226 }
227}
228
229fn get_actor_state<DB: Blockstore>(
230 state_tree: &StateTree<DB>,
231 addr: &Address,
232) -> Result<ActorState, anyhow::Error> {
233 state_tree
234 .get_actor(addr)?
235 .with_context(|| format!("Failed to get Actor for address {addr}"))
236}
237
238fn get_fil_vested(genesis_info: &GenesisInfo, height: ChainEpoch) -> TokenAmount {
239 let mut return_value = TokenAmount::default();
240
241 let pre_ignition = &genesis_info.vesting.genesis;
242 let post_ignition = &genesis_info.vesting.ignition;
243 let calico_vesting = &genesis_info.vesting.calico;
244
245 if height <= genesis_info.chain_config.epoch(Height::Ignition) {
246 for (unlock_duration, initial_balance) in pre_ignition {
247 return_value +=
248 initial_balance - v0_amount_locked(*unlock_duration, initial_balance, height);
249 }
250 } else if height <= genesis_info.chain_config.epoch(Height::Calico) {
251 for (start_epoch, unlock_duration, initial_balance) in post_ignition {
252 return_value += initial_balance
253 - v0_amount_locked(*unlock_duration, initial_balance, height - start_epoch);
254 }
255 } else {
256 for (start_epoch, unlock_duration, initial_balance) in calico_vesting {
257 return_value += initial_balance
258 - v0_amount_locked(*unlock_duration, initial_balance, height - start_epoch);
259 }
260 }
261
262 if height <= genesis_info.chain_config.epoch(Height::Assembly) {
263 return_value += &genesis_info.genesis_pledge + &genesis_info.genesis_market_funds;
264 }
265
266 return_value
267}
268
269fn get_fil_mined<DB: Blockstore>(state_tree: &StateTree<DB>) -> Result<TokenAmount, anyhow::Error> {
270 let state: reward::State = state_tree.get_actor_state()?;
271 Ok(state.into_total_storage_power_reward().into())
272}
273
274fn get_fil_market_locked<DB: Blockstore>(
275 state_tree: &StateTree<DB>,
276) -> Result<TokenAmount, anyhow::Error> {
277 let actor = state_tree
278 .get_actor(&Address::MARKET_ACTOR)?
279 .ok_or_else(|| Error::state("Market actor address could not be resolved"))?;
280 let state = market::State::load(state_tree.store(), actor.code, actor.state)?;
281
282 Ok(state.total_locked().into())
283}
284
285fn get_fil_power_locked<DB: Blockstore>(
286 state_tree: &StateTree<DB>,
287) -> Result<TokenAmount, anyhow::Error> {
288 let actor = state_tree
289 .get_actor(&Address::POWER_ACTOR)?
290 .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
291 let state = power::State::load(state_tree.store(), actor.code, actor.state)?;
292
293 Ok(state.into_total_locked().into())
294}
295
296fn get_fil_reserve_disbursed<DB: Blockstore>(
297 chain_config: &ChainConfig,
298 height: ChainEpoch,
299 state_tree: &StateTree<DB>,
300) -> Result<TokenAmount, anyhow::Error> {
301 let fil_reserved = chain_config.initial_fil_reserved_at_height(height);
305 let reserve_actor = get_actor_state(state_tree, &Address::RESERVE_ACTOR)?;
306
307 Ok(fil_reserved - TokenAmount::from(&reserve_actor.balance))
309}
310
311fn get_fil_locked<DB: Blockstore>(
312 state_tree: &StateTree<DB>,
313 network_version: NetworkVersion,
314) -> Result<TokenAmount, anyhow::Error> {
315 let total = if network_version >= NetworkVersion::V23 {
316 get_fil_power_locked(state_tree)?
317 } else {
318 get_fil_market_locked(state_tree)? + get_fil_power_locked(state_tree)?
319 };
320
321 Ok(total)
322}
323
324fn get_fil_burnt<DB: Blockstore>(state_tree: &StateTree<DB>) -> Result<TokenAmount, anyhow::Error> {
325 let burnt_actor = get_actor_state(state_tree, &Address::BURNT_FUNDS_ACTOR)?;
326
327 Ok(TokenAmount::from(&burnt_actor.balance))
328}
329
330fn setup_genesis_vesting_schedule() -> Vec<(ChainEpoch, TokenAmount)> {
331 PRE_CALICO_VESTING
332 .into_iter()
333 .map(|(unlock_duration, initial_balance)| {
334 (unlock_duration, TokenAmount::from_atto(initial_balance))
335 })
336 .collect()
337}
338
339fn setup_ignition_vesting_schedule(
340 liftoff_height: ChainEpoch,
341) -> Vec<(ChainEpoch, ChainEpoch, TokenAmount)> {
342 PRE_CALICO_VESTING
343 .into_iter()
344 .map(|(unlock_duration, initial_balance)| {
345 (
346 liftoff_height,
347 unlock_duration,
348 TokenAmount::from_whole(initial_balance),
349 )
350 })
351 .collect()
352}
353fn setup_calico_vesting_schedule(
354 liftoff_height: ChainEpoch,
355) -> Vec<(ChainEpoch, ChainEpoch, TokenAmount)> {
356 CALICO_VESTING
357 .into_iter()
358 .map(|(unlock_duration, initial_balance)| {
359 (
360 liftoff_height,
361 unlock_duration,
362 TokenAmount::from_whole(initial_balance),
363 )
364 })
365 .collect()
366}
367
368fn v0_amount_locked(
372 unlock_duration: ChainEpoch,
373 initial_balance: &TokenAmount,
374 elapsed_epoch: ChainEpoch,
375) -> TokenAmount {
376 if elapsed_epoch >= unlock_duration {
377 return TokenAmount::zero();
378 }
379 if elapsed_epoch < 0 {
380 return initial_balance.clone();
381 }
382 let unit_locked: TokenAmount = initial_balance.div_floor(unlock_duration);
384 unit_locked * (unlock_duration - elapsed_epoch)
385}