Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add fee calculation helpers #537

Draft
wants to merge 1 commit into
base: 02-21-link_payer_to_gateway_envelopes
Choose a base branch
from

Conversation

neekolas
Copy link
Contributor

@neekolas neekolas commented Feb 21, 2025

TL;DR

  • Introduces a new fee calculation system with currency handling in picodollars (12 decimal precision).
  • Adds some interfaces for how we can get the current rates from the system. Hardcoded for now, but we'll move to something onchain.

Notes

Handling money in integers feels like the right way to do it, even if we have to convert from PicoDollars to MicroDollars (6 decimals) when we go onchain.

We will have to be careful about that conversion to make sure no one can get free messaging.

What changed?

  • Added a new currency package with PicoDollar type for precise financial calculations
  • Created a fee calculation system with components for message, storage, and congestion fees
  • Implemented a rates fetcher interface with a fixed-rate implementation
  • Added currency conversion utilities between dollars, picodollars, and microdollars
  • Included comprehensive test coverage for currency conversions

How to test?

  1. Run the currency package tests:
go test ./pkg/currency
  1. Verify currency conversions:
    • Test dollar to picodollar conversion
    • Validate string representation
    • Check microdollar conversion
  2. Test fee calculations with the fixed rate fetcher implementation

Why make this change?

To provide a precise and reliable fee calculation system that:

  • Handles currency with high precision (12 decimal places)
  • Supports different fee components (message, storage, congestion)
  • Allows for future rate adjustments through the rates fetcher interface
  • Prevents floating-point arithmetic errors in financial calculations

Summary by CodeRabbit

  • New Features

    • Introduced a high-precision currency system that supports seamless conversions between standard dollars and smaller units.
    • Added a fee calculation module that computes both base and congestion fees based on message characteristics.
    • Established a fixed fee schedule option and defined interfaces to streamline fee rate retrieval and processing.
  • Tests

    • Expanded testing to ensure accurate currency conversions and fee computations.

Copy link

coderabbitai bot commented Feb 21, 2025

Warning

Rate limit exceeded

@neekolas has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8832f00 and d9eb111.

📒 Files selected for processing (6)
  • pkg/currency/currency.go (1 hunks)
  • pkg/currency/currency_test.go (1 hunks)
  • pkg/fees/calculator.go (1 hunks)
  • pkg/fees/calculator_test.go (1 hunks)
  • pkg/fees/fixedRates.go (1 hunks)
  • pkg/fees/interface.go (1 hunks)

Walkthrough

This pull request introduces a new currency module that defines a PicoDollar type with conversion methods (from dollars, to dollars, to microdollars, and string representation) along with corresponding tests. Additionally, the fees package has been enhanced with a FeeCalculator for computing base and congestion fees by fetching rate data, a FixedRatesFetcher for fixed fee scenarios, and an interface file that defines the Rates structure and associated interfaces for fee calculation and rate fetching.

Changes

File(s) Change Summary
pkg/currency/…/currency.go
pkg/currency/…/currency_test.go
Added a new PicoDollar type with conversion methods (FromDollars, toDollars, ToMicroDollars, String) and test cases for conversion, formatting, and microdollar conversion.
pkg/fees/…/calculator.go
pkg/fees/…/fixedRates.go
pkg/fees/…/interface.go
Introduced fee calculation components: a FeeCalculator with methods for base and congestion fee computations, a FixedRatesFetcher for returning preset rates, and interface definitions (Rates, IRatesFetcher, IFeeCalculator) centralizing fee-related contracts.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FC as FeeCalculator
    participant RF as RatesFetcher

    Client->>FC: CalculateBaseFee(timestamp, size, duration)
    FC->>RF: GetRates(timestamp)
    RF-->>FC: Return Rates
    FC->>Client: Return computed base fee (PicoDollar)

    Client->>FC: CalculateCongestionFee(timestamp, congestionPercent)
    FC->>RF: GetRates(timestamp)
    RF-->>FC: Return Rates
    FC->>Client: Return computed congestion fee (PicoDollar)
Loading

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

This was referenced Feb 21, 2025
Copy link
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (9)
pkg/fees/fixedRates.go (1)

11-13: Add input validation for rates parameter.

Consider adding validation to ensure the rates parameter is not nil to prevent potential nil pointer dereferences.

 func NewFixedRatesFetcher(rates *Rates) *FixedRatesFetcher {
+	if rates == nil {
+		panic("rates cannot be nil")
+	}
 	return &FixedRatesFetcher{rates: rates}
 }
pkg/currency/currency_test.go (3)

9-16: Add more test cases for currency conversion.

Consider adding test cases for:

  • Edge cases (0, very large numbers)
  • Negative numbers
  • Numbers with more decimal places
 func TestConversion(t *testing.T) {
+	testCases := []struct {
+		name     string
+		dollars  float64
+		expected PicoDollar
+	}{
+		{"regular case", 1.25, 1250000000000},
+		{"zero", 0, 0},
+		{"large number", 1000000.50, 1000000500000000000},
+		{"negative", -1.25, -1250000000000},
+		{"many decimals", 1.23456789, 1234567890000},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			initial, err := FromDollars(tc.dollars)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, initial)
+
+			converted := initial.toDollars()
+			require.Equal(t, tc.dollars, converted)
+		})
+	}
 }

18-22: Add more test cases for string representation.

Consider adding test cases for:

  • Zero
  • Negative numbers
  • Numbers with trailing zeros
  • Large numbers
 func TestString(t *testing.T) {
+	testCases := []struct {
+		name     string
+		dollars  float64
+		expected string
+	}{
+		{"regular case", 1.25, "1.25"},
+		{"zero", 0, "0.00"},
+		{"negative", -1.25, "-1.25"},
+		{"trailing zeros", 1.50, "1.50"},
+		{"large number", 1000000.25, "1000000.25"},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			initial, err := FromDollars(tc.dollars)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, initial.String())
+		})
+	}
 }

24-28: Add more test cases for microdollars conversion.

Consider adding test cases for:

  • Zero
  • Negative numbers
  • Numbers that might cause overflow
  • Numbers with more decimal places
 func TestToMicroDollars(t *testing.T) {
+	testCases := []struct {
+		name     string
+		dollars  float64
+		expected int64
+	}{
+		{"regular case", 1.25, 1250000},
+		{"zero", 0, 0},
+		{"negative", -1.25, -1250000},
+		{"large number", 1000000.50, 1000000500000},
+		{"many decimals", 1.23456789, 1234568}, // rounds to nearest microdollar
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			initial, err := FromDollars(tc.dollars)
+			require.NoError(t, err)
+			require.Equal(t, tc.expected, initial.ToMicroDollars())
+		})
+	}
 }
pkg/fees/calculator.go (1)

9-15: Add input validation for ratesFetcher parameter.

Consider adding validation to ensure the ratesFetcher parameter is not nil to prevent potential nil pointer dereferences.

 func NewFeeCalculator(ratesFetcher IRatesFetcher) *FeeCalculator {
+	if ratesFetcher == nil {
+		panic("ratesFetcher cannot be nil")
+	}
 	return &FeeCalculator{ratesFetcher: ratesFetcher}
 }
pkg/fees/interface.go (1)

9-15: Add validation constraints in comments.

Consider documenting the expected constraints for each field:

  • Valid ranges (e.g., non-negative values)
  • Units of measurement
  • Any other business rules
 // Rates containt the cost for each fee component at a given message time.
 // Values in the rates struct are denominated in USD PicoDollars
 type Rates struct {
-	MessageFee    currency.PicoDollar // The flat per-message fee
-	StorageFee    currency.PicoDollar // The fee per byte-day of storage
-	CongestionFee currency.PicoDollar // The fee per unit of congestion
+	MessageFee    currency.PicoDollar // The flat per-message fee (must be non-negative)
+	StorageFee    currency.PicoDollar // The fee per byte-day of storage (must be non-negative)
+	CongestionFee currency.PicoDollar // The fee per unit of congestion (must be non-negative)
 }
pkg/currency/currency.go (3)

9-14: Add documentation for the PicoDollarsPerDollar constant.

While the type is well-documented, the constant lacks documentation explaining its purpose and significance.

Add a comment above the constant:

 // Picodollar is a type to represent currency with 12 decimal precision
 type PicoDollar int64
 
+// PicoDollarsPerDollar represents the number of picodollars in one dollar (1e12)
 const (
 	PicoDollarsPerDollar = 1e12
 )

26-29: Consider making ToDollars method exported.

The method name toDollars starts with a lowercase letter, making it unexported, while other conversion methods like ToMicroDollars are exported. This inconsistency might make the API less intuitive.

Consider making the method exported for consistency:

-func (p PicoDollar) toDollars() float64 {
+func (p PicoDollar) ToDollars() float64 {
 	return float64(p) / PicoDollarsPerDollar
 }

31-34: Extract magic number and improve documentation.

The method uses a magic number and could benefit from more detailed documentation explaining the conversion ratio.

Consider these improvements:

+const (
+	MicroDollarsPerDollar = 1e6
+)
+
+// ToMicroDollars converts PicoDollars to MicroDollars.
+// The conversion ratio is:
+// 1 Dollar = 1e12 PicoDollars = 1e6 MicroDollars
 func (p PicoDollar) ToMicroDollars() int64 {
-	return int64(p / 1e6)
+	return int64(p / (PicoDollarsPerDollar / MicroDollarsPerDollar))
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 635853b and 3a7522d.

📒 Files selected for processing (5)
  • pkg/currency/currency.go (1 hunks)
  • pkg/currency/currency_test.go (1 hunks)
  • pkg/fees/calculator.go (1 hunks)
  • pkg/fees/fixedRates.go (1 hunks)
  • pkg/fees/interface.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test (Node)
  • GitHub Check: Build pre-baked anvil-xmtpd
🔇 Additional comments (5)
pkg/fees/fixedRates.go (2)

5-9: LGTM! Clear documentation and focused design.

The comments effectively explain the purpose and use case of the FixedRatesFetcher struct.


15-17: LGTM! Clean implementation of the IRatesFetcher interface.

The method correctly implements the interface contract and follows Go conventions for unused parameters.

pkg/fees/interface.go (1)

17-21: LGTM! Well-documented and focused interface design.

The interface follows Go conventions and its purpose is clearly documented.

pkg/currency/currency.go (2)

1-7: LGTM! Clean package structure and imports.

The package name is appropriate, and imports are well-organized with no unnecessary dependencies.


9-38:

❓ Verification inconclusive

Verify precision requirements for currency handling.

The implementation uses 12 decimal places of precision (picodollars). Please verify if this level of precision aligns with the system requirements and industry standards for currency handling.


🌐 Web query:

What is the standard precision for handling cryptocurrency transactions?

💡 Result:

Cryptocurrency transaction precision varies significantly across different digital assets and fiat currencies, with standards shaped by both technical protocols and practical usability considerations. Here's a detailed breakdown:

Bitcoin (BTC) Precision

  • Maximum precision: 8 decimal places (1 satoshi = 0.00000001 BTC) [10]
  • Internal calculations: Up to 10 decimal places in APIs, though only 8 are usable on-chain [2]
  • Display standards: Often rounded to 5 decimal places for user interfaces [2]

Ethereum (ETH) and ERC-20 Tokens

  • Standard precision: 18 decimal places (1 wei = 0.000000000000000001 ETH)
  • Gwei subunit: Commonly used for gas fees (1 gwei = 0.000000001 ETH) [10]

Fiat Currency Handling

  • USD calculations:
    • Internal API precision: 4 decimal places
    • Display standard: 2 decimal places [2]
  • EUR/GBP: Typically follow similar patterns to USD

Token Variability

  • General range: Most tokens use 8–18 decimal places depending on their protocol design [8]
  • Examples:
    • Stablecoins like USDT: Often mirror fiat standards (2 displayed decimals)
    • High-supply tokens (e.g., SHIB): May use full 18-decimal precision

Implementation Considerations

  1. APIs vs On-Chain:

    • Exchanges like Kraken perform internal calculations at higher precision than displayed (e.g., BTC at 10 decimals internally vs 5 displayed) [2]
    • Blockchain networks enforce protocol-defined decimal limits
  2. Rounding Practices:

    • Transactions exceeding display decimals get truncated/rounded (e.g., $1.45406823 → $1.4541 in APIs) [2]
    • Mismatches require alignment between local calculations and API specifications

This tiered approach balances technical accuracy with human readability while maintaining consistency across wallets, exchanges, and blockchain networks.

Citations:


Confirm the chosen precision for PicoDollar conversion aligns with system and industry requirements.

  • The file uses 12 decimal places (PicoDollars) for representing currency.
  • Web sources indicate that cryptocurrency precisions vary widely (e.g., BTC uses 8 decimals and ETH uses 18 decimals), while fiat currencies often use far fewer decimals.
  • Please verify that the 12-decimal scheme meets your intended precision for both internal calculations and external displays, and consider whether any adjustments or additional tests (e.g., for rounding accuracy and overflow safety) are necessary.

Comment on lines 36 to 42
func (p PicoDollar) String() string {
return fmt.Sprintf("%g", p.toDollars())
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use %f format specifier for currency values.

The %g format specifier might use scientific notation for very large or small numbers, which is not ideal for currency display. Also, the method depends on the unexported toDollars method.

Consider these improvements:

 func (p PicoDollar) String() string {
-	return fmt.Sprintf("%g", p.toDollars())
+	return fmt.Sprintf("%.2f", p.ToDollars())  // Assumes ToDollars is exported as suggested earlier
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (p PicoDollar) String() string {
return fmt.Sprintf("%g", p.toDollars())
}
func (p PicoDollar) String() string {
return fmt.Sprintf("%.2f", p.ToDollars()) // Assumes ToDollars is exported as suggested earlier
}

@neekolas neekolas force-pushed the 02-21-add_fee_calculation_helpers branch 2 times, most recently from 029ba8f to 8832f00 Compare February 21, 2025 22:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
pkg/fees/calculator.go (3)

18-31: Solid input validation.

Checking for negative messageSize and storageDurationDays is good practice for error prevention. Consider clarifying how zero values should be treated in domain-specific documentation (if zero storage days are valid).


38-49: Effective overflow checks—consider modularizing.

The overflow checks for storage fees are sensible. As an optional improvement, extracting these checks into a helper function could reduce repetition and improve clarity. If fee values can grow significantly, exploring big.Int may provide safer arithmetic.


68-73: Congestion fee overflow checks—centralize logic.

Similar overflow handling is repeated here. Refactoring shared multiplication checks into a separate helper method could simplify updates if the logic evolves.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 029ba8f and 8832f00.

📒 Files selected for processing (5)
  • pkg/currency/currency.go (1 hunks)
  • pkg/currency/currency_test.go (1 hunks)
  • pkg/fees/calculator.go (1 hunks)
  • pkg/fees/fixedRates.go (1 hunks)
  • pkg/fees/interface.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/currency/currency_test.go
  • pkg/currency/currency.go
  • pkg/fees/interface.go
  • pkg/fees/fixedRates.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test (Node)
  • GitHub Check: Build pre-baked anvil-xmtpd
🔇 Additional comments (5)
pkg/fees/calculator.go (5)

1-9: Package and imports look good.

The package name and imports are clear and minimal, aligning with Go conventions.


10-16: Well-structured constructor.

Returning a pointer to FeeCalculator is appropriate. The code is concise and straightforward.


33-36: Good error handling on rate fetch.

Returning the error immediately ensures clarity. You might consider wrapping the error to provide more context, but this is optional.


52-61: Validate congestion range.

Restricting congestion to 0–100 seems reasonable in most scenarios. Double-check domain requirements to ensure this range meets all future needs.

Would you like me to scan the codebase for calls to CalculateCongestionFee to confirm no assumptions exceed 100%?


63-66: Consistent rate fetching.

This mirrors the approach in CalculateBaseFee, promoting consistency and preventing silent failures.

@neekolas neekolas force-pushed the 02-21-add_fee_calculation_helpers branch from 8832f00 to 41d4e40 Compare February 21, 2025 23:04
@neekolas neekolas force-pushed the 02-21-add_fee_calculation_helpers branch from 41d4e40 to d9eb111 Compare February 21, 2025 23:06
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant