Data cap token design

Last edited
Aug 2, 2022 12:42 AM

This is an iteration on part of

, exploring the design of FIL+ data cap as a fungible token.

Work in progress

The verified registry actor will perform multiple significant tasks:

  • Account for the verifiers (notaries) and verified clients data cap allowances
  • Track the allocations of data cap to specified pieces
  • Track the claims of data cap by sectors

These need to be broken into three separate actors.

Verified Registry actor

This actor controls minting of the data cap token and tracks allocations of data cap to specific pieces (we could maybe even break out these claims to a new actor).

Verifiers have ability to mint tokens to clients. The verifiers’ minting allowance isn’t represented as a token.

When data cap is allocated, the registry transfers data cap balance to its own account. Its balance represents allocated but not yet claimed data cap.

When an allocation is claimed, the registry burns the associated data cap tokens.

type StoragePower = BigInt // bytes

type Allocation struct {
	Client Address // Could drop this if it's in the map key
  Provider Address // Optional
  Piece CID
	Size uint64
  TermMinimum uint64
	TermMaximum uint64
  Expiration Epoch
  AllOrNothing bool // Optional? Require atomic commit of entire piece

State {
	root_key: Address,

	// HAMT[Address]StoragePower
	// Maps verifiers to their minting allowance.
	verifiers: Cid,

	remove_data_cap_proposal_ids: Cid,

  // Allocations by client, then by ID.
	allocations: HAMT[Address]AMT[AllocID]Allocation

	next_allocation_id: uint64

// TODO: verifier delegates (like markets)


// Add minting allowance to a verifier.
// Callable only by root key.
fn add_verifier({address: Address, allowance: StoragePower})
fn remove_verifier(address: Address)

// Mints tokens to an address and decreases caller's minting allowance.
// Callable by verifier.
fn add_verified_client({address: Address, allowance: StoragePower})

// Burns tokens.
// Callable by root.
fn remove_verified_data_cap({...}) 

// Called by a verified client, or a delegate, to allocate some of its
// data cap balance to specific pieces and providers.
// Creates an allocation record and transfers the appropriate data cap
// token to this registry's balance.
// Replaces use_bytes.
fn allocate_data_cap({allocations: []DataCapAllocation})

// Cleans up all expired allocations for client.
// Data cap for removed allocations is transferred back to client's balance.
// Note that un-expired allocations cannot be revoked.
// Replaces restore_bytes.
fn revoke_allocations({client: Address})

// Called by storage miner actor to claim allocations for data
// provably committed to storage.
// Burns the associated data cap tokens from this actor's balance.
fn claim_allocations({sectors: ...}) -> {...}

Data cap fungible token actor

This token is in whole units only (representing bytes).

State {
	// HAMT[Address]BigInt
	// This replaces the old verifreg.verified_clients map
	balances: Cid

// Mints new data cap to a verified client.
// Callable only by the verified registry.
fn mint({address: Address, amount: TokenAmount})

// Burns data cap of the caller
// (Standard token method)
fn burn({amount: TokenAmount})

// Burns data cap of a verified client.
// Callable only by the verified registry.
// (Standard token method)
fn burn_from({address: Address, amount: TokenAmount})

// Transfers tokens between addresses.
// Callable only by the verified registry
// (Standard token method)
fn transfer_from({from: Address, to: Address, amount: TokenAmount})

// Other standard token read-only methods as usual.
// Other standard token mutation methods all abort.

Data cap claim non-fungible token actor

type Claim struct {
	Provider Address // Could drop this if it's in map key
	Client Address
	Piece CID
	Size uint64
  // This representation is for super-sector allocations.
	// It's unfortunately not compressible while retaining the association
	// between specific sectors and their range of the piece.
  // Non-overlapping ranges. Store sorted by RangeStart? Use an AMT?
	Sectors [] {
		Sector SectorID,
		RangeStart uint64, 
		RangeEnd uint64, 
	TermStart ChainEpoch // Epoch when piece was committed
  TermMinimum ChainEpoch
	TermMaximum ChainEpoch

State {
	// HAMT[Address]AMT[ClaimID]Claim
	// Claims by provider ID, then Claim ID.
  // Claim ID is inherited from allocation ID.
	claims: Cid
	// Number of current claims
	claim_count: uint64

// Mints a claim NFT.
// Callable only by verified registry.
fn mint({claim data...}) -> uint64

fn remove_expired_claims(...)

// TODO more

// Other standard nft read-only methods as usual.
// Other standard nft mutation methods all abort.

Design notes

  • The existing methods use a BigInt representing storage power bytes. Token APIs may expect a TokenAmount (18 dp) value. Internal state can use the byte values, but there’s possibility for confusion.