1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright 2019-2022 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::balance_table::BalanceTable;
use anyhow::anyhow;
use cid::Cid;
use fil_actor_verifreg_state::v9::AllocationID;
use fil_actors_shared::v9::{make_empty_map, ActorError, Array, AsActorError as _, SetMultimap};
use fvm_ipld_blockstore::Blockstore;
use fvm_ipld_encoding::tuple::*;
use fvm_shared::clock::{ChainEpoch, EPOCH_UNDEFINED};
use fvm_shared::deal::DealID;
use fvm_shared::econ::TokenAmount;
use fvm_shared::error::ExitCode;
use fvm_shared::HAMT_BIT_WIDTH;

use super::types::*;

/// Market actor state
#[derive(Clone, Default, Serialize_tuple, Deserialize_tuple, Debug)]
pub struct State {
    /// Proposals are deals that have been proposed and not yet cleaned up after expiry or termination.
    /// `Array<DealID, DealProposal>`
    pub proposals: Cid,

    // States contains state for deals that have been activated and not yet cleaned up after expiry or termination.
    // After expiration, the state exists until the proposal is cleaned up too.
    // `Invariant: keys(States) ⊆ keys(Proposals)`.
    /// `Array<DealID, DealState>`
    pub states: Cid,

    /// `PendingProposals` tracks `dealProposals` that have not yet reached their deal start date.
    /// We track them here to ensure that miners can't publish the same deal proposal twice
    pub pending_proposals: Cid,

    /// Total amount held in escrow, indexed by actor address (including both locked and unlocked amounts).
    pub escrow_table: Cid,

    /// Amount locked, indexed by actor address.
    /// Note: the amounts in this table do not affect the overall amount in escrow:
    /// only the _portion_ of the total escrow amount that is locked.
    pub locked_table: Cid,

    /// Deal id state sequential incrementer
    pub next_id: DealID,

    /// Metadata cached for efficient iteration over deals.
    /// `SetMultimap<Address>`
    pub deal_ops_by_epoch: Cid,
    pub last_cron: ChainEpoch,

    /// Total Client Collateral that is locked unlocked when deal is terminated
    pub total_client_locked_collateral: TokenAmount,
    /// Total Provider Collateral that is locked unlocked when deal is terminated
    pub total_provider_locked_collateral: TokenAmount,
    /// Total storage fee that is locked in escrow unlocked when payments are made
    pub total_client_storage_fee: TokenAmount,

    /// Verified registry allocation IDs for deals that are not yet activated.
    pub pending_deal_allocation_ids: Cid, // HAMT[DealID]AllocationID
}

impl State {
    pub fn new<BS: Blockstore>(store: &BS) -> anyhow::Result<Self> {
        let empty_proposals_array =
            Array::<(), BS>::new_with_bit_width(store, PROPOSALS_AMT_BITWIDTH)
                .flush()
                .map_err(|e| anyhow!("Failed to create empty proposals array: {}", e))?;
        let empty_states_array = Array::<(), BS>::new_with_bit_width(store, STATES_AMT_BITWIDTH)
            .flush()
            .map_err(|e| anyhow!("Failed to create empty states array: {}", e))?;

        let empty_pending_proposals_map = make_empty_map::<_, ()>(store, HAMT_BIT_WIDTH)
            .flush()
            .map_err(|e| anyhow!("Failed to create empty pending proposals map state: {}", e))?;
        let empty_balance_table = BalanceTable::new(store)
            .root()
            .map_err(|e| anyhow!("Failed to create empty balance table map: {}", e))?;
        let empty_deal_ops_hamt = SetMultimap::new(store)
            .root()
            .map_err(|e| anyhow!("Failed to create empty multiset: {}", e))?;
        let empty_pending_deal_allocation_map =
            make_empty_map::<_, AllocationID>(store, HAMT_BIT_WIDTH)
                .flush()
                .map_err(|e| {
                    anyhow!("Failed to create empty pending deal allocation map: {}", e)
                })?;
        Ok(Self {
            proposals: empty_proposals_array,
            states: empty_states_array,
            pending_proposals: empty_pending_proposals_map,
            escrow_table: empty_balance_table,
            locked_table: empty_balance_table,
            next_id: 0,
            deal_ops_by_epoch: empty_deal_ops_hamt,
            last_cron: EPOCH_UNDEFINED,

            total_client_locked_collateral: TokenAmount::default(),
            total_provider_locked_collateral: TokenAmount::default(),
            total_client_storage_fee: TokenAmount::default(),
            pending_deal_allocation_ids: empty_pending_deal_allocation_map,
        })
    }

    pub fn total_locked(&self) -> TokenAmount {
        &self.total_client_locked_collateral
            + &self.total_provider_locked_collateral
            + &self.total_client_storage_fee
    }

    pub fn escrow_table<'a, BS: Blockstore>(
        &self,
        store: &'a BS,
    ) -> Result<BalanceTable<'a, BS>, ActorError> {
        BalanceTable::from_root(store, &self.escrow_table)
            .context_code(ExitCode::USR_ILLEGAL_STATE, "failed to load escrow table")
    }

    pub fn locked_table<'a, BS: Blockstore>(
        &self,
        store: &'a BS,
    ) -> Result<BalanceTable<'a, BS>, ActorError> {
        BalanceTable::from_root(store, &self.locked_table)
            .context_code(ExitCode::USR_ILLEGAL_STATE, "failed to load locked table")
    }
}