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 ) -> anyhow::Result<TokenAmount> {
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 ) -> anyhow::Result<TokenAmount> {
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 = ms.total_locked();
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 circ += avail_balance.clone();
173 un_circ += actor_balance.clone() - &avail_balance;
174 } else {
175 un_circ += actor_balance;
178 }
179 }
180 _ if is_multisig_actor(&actor.code) => {
181 let ms = multisig::State::load(&db, actor.code, actor.state)?;
182
183 let locked_balance = ms.locked_balance(height)?;
184 let avail_balance = actor_balance.clone() - &locked_balance;
185 circ += avail_balance.max(TokenAmount::zero());
186 un_circ += actor_balance.min(locked_balance);
187 }
188 _ => bail!("unexpected actor: {:?}", actor),
189 }
190 } else {
191 }
193 Ok(())
194 })?;
195
196 let total = circ.clone() + un_circ;
197 if total != *TOTAL_FILECOIN {
198 bail!(
199 "total filecoin didn't add to expected amount: {} != {}",
200 total,
201 *TOTAL_FILECOIN
202 );
203 }
204
205 Ok(circ)
206 }
207}
208
209#[derive(Default, Clone)]
212struct GenesisInfoVesting {
213 genesis: Vec<(ChainEpoch, TokenAmount)>,
214 ignition: Vec<(ChainEpoch, ChainEpoch, TokenAmount)>,
215 calico: Vec<(ChainEpoch, ChainEpoch, TokenAmount)>,
216}
217
218impl GenesisInfoVesting {
219 fn new(liftoff_height: i64) -> Self {
220 Self {
221 genesis: setup_genesis_vesting_schedule(),
222 ignition: setup_ignition_vesting_schedule(liftoff_height),
223 calico: setup_calico_vesting_schedule(liftoff_height),
224 }
225 }
226}
227
228fn get_actor_state<DB: Blockstore>(
229 state_tree: &StateTree<DB>,
230 addr: &Address,
231) -> anyhow::Result<ActorState> {
232 state_tree
233 .get_actor(addr)?
234 .with_context(|| format!("Failed to get Actor for address {addr}"))
235}
236
237fn get_fil_vested(genesis_info: &GenesisInfo, height: ChainEpoch) -> TokenAmount {
238 let mut return_value = TokenAmount::default();
239
240 let pre_ignition = &genesis_info.vesting.genesis;
241 let post_ignition = &genesis_info.vesting.ignition;
242 let calico_vesting = &genesis_info.vesting.calico;
243
244 if height <= genesis_info.chain_config.epoch(Height::Ignition) {
245 for (unlock_duration, initial_balance) in pre_ignition {
246 return_value +=
247 initial_balance - v0_amount_locked(*unlock_duration, initial_balance, height);
248 }
249 } else if height <= genesis_info.chain_config.epoch(Height::Calico) {
250 for (start_epoch, unlock_duration, initial_balance) in post_ignition {
251 return_value += initial_balance
252 - v0_amount_locked(*unlock_duration, initial_balance, height - start_epoch);
253 }
254 } else {
255 for (start_epoch, unlock_duration, initial_balance) in calico_vesting {
256 return_value += initial_balance
257 - v0_amount_locked(*unlock_duration, initial_balance, height - start_epoch);
258 }
259 }
260
261 if height <= genesis_info.chain_config.epoch(Height::Assembly) {
262 return_value += &genesis_info.genesis_pledge + &genesis_info.genesis_market_funds;
263 }
264
265 return_value
266}
267
268fn get_fil_mined<DB: Blockstore>(state_tree: &StateTree<DB>) -> anyhow::Result<TokenAmount> {
269 let state: reward::State = state_tree.get_actor_state()?;
270 Ok(state.into_total_storage_power_reward())
271}
272
273fn get_fil_market_locked<DB: Blockstore>(
274 state_tree: &StateTree<DB>,
275) -> anyhow::Result<TokenAmount> {
276 let actor = state_tree
277 .get_actor(&Address::MARKET_ACTOR)?
278 .ok_or_else(|| Error::state("Market actor address could not be resolved"))?;
279 let state = market::State::load(state_tree.store(), actor.code, actor.state)?;
280
281 Ok(state.total_locked())
282}
283
284fn get_fil_power_locked<DB: Blockstore>(state_tree: &StateTree<DB>) -> anyhow::Result<TokenAmount> {
285 let actor = state_tree
286 .get_actor(&Address::POWER_ACTOR)?
287 .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
288 let state = power::State::load(state_tree.store(), actor.code, actor.state)?;
289 Ok(state.into_total_locked())
290}
291
292fn get_fil_reserve_disbursed<DB: Blockstore>(
293 chain_config: &ChainConfig,
294 height: ChainEpoch,
295 state_tree: &StateTree<DB>,
296) -> anyhow::Result<TokenAmount> {
297 let fil_reserved = chain_config.initial_fil_reserved_at_height(height);
301 let reserve_actor = get_actor_state(state_tree, &Address::RESERVE_ACTOR)?;
302
303 Ok(fil_reserved - TokenAmount::from(&reserve_actor.balance))
305}
306
307fn get_fil_locked<DB: Blockstore>(
308 state_tree: &StateTree<DB>,
309 network_version: NetworkVersion,
310) -> anyhow::Result<TokenAmount> {
311 let total = if network_version >= NetworkVersion::V23 {
312 get_fil_power_locked(state_tree)?
313 } else {
314 get_fil_market_locked(state_tree)? + get_fil_power_locked(state_tree)?
315 };
316
317 Ok(total)
318}
319
320fn get_fil_burnt<DB: Blockstore>(state_tree: &StateTree<DB>) -> anyhow::Result<TokenAmount> {
321 let burnt_actor = get_actor_state(state_tree, &Address::BURNT_FUNDS_ACTOR)?;
322
323 Ok(TokenAmount::from(&burnt_actor.balance))
324}
325
326fn setup_genesis_vesting_schedule() -> Vec<(ChainEpoch, TokenAmount)> {
327 PRE_CALICO_VESTING
328 .into_iter()
329 .map(|(unlock_duration, initial_balance)| {
330 (unlock_duration, TokenAmount::from_atto(initial_balance))
331 })
332 .collect()
333}
334
335fn setup_ignition_vesting_schedule(
336 liftoff_height: ChainEpoch,
337) -> Vec<(ChainEpoch, ChainEpoch, TokenAmount)> {
338 PRE_CALICO_VESTING
339 .into_iter()
340 .map(|(unlock_duration, initial_balance)| {
341 (
342 liftoff_height,
343 unlock_duration,
344 TokenAmount::from_whole(initial_balance),
345 )
346 })
347 .collect()
348}
349fn setup_calico_vesting_schedule(
350 liftoff_height: ChainEpoch,
351) -> Vec<(ChainEpoch, ChainEpoch, TokenAmount)> {
352 CALICO_VESTING
353 .into_iter()
354 .map(|(unlock_duration, initial_balance)| {
355 (
356 liftoff_height,
357 unlock_duration,
358 TokenAmount::from_whole(initial_balance),
359 )
360 })
361 .collect()
362}
363
364fn v0_amount_locked(
368 unlock_duration: ChainEpoch,
369 initial_balance: &TokenAmount,
370 elapsed_epoch: ChainEpoch,
371) -> TokenAmount {
372 if elapsed_epoch >= unlock_duration {
373 return TokenAmount::zero();
374 }
375 if elapsed_epoch < 0 {
376 return initial_balance.clone();
377 }
378 let unit_locked: TokenAmount = initial_balance.div_floor(unlock_duration);
380 unit_locked * (unlock_duration - elapsed_epoch)
381}