PoRep Market V2 coordinates Filecoin storage deals between clients and Storage Providers (SPs). It matches deal requests to provider offers, validates storage evidence, tracks provider capacity and deal state, and controls settlement through per-deal FilecoinPay validators.
The contracts, interfaces, and tests are authoritative. This README maintains only the system ownership and deal lifecycle needed to navigate them.
- Latest Calibnet deployment
- Mainnet deployment. V2 release candidate.
main contains V2. V1 maintenance, releases, deployments, and upgrades belong
on the separate v1 branch.
PoRepMarketowns deal state, frozen deal terms, service dates, payment calculation, settlement progress, and lifecycle transitions. Its external contract isIPoRepMarket.SPRegistrystores providers and their mutable offers, selects an offer for a request, and tracks available, reserved, and committed capacity.DataCapEvidenceAdaptersubmits DataCap allocations, records Filecoin claims, and reports covered bytes. Evidence adapters implementIStorageEvidenceAdapter.SectorEvidenceAdaptervalidates authenticated FIP-0109 placement callbacks against the complete piece-set commitment stored in a deal'smanifestHash. It records each piece placement, unique sector numbers, covered bytes per sector, and the earliest accepted minimum commitment epoch. Activation requires that epoch to cover the activation block plus the complete deal duration. FIP-0112 refreshes run over the stored sector list.Validatoris the per-deal FilecoinPay operator. It controls the payment rail and delegates settlement decisions toPoRepMarket.ValidatorFactorycreates Validator beacon proxies for deals.SLIOraclestores provider attestations, andSLIScorerevaluates the deal's frozen SLI thresholds during settlement.- FilecoinPay holds client funds, settles rails through Validator, and pays providers.
Canonical lifecycle codes and data structures live in
DealState,
PoRepTypes, and
SharedTypes.
DataCap and VerifReg are the current way PoRep Market checks storage, but this path is being phased out. V2 cannot bake DataCap allocations and claims into its deal and payment model and then require another market redesign when Filecoin removes them.
The adapter is the transition boundary. It translates whichever Filecoin storage mechanism is available into the few facts PoRep Market needs: how many deal bytes have storage coverage, whether the deal can activate, and whether that coverage is still current.
Today DataCapEvidenceAdapter gets those facts from DataCap allocations and
VerifReg claims. A later adapter can use the post-DataCap mechanism without
changing PoRepMarket, Validator, or the existing deal ABI.
Each deal keeps the adapter selected when it was created. New deals can move to
the replacement while older deals remain tied to the path they started with.
Fresh deployments include SectorEvidenceAdapter, but keep
DataCapEvidenceAdapter as the global default. An administrator can select the
sector adapter for new deals when running this PoC. No existing Calibnet or
mainnet deployment is changed by this branch.
The sector adapter uses the same AccessManager and UPGRADER_ROLE as the
other contracts. Deploy this rebased PoC fresh; existing PoC proxies initialized
with adapter-local roles have no configured AccessManager. The first accepted
piece locks the deal manifest, including before full activation.
The code calls these storage facts evidence. Evidence here means the on-chain information used to check storage coverage; it does not mean that every adapter returns a cryptographic proof. The rest of this README calls it the adapter.
Before proposing a Sector evidence deal, the manifest builder rejects duplicate
rows and sorts every data and DAG row by
(PieceCID digest bytes32, paddedSize uint64).
The zero-based index after sorting is part of the leaf. The tree is padded to
the next power of two with a fixed empty leaf and uses ordered, not commutative,
left/right hashes:
The adapter verifies membership in the client-committed rows, but it does not
enforce uniqueness between different indexes. The manifest builder rejects
duplicates because one physical sector piece may carry
multiple notifications. If the commitment contained the same (PieceCID, paddedSize) row more than once, those notifications could satisfy several
bitmap indexes without several stored copies. A provider cannot add a row that
the client did not commit, but the contract does not protect a client that
deliberately commits duplicate rows.
leaf = keccak256(abi.encode(keccak256("PoRepMarket.PieceSet.Leaf"), uint8(1), index, digest, size))
empty = keccak256(abi.encode(keccak256("PoRepMarket.PieceSet.Empty"), uint8(1)))
node = keccak256(abi.encode(keccak256("PoRepMarket.PieceSet.Node"), left, right))
manifestHash = keccak256(abi.encode(
keccak256("PoRepMarket.PieceSet.Commitment"), uint8(1), uint32(pieceCount), requestedSizeBytes, root
))
Each FIP-0109 notification payload is
abi.encode(uint256 dealId, uint32 pieceIndex, uint32 pieceCount, bytes32[] proof).
Activation requires every unique index and the exact requested byte total. This
adapter stores the authenticated PieceCID, sector number, padded size, and
minimum commitment epoch for every accepted index. Exact callback replay is a
no-op. A replay that changes any stored placement field is rejected.
Before sealing, SP tooling must fetch the complete manifest, recompute
manifestHash, verify that the padded sizes sum to requestedSizeBytes, and
prepare exactly one notification for each row. The ProveCommit or replica-update
call must set require_notification_success to true; otherwise a rejected
notification does not abort sector activation and there is no callback retry
path. The commitment proves membership in the client-selected piece set, not
retrievability, continued storage after initial placement, or physical
placement exclusivity between different deals.
PoRepMarket.refreshEvidenceStatus is permissionless. The caller supplies the
current deadline and partition for each consecutive sector selected by the
adapter's stored cursor:
struct SectorLocation {
int64 deadline;
int64 partition;
}
abi.encode(SectorLocation[] locations)The adapter verifies each stored sector through FIP-0112 and reads its nominal
expiration. It returns PARTIAL when the batch stops before the end of the
sector list. Partial progress does not replace the last completed status used by
settlement. A complete sweep publishes ACTIVE only when the accumulated sector
bytes equal the authenticated manifest receipt. One verified Faulty or Dead
sector publishes COVERED_BYTES_MISMATCH with zero covered bytes. A bad location
or actor-call failure reverts and preserves the previous completed status.
Operators read getRefreshState, getSectorCount, and getSectorNumber, resolve
the current sector locations off chain, estimate the exact refresh transaction,
and submit as many consecutive locations as fit. There is no contract-level
piece, sector, or batch cap. Repeat until EvidenceRefreshCompleted reports
ACTIVE or COVERED_BYTES_MISMATCH. getPiecePlacement and getSectorCoveredBytes let a new
operator reconstruct the placement inventory from chain state without the
original manifest host or a private database.
Stale or never-refreshed (INACTIVE) evidence makes settlement revert with
EvidenceTooStale() without advancing its stored settlement cursor. A later
completed ACTIVE sweep lets the same interval be retried. A coverage mismatch
from a Faulty or Dead sector settles the interval with zero payment and advances
the cursor, as the DataCap adapter does for terminated claims. A recovered
Faulty sector is paid again from the next ACTIVE sweep. This adapter relies on the current
pre-Re-Snap rule that sector content cannot change while the sector number stays
Active. It must be replaced or upgraded with authenticated current-content
membership before Re-Snap is enabled for these deals.
stateDiagram-v2
[*] --> Accepted: proposeDeal / reserve provider capacity
Accepted --> Accepted: create Validator and prepare FilecoinPay rail
Accepted --> Active: activateEvidence / commit covered capacity and start payment
Accepted --> Rejected: reject before rail creation / release reserved capacity
Accepted --> Terminated: terminate early / release reserved capacity
Active --> Active: refresh evidence and settle FilecoinPay rail
Active --> Finalized: finalize after service end / release committed capacity
Active --> Terminated: terminate early / release committed capacity
Rejected --> [*]
Finalized --> [*]
Terminated --> [*]
proposeDeal selects and reserves a provider offer.
proposeDealWithSpecificOffer lets an administrator
reserve a specific offer. Both create an ACCEPTED deal with a snapshot of the
selected provider, payment terms, promised SLIs, and reserved bytes.
While a deal is ACCEPTED, its client creates a Validator through
ValidatorFactory, approves it as a FilecoinPay
operator, and calls Validator.createRail. The client
posts DataCap through the deal's evidence adapter. The PoRep service then
submits claim evidence and calls activateEvidence.
Successful activation commits the covered capacity, moves the deal to ACTIVE,
sets its service window, and starts payment.
While the deal is ACTIVE, any caller can call
refreshEvidenceStatus to keep evidence current.
FilecoinPay calls Validator.validatePayment, which asks
PoRepMarket.validateDealSettlement for the accepted
amount and settlement epoch.
After the service end epoch, finalizeDeal terminates
the rail and releases committed capacity. terminateDeal
handles early termination from a prepared or active rail.
rejectAcceptedDeal releases reserved capacity for an
accepted deal that does not yet have a rail.
- Provider offers may change, but the provider, payment terms, promised SLIs, and reserved bytes copied into a deal do not follow later offer changes.
- Deal state, evidence status, and FilecoinPay rail status advance through separate contract calls.
- The evidence adapter owns evidence-specific facts.
PoRepMarketowns deal state, settlement decisions, and settlement accounting. - Provider capacity is reserved when the deal is created, committed using covered bytes on activation, and released on rejection, termination, or finalization.
- Validator implements FilecoinPay's validation interface, while
PoRepMarket.validateDealSettlementcomputes the accepted amount and updates settlement progress.
The justfile is the command reference. Run just --list to see
the commands supported by the current checkout. Deployment behavior is
implemented in script/deployment.ts.
Run just pre-push before pushing changes.