---
# System prepended metadata

title: 'Actor Spec: StorageMarket'

---

# Actor Spec: StorageMarket

[Back to Master Tracking Doc](https://hackmd.io/LOZjAsz-THelSD5lWqSVlw)

## Contents

[TOC]

## At a Glance

The StorageMarket actor manages the relationship between Miners and their Clients. This relationship revolves around DealProposals, in which a Miner agrees to store a specific piece of the Client's data for a period of time and at a specified price.

The StorageMarket actor:
* Manages Miner and Client collateral held on behalf of their deals.
* Allows a Miner to publish signed DealProposals, at which point the Miner becomes responsible for the Client's data (as well as the terms of the deal).
    * A Client signs a DealProposal to signal their agreement to its terms
    * A Miner's publication of these signed DealProposals signals the Miner's agreement to its terms
* Tracks important information about published DealProposals for use at various points during the Miner's Storage Mining Cycle.

**Actor Type:**
- Singleton
    - Address: 5

**Exported Methods:**

1. Constructor
2. AddBalance
3. WithdrawBalance
4. PublishStorageDeals
5. VerifyDealsForActivation
6. ActivateDeals
7. OnMinerSectorsTerminate
8. ComputeDataCommitment
9. CronTick

## State

```go=
type State struct {
	Proposals cid.Cid
	States    cid.Cid
	PendingProposals cid.Cid
	EscrowTable cid.Cid
	LockedTable cid.Cid
	NextID abi.DealID
	DealOpsByEpoch cid.Cid
	LastCron       abi.ChainEpoch
	TotalClientLockedCollateral abi.TokenAmount
	TotalProviderLockedCollateral abi.TokenAmount
	TotalClientStorageFee abi.TokenAmount
}
```

**`Proposals`**: Tracks published DealProposals by unique DealID.
* Notes:
    * Cid type: AMT, `array[DealID]DealProposal`
        * Maps a deal's unique DealID to its `DealProposal`
    * See DealProposal below

**`States`**: Tracks important epochs for active deals that have been included in a Sector.
* Notes:
    * Cid type: AMT, `array[DealID]DealState`
        * Maps a deal's unique DealID to its `DealState`
    * See DealState below

**`PendingProposals`**: Tracks proposals that have not reached their start epoch. Prevents Miners from publishing the same `DealProposal` twice.
* Notes:
    * Cid type, HAMT, `map[DealCid]DealProposal`
        * Maps a `DealProposal` Cid to its `DealProposal`
    * See DealProposal below

**`EscrowTable`**: Tracks funds held in escrow. Includes locked funds (see below).
* Notes:
    * Cid type: BalanceTable, `map[Address]TokenAmount`
        * Maps an actor's ID address to the token amount held in this table

**`LockedTable`**: Tracks funds held in escrow that are also locked.
* Notes:
    * Cid type: BalanceTable, `map[Address]TokenAmount`
        * Maps an actor's ID address to the token amount held in this table

**`NextID`**: Tracks the next DealID that will be assigned to a published storage deal
* Invariants:
    * `NextID >= 0`

**`DealOpsByEpoch`**: Tracks deals that need updates at specific epochs. The `CronTick` method performs these updates and may re-insert the deal if future updates are required. 
* Notes:
    * Cid type: SetMultimap, `map[ChainEpoch]Set`
        * Maps a StartEpoch to a set of DealProposals that start at that epoch
    * Deals are initially inserted into `DealOpsByEpoch` in `PublishStorageDeals`.

**`LastCron`**: The last epoch in which `CronTick` was called. Subsequent `CronTick`s perform updates from `LastCron + 1` to `rt.CurrEpoch`.
* Invariants:
    * `LastCron >= -1`

**`TotalClientLockedCollateral`**: Sum of all ClientCollateral locked in published deals. Unlocked when corresponding deals are terminated.
* Invariants:
    * `TotalClientLockedCollateral >= 0`

**`TotalProviderLockedCollateral`**: Sum of all ProviderCollateral locked in published deals. Unlocked when corresponding deals are terminated.
* Invariants:
    * `TotalProviderLockedCollateral >= 0`

**`TotalClientStorageFee`**: Sum of all storage fees locked in published deals. Unlocked over time as payments are made.
* Invariants:
    * `TotalClientStorageFee >= 0`

#### DealProposal

A `DealProposal` contains information about a Storage Deal between a Miner (`Provider`) and their Client.

```go=
type DealProposal struct {
	PieceCID     cid.Cid
	PieceSize    abi.PaddedPieceSize
	VerifiedDeal bool
	Client       addr.Address
	Provider     addr.Address
	Label string
	StartEpoch           abi.ChainEpoch
	EndEpoch             abi.ChainEpoch
	StoragePricePerEpoch abi.TokenAmount
	ProviderCollateral abi.TokenAmount
	ClientCollateral   abi.TokenAmount
}
```

**`PieceCID`**: The root Cid of the "piece" of data to be stored by the Miner on behalf of the Client.
* Invariants:
    * `PieceCID.Defined() == true`

**`PieceSize`**: The number of bytes that will be stored, padded to a power of two with extra zeroes.
* Invariants:
    * Should pass `PieceSize.Validate()` (`abi/piece.go`)

**`VerifiedDeal`**: Whether the deal is a Verified Deal (see VerifiedRegistry spec)
* Notes:
    * If `true`, the `Client` must be a Verified Client. StorageMarket must successfully invoke VerifiedRegistry.UseBytes, consuming a portion of Client's `DataCap`

**`Client`**: The address of the client providing the Piece data and paying the Miner for storage.
* Notes:
    * Uses ID address protocol

**`Provider`**: The Miner storing the data for `Client`.
* Notes:
    * Uses ID address protocol
* Requirements:
    * Must have a Miner actor CodeCID
        * NOTE: This is not currently enforced.

**`Label`**: Arbitrary string that can help identify the deal.

**`StartEpoch`**: The epoch in which the `Provider` becomes responsible for storing the data.

**`EndEpoch`**: The epoch in which the `Provider` is no longer responsible for storing the data.
* Invariants:
    * `EndEpoch > StartEpoch`

**`StoragePricePerEpoch`**: The token amount paid by `Client` for each epoch the deal is active.
* Notes:
    * Total payment is calculated for each epoch from `StartEpoch` to `EndEpoch`
* Requirements:
    * `StoragePricePerEpoch >= 0`

**`ProviderCollateral`**: The token amount put up by the deal's `Provider`. This amount is taken from the Provider's existing entry in `st.EscrowTable`, and added to the Provider's entry in `st.LockedTable`.
* Notes:
    * See `OnMinerSectorsTerminate` for details on refund/slashing
* Requirements:
    * `ProviderCollateral >= 0`

**`ClientCollateral`**: The token amount put up by the deal's `Client`. This amount is taken from the Client's existing entry in `st.EscrowTable`, and added to the Client's entry in `st.LockedTable`
* Notes:
    * See `OnMinerSectorsTerminate` for details on refund/slashing
* Requirements:
    * `ClientCollateral >= 0`

#### DealState

A deal's `DealState` tracks important epochs for deals that have been included in a Sector.

```go=
type DealState struct {
	SectorStartEpoch abi.ChainEpoch
	LastUpdatedEpoch abi.ChainEpoch
	SlashEpoch       abi.ChainEpoch
}
```

**`SectorStartEpoch`**: Once a deal is activated (`ActivateDeals`), the deal's `SectorStartEpoch` records the `StartEpoch` in which the Miner ProveCommitted the Sector.
* Invariants:
    * `SectorStartEpoch >= 0`

**`LastUpdatedEpoch`**: Tracks the last epoch in which a deal's `DealState` was updated.
* Invariants:
    * `LastUpdatedEpoch >= -1`

**`SlashEpoch`**: Tracks the epoch in which a deal was slashed.
* Invariants:
    * `SlashEpoch >= -1`

## Exported Methods

#### 1. Constructor

```go=
func (a Actor) Constructor(rt Runtime, _ *adt.EmptyValue) *adt.EmptyValue
```

Initializes the StorageMarket actor's state. All values are `nil`, except `LastCron`, which is set to `ChainEpoch(-1)`.

#### 2. AddBalance

```go=
func (a Actor) AddBalance(rt Runtime, providerOrClientAddress *addr.Address) *adt.EmptyValue
```

Adds `ValueReceived()` to the `EscrowTable` entry corresponding to `providerOrClientAddress`
* `providerOrClientAddress` must resolve to an ID address
    * Resolved address must have a CodeCID
        * If CodeCID is of a Miner actor, Caller must be one of the Miner's control addresses
    * Caller must be a Signable-type actor
* `ValueReceived()` must be greater than zero

Additionally, an empty entry is created in the `LockedTable` if one does not exist.

##### Failure conditions
* Caller provides zero `ValueReceived()`
* Caller is not a Signable-type actor
* `providerOrClientAddress` fails to resolve
* `providerOrClientAddress` does not have an associated CodeCID
* `providerOrClientAddress` resolves to a Miner actor, and Caller is not one of the Miner's control addresses
* `providerOrClientAddress` does not resolve to a Miner actor, and Caller is not a Signable-type actor

#### 3. WithdrawBalance

```go=
func (a Actor) WithdrawBalance(rt Runtime, params *WithdrawBalanceParams) *adt.EmptyValue
```

Allows an actor to withdraw available balance held in escrow.
* If the actor in question is a Miner, balance withdrawn is sent to the Miner's Owner address.

##### Parameters

```go=
type WithdrawBalanceParams struct {
	ProviderOrClientAddress addr.Address
	Amount                  abi.TokenAmount
}
```

**`ProviderOrClientAddress`**: Must resolve to a Signable-type actor, or a Miner actor.
* Notes:
    * All address protocols allowed
* Requirements:
    * If `ProviderOrClientAddress` resolves to a Miner actor, Caller must be one of the Miner's control addresses

**`Amount`**: The amount to withdraw from escrow.
* Notes:
    * If less than `Amount` is available, this method will send the entire available balance.
* Requirements:
    * `Amount >= 0`

##### Failure conditions
* `params.Amount < 0`
* `providerOrClientAddress` does not have an associated CodeCID
* `providerOrClientAddress` resolves to a Miner actor, and Caller is not one of the Miner's control addresses
* `providerOrClientAddress` is not a Miner actor, and is not Caller

#### 4. PublishStorageDeals

```go=
func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams) *PublishStorageDealsReturn
```

Used by a Miner to publish a new set of Storage Deals, each signed by the deal's Client.
* These deals have not yet been included in a Sector.
* Each passed-in deal is given a unique DealID before being placed in `State`:
    * `st.PendingProposals`: Maps the Cid of the deal's `Proposal` to the `Proposal` itself
    * `st.Proposals`: Maps the unique DealID to the deal's `Proposal`
    * `st.DealOpsByEpoch`: Maps the Proposal's `StartEpoch` to the unique DealID
        * This is used by `CronTick`
* Each deal corresponds to collateral amounts that both the Miner and Client must provide.
    * Collateral should already exist for both parties in their respective `st.EscrowTable` entries.
    * The Client provides a base `ClientCollateral`, as well as the total storage fee calculated from various deal parameters.

##### Parameters

`PublishStorageDealsParams` contains a slice of signed `DealProposals`.

```go=
type PublishStorageDealsParams struct {
	Deals []ClientDealProposal
}
```

**`Deals`**: See `ClientDealProposal` below
* Requirements:
    * `len(Deals) != 0`

###### ClientDealProposal

```go=
type ClientDealProposal struct {
	Proposal        DealProposal
	ClientSignature acrypto.Signature
}
```

**`Proposal`**: The DealProposal agreed to between the Miner and Client
* Requirements:
    * Must not be `nil`

**`ClientSignature`**: A signature over `Proposal` by `Proposal.Client`
* Requirements:
    * Must not be `nil`
    * Must be a valid signature as per `rt.Syscalls().VerifySignature`

##### Return

A slice of DealIDs corresponding to the newly-published Storage Deals.

```go=
type PublishStorageDealsReturn struct {
	IDs []abi.DealID
}
```

##### Failure conditions
* Caller is not a Signable actor
* `len(params.Deals) == 0`
* Deal's Provider address:
    * Is not able to be resolved
    * Does not have a Miner actor CodeCID
        * NOTE: This is not currently enforced
* Caller is not the Worker address associated with deal's Provider
* VerifiedRegistry.UseBytes fails for any `deal.Client` and `deal.PieceSize`
* For any `ClientDealProposal`:
    * `validateDeal` fails:
        * Signature verification fails
        * `len(proposal.Label) > DealMaxLabelSize` (256)
        * `proposal.PieceSize.Validate()` fails
        * `proposal.PieceCID.Defined() == false`
        * `proposal.PieceCID.Prefix() != PieceCIDPrefix`
        * `proposal.EndEpoch <= proposal.StartEpoch`
        * `rt.CurrEpoch() > proposal.StartEpoch`
        * Invalid proposal duration
        * Invalid proposal price
        * Invalid `proposal.ProviderCollateral`
        * Invalid `proposal.ClientCollateral`
    * `deal.Provider` is different than any other deal's Provider
    * `deal.Client` address is not able to be resolved
    * Specified collateral is not available in Client or Provider's escrow balance
    * Proposal already exists in `st.PendingProposals`

#### 5. VerifyDealsForActivation

```go=
func (A Actor) VerifyDealsForActivation(rt Runtime, params *VerifyDealsForActivationParams) *VerifyDealsForActivationReturn
```

Allows a Miner to verify that a set of Storage Deals is valid for a Sector. This method is called by a Miner after publishing a set of Storage Deals via `PublishStorageDeals`. After accepting Client data from published deals, the Miner PreCommits a Sector containing those deals via `Miner.PreCommitSector`.
* As part of this, the Miner declares an expiration epoch for the containing Sector. The start epoch of the Sector is defined as `rt.CurrEpoch()` (when `Miner.PreCommitSector` is invoked).
* `Miner.PreCommitSector` must record the total DealWeight of the Sector. In order to calculate this, `VerifyDealsForActivation` is invoked.

This method iterates over a slice of provided DealIDs, validates that the deal in question is unique and may be activated, then calculates each deal's respective weight. The sum of these weights is returned, split into weight corresponding to verified deals and weight corresponding to regular deals.

##### Parameters

```go=
type VerifyDealsForActivationParams struct {
	DealIDs      []abi.DealID
	SectorExpiry abi.ChainEpoch
	SectorStart  abi.ChainEpoch
}
```

**`DealIDs`**: A slice of DealIDs included in a Sector the Miner is PreCommitting.
* Requirements:
    * Each `DealID` must correspond to an entry in `st.Proposals`
    * Caller must be the `Provider` of each referenced deal
    * Each `DealID` must be unique within the slice

**`SectorExpiry`**: The expiration epoch for the containing Sector declared by the Miner.
* Requirements:
    * Each deal's `EndEpoch` must be less than or equal to `SectorExpiry`

**`SectorStart`**: The start epoch for the containing Sector. Set to `rt.CurrEpoch()`.
* Requirements:
    * Each deal's `StartEpoch` must come after `SectorStart`

##### Return

```go=
type VerifyDealsForActivationReturn struct {
    DealWeight         abi.DealWeight
    VerifiedDealWeight abi.DealWeight
    DealSpace uint64
}
```

**`DealWeight`**: The sum of DealWeights calculated for each passed-in `DealID` not corresponding to a Verified Deal.
* Notes:
    * Measured in byte-epochs
    * Calculated as `proposal.Duration() * proposal.PieceSize`
* Invariants:
    * `DealWeight >= 0`

**`VerifiedDealWeight`**: The sum of DealWeights calculated for each passed-in `DealID` corresponding to a Verified Deal.
* Notes:
    * Measured in byte-epochs
    * Calculated as `proposal.Duration() * proposal.PieceSize`
* Invariants:
    * `VerifiedDealWeight >= 0`

**`DealSpace`**: The sum of the `PieceSize` of each passed-in deal.
* Notes:
    * Measured in bytes
* Invariants:
    * `DealSpace >= 0`

##### Failure conditions
* Caller is not a Miner actor
* `params.DealIDs` contains a duplicate entry
* A referenced `DealID` is not found in `st.Proposals`
* A referenced deal cannot be activated (`validateDealCanActivate`):
    * Caller is not the deal's `Provider`
    * Deal's `StartEpoch` has already elapsed (`CurrEpoch > proposal.StartEpoch`)
    * Deal's `EndEpoch` exceeds the Sector's expiration (`proposal.EndEpoch > params.SectorExpiry`)

#### 6. ActivateDeals

```go=
func (a Actor) ActivateDeals(rt Runtime, params *ActivateDealsParams) *adt.EmptyValue
```

After PreCommitting a Sector with its contained deals, a Miner attempts to ProveCommit the Sector via `Miner.ProveCommitSector`, which submits a proof of replication to the StoragePower actor (`SubmitPoRepForBulkVerify`). If this proof is valid, the StoragePower actor calls `Miner.ConfirmSectorProofsValid` at the end of the same epoch.

For each valid Sector proof, `Miner.ConfirmSectorProofsValid` activates the deals stored in that Sector via `ActivateDeals`. This method:
* Validates that each DealProposal may be activated
* Checks that each DealProposal:
    * Exists in `st.Proposals`
    * Exists in `st.PendingProposals`
    * Does not exist in `st.States`
        * This would imply that the deal has already been included in a Sector
* Inserts each DealProposal into `st.States` by its `DealID`.

Each deal now has a corresponding DealState. The `DealState.SectorStartEpoch` of each deal is set to `rt.CurrEpoch()`, while the other two fields are set to `epochUndefined` (`ChainEpoch(-1)`).

##### Parameters

```go=
type ActivateDealsParams struct {
	DealIDs      []abi.DealID
	SectorExpiry abi.ChainEpoch
}
```

**`DealIDs`**: A slice of `DealIDs` being activated.
* Notes:
    * Each `DealID` is stored within the same Sector.
* Requirements:
    * Each `DealID` must correspond to an entry in `st.Proposals`
    * Caller must be the `Provider` of each referenced deal
    * No deal's `StartEpoch` should have already elapsed
    * No deal should exist within `st.States`
    * Each deal must exist within `st.PendingProposals`

**`SectorExpiry`**: The expiration epoch of the containing Sector.
* Requirements:
    * Each deal's `EndEpoch` must be less than or equal to `SectorExpiry`

##### Failure conditions
* Caller is not a Miner actor
* A referenced deal cannot be activated (`validateDealCanActivate`):
    * Caller is not the deal's `Provider`
    * Deal's `StartEpoch` has already elapsed (`CurrEpoch > proposal.StartEpoch`)
    * Deal's `EndEpoch` exceeds the Sector's expiration (`proposal.EndEpoch > params.SectorExpiry`)
* A referenced deal exists within `st.States`
* A referenced deal was not found within `st.Proposals`
* A referenced deal was not found within `st.PendingProposals`

#### 7. OnMinerSectorsTerminate

```go=
func (a Actor) OnMinerSectorsTerminate(rt Runtime, params *OnMinerSectorsTerminateParams) *adt.EmptyValue
```

A Miner may invoke this method from one of the following exported methods:
* Miner.TerminateSectors
* Miner.ReportConsensusFault
* Miner.OnDeferredCronEvent

This method simply iterates over the provided `DealIDs` and sets the deal's `DealState.SlashEpoch` to `params.Epoch`. The actual slashing occurs the next time this deal is processed in `CronTick`.
* Special cases:
    * If the deal has already expired (`deal.EndEpoch <= params.Epoch`), `SlashEpoch` is not set.
    * If the deal's `SlashEpoch` has already been set (`DealState.SlashEpoch != epochUndefined`), nothing happens.

##### Parameters

```go=
type OnMinerSectorsTerminateParams struct {
	Epoch   abi.ChainEpoch
	DealIDs []abi.DealID
}
```

**`Epoch`**: The epoch in which the containing Sector was terminated.

**`DealIDs`**: A slice of `DealIDs` whose containing sector has been terminated.

##### Failure conditions
* Caller is not a Miner actor
* For any given passed-in `DealID`, Caller is not that deal's Provider (panic)
* Deal does not exist in `st.States`
    * Note: This implies the deal was never activated and so should not be included in a Sector

#### 8. ComputeDataCommitment

```go=
func (a Actor) ComputeDataCommitment(rt Runtime, params *ComputeDataCommitmentParams) *cbg.CborCid
```

After PreCommitting a Sector with its contained deals, a Miner attempts to ProveCommit the Sector via `Miner.ProveCommitSector`, which submits a proof of replication to the StoragePower actor (`SubmitPoRepForBulkVerify`). 

As part of this process, the Miner invokes `ComputeDataCommitment` to calculate the Unsealed Sector CID for the Sector in question. The resulting UnsealedCID (also referred to as "CommD," or "data commitment") is passed in along with other values to `SubmitPoRepForBulkVerify`.

This method returns the result of the syscall `rt.Syscalls().ComputeUnsealedSectorCID`.

##### Parameters

```go=
type ComputeDataCommitmentParams struct {
	DealIDs    []abi.DealID
	SectorType abi.RegisteredSealProof
}
```

**`DealIDs`**: A slice of `DealIDs` that will all be stored in the same Sector.
* Requirements:
    * Each deal must exist within `st.Proposals`

**`SectorType`**: The seal proof type used by the Miner for this Sector. Should be equivalent to the Miner's `MinerInfo.SealProofType`.

##### Failure conditions
* Caller is not a Miner actor
* A referenced `DealID` is not found within `st.Proposals`

#### 9. CronTick

```go=
func (a Actor) CronTick(rt Runtime, _ *adt.EmptyValue) *adt.EmptyValue
```

`CronTick` is a special method invoked by the Cron actor at the end of each tipset. 

The primary purpose of `CronTick` is to handle updates to deals that need to occur at specific epochs. These updates are tracked by `st.DealOpsByEpoch`, which maps an epoch to a list of deals that need updates at that epoch. `CronTick` iterates over each deal for a given epoch.

Typically, `CronTick` is only concerned with a single epoch, `rt.CurrEpoch()`. However, in the rare case that an epoch does not invoke the Cron actor, `CronTick` iterates over each of the deals for each epoch since it was last invoked (`st.LastCron + 1` to `rt.CurrEpoch()`).

Deals are initially inserted in `st.DealOpsByEpoch` in `PublishStorageDeals`, where they are inserted by `DealProposal.StartEpoch`. This means that during the deal's start epoch, `CronTick` will process an update for that deal for the first time.

The operations performed on each deal depend in large part on the deal's respective `DealState` entry (in `st.States`), which is created on a deal's activation via `ActivateDeals`:
* `SectorStartEpoch`: Set to `rt.CurrEpoch()`. Never updated.
* `LastUpdatedEpoch`: Set to `epochUndefined` (-1). Updated each time the deal is processed in `CronTick`.
* `SlashEpoch`: Set to `epochUndefined` (-1). Updated if a deal's containing Sector is terminated via `OnMinerSectorsTerminate`.

The first time `CronTick` processes a specific deal, it will perform ONE of the following actions:
* **Init Timeout**: If a deal was published via `PublishStorageDeals` but never activated via `ActivateDeals`, `CronTick` considers it "timed out," as its `StartEpoch` has elapsed without it appearing in a proven Sector.
    * Identification: 
        * `DealID` exists in `st.Proposals` and `st.PendingProposals`, but NOT within `st.States`
    * Actions taken:
        * `deal.ClientCollateral` and `deal.ClientStorageFee` are unlocked and returned to `deal.Client`
        * A penalty is calculated and slashed from `deal.ProviderCollateral`. Any amount remaining is unlocked and returned to `deal.Provider`.
        * If the deal in question is a Verified Deal (see VerifiedRegistry actor), the bytes consumed by the deal are restored.
        * The deal is deleted from `st.Proposals`
    * Notes:
        * If a deal is in Init Timeout, no other updates will be performed.
* **Init Update**: If this is the first time a deal has been processed by `CronTick`, `CronTick` performs an Init Update.
    * Identification: 
        * `DealID` exists in `st.Proposals`, `st.PendingProposals`, and `st.States`
        * `DealState.LastUpdatedEpoch == epochUndefined`
    * Actions taken:
        * Deal's entry is deleted from `st.PendingProposals`

`CronTick` performs these updates for ALL deals, EXCEPT those caught by Init Timeout:
* **Client-Provider Payment**: Clients pay Providers for storage incrementally. Payment is due each time a deal is processed by `CronTick`, and is calculated from `deal.StoragePricePerEpoch` times the number of epochs elapsed since the last time the deal was processed. If the deal has already ended (`rt.CurrEpoch > deal.EndEpoch`), payment is not due for epochs between `EndEpoch` and `CurrEpoch`.
    * Special case: If `deal.Provider` was slashed for this deal, `deal.Client` will only be charged up to `state.SlashEpoch` at max.
    * Actions taken:
        * Payment amount is transferred from the Client's `LockedTable` to the Provider's `EscrowTable`.
* **Handle Slashed Deal**: If a deal's containing Sector was terminated, the Provider must be slashed.
    * Identification:
        * `DealState.SlashEpoch != epochUndefined`
    * Actions taken:
        * `deal.ClientCollateral` is unlocked and returned to `deal.Client`
        * Any remaining storage fees are unlocked and returned to `deal.Client`
        * `deal.ProviderCollateral` is slashed by removing the amount from the Provider's `EscrowTable` balance and sending it to the BurntFunds actor
        * The deal is deleted from `st.Proposals` and `st.States`
* **Handle Expired Deal**: If a deal's `EndEpoch` has elapsed (and the deal was not slashed), the deal has been completed.
    * Identification:
        * `rt.CurrEpoch() >= deal.EndEpoch`, AND `DealState.SlashEpoch == epochUndefined`
    * Actions taken:
        * `deal.ClientCollateral` and `deal.ProviderCollateral` are unlocked and returned to `deal.Client` and `deal.Provider`
        * The deal is deleted from `st.Proposals` and `st.States`
* **Handle Ongoing Deal**: A deal that has not been slashed and has not expired must be processed again in a future epoch.
    * Identification:
        * `rt.CurrEpoch() < deal.EndEpoch`, AND `DealState.SlashEpoch == epochUndefined`
    * Actions taken:
        * `DealState.LastUpdatedEpoch` is set to `rt.CurrEpoch()`
        * The deal is re-inserted in `st.DealOpsByState` at the epoch `rt.CurrEpoch() + DealUpdatesInterval` (100)
            * NOTE: If this epoch is beyond the deal's EndEpoch, the EndEpoch is used instead.

No matter the case, once an epoch's deals have been iterated over and processed, that epoch's entries are deleted from `st.DealOpsByEpoch`.

##### Failure conditions
* Caller is not the Cron actor
* For any deal/`DealID` referenced by `StartEpoch` in `st.DealOpsByEpoch`:
    * Deal does not exist in `st.Proposals`
    * `DealState` does not exist in `st.States`, AND:
        * `deal.StartEpoch > rt.CurrEpoch()` (panic)
        * `processDealInitTimeout` fails to unlock or slash balances
    * `DealState.LastUpdatedEpoch == epochUndefined` AND deal does not exist in `st.PendingProposals`
    * `updatePendingDealState` fails:
        * `DealState.LastUpdatedEpoch > rt.CurrEpoch()` (panic)
        * `DealState.SlashEpoch > rt.CurrEpoch()` (panic)
        * `DealState.SlashEpoch > deal.EndEpoch` (panic)
        * Payment from `deal.Client` to `deal.Provider` fails (`st.transferBalance`)
        * If `DealState.SlashEpoch` is set, AND:
            * Client collateral and remaining storage fee fail to be unlocked
            * Provider collateral fails to be slashed
        * If `rt.CurrEpoch() >= deal.EndEpoch`, AND:
            * `DealState.SectorStartEpoch == epochUndefined` (panic)
            * Provider and Client collateral fail to be unlocked