-
Notifications
You must be signed in to change notification settings - Fork 10
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
base: 02-21-link_payer_to_gateway_envelopes
Are you sure you want to change the base?
Add fee calculation helpers #537
Conversation
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 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. 📒 Files selected for processing (6)
WalkthroughThis pull request introduces a new currency module that defines a Changes
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)
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this 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 likeToMicroDollars
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
📒 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
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
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:
- 1: https://www.getfocal.ai/blog/cryptocurrency-transaction-monitoring
- 2: https://support.kraken.com/hc/en-us/articles/201988998-Decimal-precision-for-API-calculations
- 3: https://www.arkoselabs.com/explained/guide-to-cryptocurrency-security/
- 4: https://complyadvantage.com/insights/cryptocurrency-compliance-best-practices/
- 5: https://www.chainalysis.com/blog/introduction-to-cryptocurrency-exchange-compliance-crypto-businesses-2024/
- 6: https://www.investopedia.com/terms/c/chain-transactions-cryptocurrency.asp
- 7: https://www.kychub.com/blog/crypto-transaction-monitoring/
- 8: https://linkd.academy/en-us/library/glossary/token-decimals/
- 9: https://www.deloitte.com/mt/en/Industries/technology/perspectives/mt-article-cryptocurrency-security-standard-CCSS.html
- 10: https://www.gemini.com/cryptopedia/satoshi-value-gwei-to-ether-to-wei-converter-eth-gwei
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.
pkg/currency/currency.go
Outdated
func (p PicoDollar) String() string { | ||
return fmt.Sprintf("%g", p.toDollars()) | ||
} |
There was a problem hiding this comment.
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.
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 | |
} |
029ba8f
to
8832f00
Compare
There was a problem hiding this 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
andstorageDurationDays
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
📒 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.
8832f00
to
41d4e40
Compare
41d4e40
to
d9eb111
Compare
TL;DR
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?
currency
package withPicoDollar
type for precise financial calculationsHow to test?
Why make this change?
To provide a precise and reliable fee calculation system that:
Summary by CodeRabbit
New Features
Tests