Skip to content

Conversation

@pyropy
Copy link
Contributor

@pyropy pyropy commented Sep 29, 2025

Overview

Introduces FilBeam (operator) contract used for aggregating data set egress usage and settling of CDN-related payment rails inside the FWSS contract.

Core Functions

Usage Reporting (Controller Only)

recordUsageRollups(toEpoch, dataSetIds[], cdnBytes[], cacheMissBytes[])
terminateCDNPaymentRails(dataSetId)

Settlement (Public)

settleCDNPaymentRails(dataSetIds[])
settleCacheMissPaymentRails(dataSetIds[])

Management (Owner Only)

setFilBeamOperatorController(address)
setCDNRatePerByte(uint256)
setCacheMissRatePerByte(uint256)

Constructor Parameters

  • fwssAddress - FWSS contract address
  • cdnRatePerByte - Rate for CDN usage
  • cacheMissRatePerByte - Rate for cache-miss usage
  • filBeamOperatorController - Authorized reporter address

Data Tracking

Each dataset maintains:

  • Accumulated CDN and cache-miss egress usage
  • Latest reported epoch

Dependencies

Closes filbeam/roadmap#51

pyropy and others added 7 commits September 29, 2025 11:51
- Install @openzeppelin/contracts-upgradeable for upgradeable patterns
- Install @openzeppelin/contracts for proxy contracts
- Update .gitmodules and foundry.lock

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add custom error definitions in Errors.sol
- Add IFWSS interface for payment rail integration
- Add MockFWSS contract for testing payment settlements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add FilBeam contract with OpenZeppelin upgradeable patterns
- Implement Ownable, Initializable, and UUPSUpgradeable
- Support usage-based payments for CDN and cache miss scenarios
- Add proxy pattern for contract upgrades
- Replace constructor with initialize() function for upgradeable pattern
- Add _authorizeUpgrade() for UUPS upgrade authorization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add 29 test cases covering all contract functionality
- Test upgradeable contract patterns and proxy deployment
- Test ownership, initialization, and upgrade mechanisms
- Test usage reporting and payment settlement flows
- Include fuzz testing for usage reporting
- Test error conditions and access controls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Create deployment script with dynamic USDFC decimals detection
- Add USD per TiB to USDFC per byte conversion logic
- Support environment-based configuration
- Add comprehensive deployment logging
- Use ERC1967Proxy for upgradeable proxy pattern
- Include detailed documentation and usage examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Remove Counter.sol, Counter.t.sol, Counter.s.sol
- Clean up project to focus on FilBeam functionality
- Simplify codebase by removing template files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add SPEC.md with project requirements and design
- Update AGENTS.md with project context and instructions
- Document FilBeam usage-based payments functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@pyropy pyropy marked this pull request as draft September 29, 2025 09:57
@pyropy pyropy changed the title feat: implement FilBeam usage-based payments contract WIP: feat: implement FilBeam usage-based payments contract Sep 29, 2025
Add efficient batch processing for usage rollup reports with gas optimization
and atomic transaction guarantees.

- Add reportUsageRollupBatch method accepting arrays of dataset IDs, epochs, and usage data
- Refactor reportUsageRollup to use internal _reportUsageRollup for code deduplication
- Implement atomic batch processing (all succeed or all fail)
- Add comprehensive test suite with 10 new test cases covering:
  * Successful batch reporting across multiple datasets
  * Array length validation and error handling
  * Access control and input validation
  * Atomicity verification with partial failure scenarios
  * Integration with existing settlement functionality
- Update SPEC.md with detailed batch method documentation
- Maintain backward compatibility with existing single reporting method

Gas efficiency: Batch method reduces transaction costs for bulk operations
ideal for rollup workers reporting multiple usage periods.
Enable flexible pricing configuration with decimal precision support
for CDN and cache-miss rates.

- Add PRICE_DECIMALS environment variable to specify decimal places
- Update calculateUsdfcPerByte to handle scaled decimal inputs
- Support pricing like $12.50/TiB (1250 with 2 decimals)
- Maintain backward compatibility with whole number pricing
- Add comprehensive test suite with 9 test cases covering:
  * Whole number pricing (backward compatibility)
  * 2-3 decimal place scenarios
  * High precision pricing
  * Different token decimal configurations
  * Edge cases and fuzz testing
- Enhanced deployment logging with actual USD price display
- Update documentation with new parameter usage examples

Examples:
- $12.50/TiB: CDN_PRICE_USD_PER_TIB=1250 PRICE_DECIMALS=2
- $9.99/TiB: CDN_PRICE_USD_PER_TIB=999 PRICE_DECIMALS=2
- $10.00/TiB: CDN_PRICE_USD_PER_TIB=10 PRICE_DECIMALS=0
Add gas-efficient batch processing for CDN and cache-miss payment
rail settlements with atomic operation guarantees.

- Add settleCDNPaymentRailBatch method for bulk CDN settlements
- Add settleCacheMissPaymentRailBatch method for bulk cache-miss settlements
- Refactor single settlement methods to use internal functions
- Implement atomic batch processing (all succeed or all fail)
- Add comprehensive test suite with 9 new test cases covering:
  * Successful batch settlement across multiple datasets
  * Empty array handling
  * Error propagation and validation
  * Atomicity verification with partial failure scenarios
  * Integration with existing usage reporting
- Update SPEC.md with detailed batch settlement documentation
- Maintain backward compatibility with existing settlement methods

Gas efficiency: Batch methods significantly reduce transaction costs
for bulk settlement operations across multiple datasets.
Transform README from basic Foundry template to complete FilBeam
project documentation with deployment examples and API reference.

- Add project overview highlighting key features
- Add detailed deployment guide with environment variable configuration
- Add 3 deployment examples for different pricing scenarios:
  * Decimal pricing ($12.50 CDN, $15.75 cache miss)
  * Whole dollar pricing ($10 CDN, $15 cache miss)
  * High precision pricing ($9.995 CDN, $12.750 cache miss)
- Add pricing configuration reference table
- Add complete contract API documentation covering:
  * Usage reporting (single and batch methods)
  * Settlement operations (CDN and cache-miss rails)
  * Contract management (ownership and upgrades)
  * View functions for dataset information
- Add key concepts section explaining:
  * Batch operations and atomicity guarantees
  * Decimal pricing model and token compatibility
  * Independent settlement rail tracking

Provides developers with complete guidance for deploying and
interacting with FilBeam contracts.
Remove epochReported mapping and EpochAlreadyReported error. Epoch validation now uses only maxReportedEpoch comparison, reducing gas costs and contract complexity while maintaining duplicate epoch protection.
@pyropy pyropy requested a review from Copilot September 29, 2025 11:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Implements a comprehensive FilBeam smart contract system for usage-based payments in the Filecoin CDN ecosystem, featuring batch processing capabilities and flexible decimal pricing support.

  • Upgradeable UUPS proxy contract with independent CDN and cache-miss settlement rails
  • Batch operations for gas-efficient usage reporting and settlement processing
  • Decimal pricing system supporting fractional USD rates per TiB (e.g., $12.50, $9.99)

Reviewed Changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/FilBeam.sol Main contract implementation with batch operations and settlement logic
src/interfaces/IFWSS.sol Interface definition for FWSS contract integration
src/mocks/MockFWSS.sol Mock contract for testing FWSS functionality
src/Errors.sol Custom error definitions for contract validation
script/DeployFilBeam.s.sol Deployment script with decimal pricing calculations
test/FilBeam.t.sol Comprehensive test suite covering all contract functionality
test/DeployFilBeamDecimalPricing.t.sol Focused tests for decimal pricing calculations
README.md Updated documentation with API reference and deployment examples
SPEC.md Technical specification document

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Replace onlyOwner modifier with onlyFilBeamController for usage reporting and payment rail termination functions. Contract initialization now requires filBeamController address parameter. Owner role remains for contract upgrades only.
…d functionality

- Add zero-amount check in settlement functions to skip external calls when amount is 0
- Change function parameters from int256 to uint256 for usage reporting
- Update UsageReported event to use uint256 instead of int256
- Remove redundant isInitialized flag, use maxReportedEpoch == 0 for initialization check
- Add rate update mechanism with setCDNRatePerByte and setCacheMissRatePerByte functions
- Add CDNRateUpdated and CacheMissRateUpdated events
- Update getDataSetUsage to return 5 values instead of 6
- Update README.md and SPEC.md with API changes

Breaking changes:
- reportUsageRollup and reportUsageRollupBatch now accept uint256 instead of int256
- getDataSetUsage returns 5 values (removed isInitialized)
- UsageReported event emits uint256 values

All tests pass (64/64)
@pyropy pyropy requested a review from Copilot September 30, 2025 14:38
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@pyropy pyropy requested a review from Copilot September 30, 2025 15:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great progress! I have a few more comments to discuss, see below. We also need to decide whether the rollup reports should include fromEpoch, per the discussion above.

mapping(uint256 => DataSetUsage) public dataSetUsage;

event UsageReported(
uint256 indexed dataSetId, uint256 indexed epoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid this comment was not addressed yet.

Suggested change
uint256 indexed dataSetId, uint256 indexed epoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed
uint256 indexed dataSetId, uint256 indexed toEpoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed

It would be great to add fromEpoch to the event arguments as well.

Suggested change
uint256 indexed dataSetId, uint256 indexed epoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed
uint256 indexed dataSetId, uint256 indexed fromEpoch, uint256 indexed toEpoch, uint256 cdnBytesUsed, uint256 cacheMissBytesUsed

Comment on lines 196 to 218
/// @notice Updates the CDN rate per byte
/// @dev Can only be called by the contract owner. Rate must be greater than zero.
/// @param _cdnRatePerByte New CDN rate per byte in smallest token units
function setCDNRatePerByte(uint256 _cdnRatePerByte) external onlyOwner {
if (_cdnRatePerByte == 0) revert InvalidRate();

uint256 oldRate = cdnRatePerByte;
cdnRatePerByte = _cdnRatePerByte;

emit CDNRateUpdated(oldRate, _cdnRatePerByte);
}

/// @notice Updates the cache miss rate per byte
/// @dev Can only be called by the contract owner. Rate must be greater than zero.
/// @param _cacheMissRatePerByte New cache miss rate per byte in smallest token units
function setCacheMissRatePerByte(uint256 _cacheMissRatePerByte) external onlyOwner {
if (_cacheMissRatePerByte == 0) revert InvalidRate();

uint256 oldRate = cacheMissRatePerByte;
cacheMissRatePerByte = _cacheMissRatePerByte;

emit CacheMissRateUpdated(oldRate, _cacheMissRatePerByte);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you envision the process of changing the rates? Will we ever change only one rate at a time, or will we always change both of them?

Should there be a single method to change both rates atomically?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you envision the process of changing the rates?

I envision the process to be manua where transaction is sent from some kind of multisig after we have established new rate as a company.

Will we ever change only one rate at a time, or will we always change both of them?

I don't know but I did not exclude it as a possibility.

Should there be a single method to change both rates atomically?

We can replace two methods with a single one and in case we're changing only one rate we can submit old + new rate in transaction.

Comment on lines 227 to 237
function getDataSetUsage(uint256 dataSetId)
external
view
returns (
uint256 cdnAmount,
uint256 cacheMissAmount,
uint256 maxReportedEpoch,
uint256 lastCDNSettlementEpoch_,
uint256 lastCacheMissSettlementEpoch_
)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is going to consume this API, and what data do they need? Is it okay to get amounts waiting for settlement and not egress bytes consumed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. Maybe this method is not really needed at the moment.

@pyropy
Copy link
Contributor Author

pyropy commented Oct 9, 2025

Great progress! I have a few more comments to discuss, see below. We also need to decide whether the rollup reports should include fromEpoch, per the discussion above.

I don't know if we really need to submit fromEpoch expect for data validation. I don't think saving it would add much value to us if anything it would make things a bit more complex. WDYT about just submitting the value to make sure that data is valid but w/o saving it?

- Converting cdnRatePerByte and cacheMissRatePerByte to immutable variables
- Removing setCDNRatePerByte() and setCacheMissRatePerByte() functions
- Removing CDNRateUpdated and CacheMissRateUpdated events
- Updating tests and documentation to reflect immutable rate
Settlement methods now return early instead of reverting when:
- Dataset is not initialized (no usage recorded)
- No new usage to settle since last settlement

This change improves batch operations by allowing them to continue
processing other datasets even when some cannot be settled, making
the settlement functions idempotent and safer to call repeatedly.

Updated tests to verify the new non-reverting behavior and added
comprehensive test coverage for silent early returns.
…lify epoch tracking

- Remove epoch tracking from settlements (lastCDNSettlementEpoch, lastCacheMissSettlementEpoch)
- Simplify settlement events by removing fromEpoch and toEpoch parameters
- Add partial settlement support based on payment rail lockupFixed amounts
- Implement _getSettleableAmount() to calculate settleable amounts based on rail lockup
- Remove getDataSetUsage() getter method (use public mapping directly)
- Allow settlements whenever accumulated amounts exist, regardless of new usage reports
- Simplify rail validation by checking railId != 0 before settlement attempts
@pyropy
Copy link
Contributor Author

pyropy commented Oct 13, 2025

As previously discussed with @bajtos, we've made pricing rates immutable, refactored settlements to return early instead of reverting (enabling safer batch operations), and added partial settlement support.

Implementing partial settlements has simplified the code by removing epoch validation logic—we no longer check last settled or max reported epochs during settlement. Instead, payment rails can now be settled whenever there's an unsettled amount, regardless of whether there's been a recent usage rollup for the dataset.

cc @juliangruber

@pyropy pyropy requested a review from bajtos October 13, 2025 09:36
Copy link
Contributor

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I like how much simpler the new version is 👏🏻

I am afraid the following comment was not addressed yet, PTAL:
https://github.com/filbeam/contracts/pull/1/files/8cd48abeb8eeeb0066ce8f6f1228af6812c86028..e35cb62544f3e6c54945b7e55c5995385daee0a2#r2405384271

@pyropy pyropy requested a review from bajtos October 16, 2025 08:19
@pyropy pyropy requested a review from juliangruber October 17, 2025 10:14
/// @notice Settles CDN payment rails for multiple data sets
/// @dev Anyone can call this function to trigger settlement
/// @param dataSetIds Array of data set IDs to settle
function settleCDNPaymentRails(uint256[] calldata dataSetIds) external {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel free to ignore: What about adding settlePaymentRails() that calls both? I think we can add this later though

/// @notice Terminates CDN payment rails for a data set
/// @dev Can only be called by the FilBeam operator controller
/// @param dataSetId The data set ID to terminate payment rails for
function terminateCDNPaymentRails(uint256 dataSetId) external onlyFilBeamOperatorController {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fn names are confusing: terminateCDNPaymentRails() terminates the cdn and cache miss rails, while settleCDNPaymentRails() only settles the cdn rail, not the cache miss rail. When we mean that all types will be settled, can we find a new name, like terminateFilBeamPaymentRails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've picked terminateCDNPaymentRails method name is to keep it consitent with the one found in FWSS. However terminateFilBeamPaymentRails does sound better but that would also require us to change the contract call inside the terminator worker (which is not a big change as long as we don't forget to do it)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what to do here then, happy to follow your lead 👍

- Unify payment rail settlement into one internal method
- Move public methods above internal ones
@pyropy pyropy merged commit 42280b7 into main Oct 27, 2025
4 checks passed
@pyropy pyropy deleted the feat/filbeam-usage-based-payments-contract branch October 27, 2025 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a verifier contract that receives and exposes per-data set egress data

5 participants