Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add CCTP rebalancing to RFQ relayer #2073

Merged
merged 80 commits into from
Mar 4, 2024
Merged

Add CCTP rebalancing to RFQ relayer #2073

merged 80 commits into from
Mar 4, 2024

Conversation

dwasse
Copy link
Collaborator

@dwasse dwasse commented Feb 20, 2024

Description
Adds a Rebalance() func to the inventory.Manager interface, which is called upon witnessing a ClaimCompleted event. If configured, this triggers a CCTP bridge to rebalance the inventory.

Rebalances are triggered using two new parameters:

  • MaintenanceBalancePct: if the balance for a given chain goes below this value, a rebalance is triggered with this chain as the destination
  • InitialBalancePct: the balance threshold that will be targeted for the origin of a rebalance transaction

A RebalanceMethod enum is also added- currently only RebalanceMethodCCTP is implemented but this lays the groundwork for other future methods.

Summary by CodeRabbit

  • New Features
    • Introduced inventory management capabilities, including initiation, token approval management, and rebalancing for RFQ and CCTP bridges.
    • Added new configuration options for rebalancing settings, including method selection, balance percentages, and maximum rebalance amounts.
    • Implemented rebalancing logic to maintain optimal token balances across chains.
  • Enhancements
    • Enhanced token metadata to include chain ID and token address.
    • Improved error handling for fetching RFQ and CCTP bridge addresses.
  • Bug Fixes
    • Adjusted function signatures and test cases to align with the new features and enhancements.

@dwasse dwasse marked this pull request as draft February 20, 2024 19:52
@github-actions github-actions bot added go Pull requests that update Go code size/m labels Feb 20, 2024
Copy link
Contributor

coderabbitai bot commented Feb 20, 2024

Walkthrough

The recent changes focus on enhancing the inventory management system for a Request for Quote (RFQ) service. Key updates include the initiation of the inventory manager, improved token approval processes, and the introduction of a rebalancing method for token management. Additionally, configurations for RFQ and CCTP bridges have been refined, with specific adjustments made to rebalancing settings. These updates aim to streamline operations and ensure efficient token management across different blockchain networks.

Changes

Files Change Summaries
.../inventory/manager.go Added Start, Rebalance methods; updated ApproveAllTokens, NewInventoryManager; introduced rebalanceData; enhanced token initialization.
.../inventory/manager_test.go Updated tests; added TestGetRebalance.
.../relconfig/config.go, config_test.go Renamed Bridge to RFQAddress; added CCTPAddress, rebalancing settings; updated tests.
.../relconfig/getters.go Added GetCCTPAddress; updated functions for rebalance settings retrieval.
.../inventory/rebalance.go Introduced rebalancing functionality for CCTP contracts.

🐇✨
In a world of code and endless tweaks,
A rabbit hopped, fixing leaks.
Tokens balanced, bridges strong,
In harmony, where bytes belong.
Through chains and contracts, far and wide,
With every hop, progress implied.
🌟🐾

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>.
    • Generate unit-tests for this file.
  • 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 tests 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 8

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 7c5fcbf and d2acc79.
Files selected for processing (14)
  • services/rfq/e2e/setup_test.go (2 hunks)
  • services/rfq/relayer/inventory/manager.go (11 hunks)
  • services/rfq/relayer/quoter/quoter.go (3 hunks)
  • services/rfq/relayer/relapi/server.go (1 hunks)
  • services/rfq/relayer/relapi/suite_test.go (1 hunks)
  • services/rfq/relayer/relconfig/config.go (2 hunks)
  • services/rfq/relayer/relconfig/config_test.go (4 hunks)
  • services/rfq/relayer/relconfig/enum.go (1 hunks)
  • services/rfq/relayer/relconfig/getters.go (2 hunks)
  • services/rfq/relayer/relconfig/rebalancemethod_string.go (1 hunks)
  • services/rfq/relayer/service/handlers.go (2 hunks)
  • services/rfq/relayer/service/relayer.go (4 hunks)
  • services/rfq/relayer/service/statushandler.go (4 hunks)
  • services/rfq/relayer/service/suite_test.go (1 hunks)
Files skipped from review due to trivial changes (2)
  • services/rfq/relayer/relconfig/enum.go
  • services/rfq/relayer/relconfig/rebalancemethod_string.go
Additional comments: 26
services/rfq/relayer/relapi/server.go (2)
  • 62-62: The change from Bridge to RFQAddress in the chainCfg struct is correctly reflected here when initializing the chainListener. This update aligns with the PR objectives to enhance the RFQ relayer's functionality.
  • 66-66: Similarly, the update from Bridge to RFQAddress is correctly applied here for the creation of a new chain instance. This change is consistent with the overall goal of the PR and correctly implements the new configuration structure.
services/rfq/relayer/service/suite_test.go (2)
  • 72-72: The renaming of the Bridge field to RFQAddress for the originBackend chain configuration is correctly implemented. This change is in line with the PR's objectives to update configuration structures to support new functionality.
  • 75-75: The renaming of the Bridge field to RFQAddress for the destBackend chain configuration is also correctly implemented. This ensures consistency across the codebase and aligns with the PR's objectives.
services/rfq/relayer/service/statushandler.go (2)
  • 41-42: The renaming of RelayerAdress to RelayerAddress corrects a typo and improves code readability. This change enhances consistency across the codebase.
  • 133-137: The modification in the chain initialization logic to retrieve the RFQ address from the configuration is correctly implemented. This change aligns with the PR's objectives to utilize the updated RFQAddress field in the ChainConfig struct.
services/rfq/relayer/relconfig/config.go (1)
  • 52-55: The renaming of the Bridge field to RFQAddress and the addition of the CCTPAddress field in the ChainConfig struct are correctly implemented. These changes support the new functionality introduced in the PR and align with the objectives to enhance the RFQ relayer's functionality.
services/rfq/relayer/relapi/suite_test.go (2)
  • 82-82: The renaming of the Bridge field to RFQAddress for the originChainID configuration is correctly implemented. This change aligns with the PR's objectives and ensures consistency in the test suite setup.
  • 85-85: The renaming of the Bridge field to RFQAddress for the destChainID configuration is also correctly implemented. This ensures that the test suite accurately reflects the changes made in the main codebase.
services/rfq/relayer/service/relayer.go (3)
  • 65-75: The error handling for getting the RFQ address and chain client has been improved. This ensures that the relayer fails early if it cannot obtain necessary configuration details, enhancing the robustness of the initialization process.
  • 87-89: Refactoring the initialization of im (Inventory Manager) by directly passing the TransactionSubmitter to NewInventoryManager simplifies the setup process and reduces redundancy. This change improves code readability and maintainability.
  • 136-136: The modification to call ApproveAllTokens without passing the submitter simplifies the token approval process. This change likely reflects an internal update to how ApproveAllTokens manages submissions, potentially leading to cleaner and more encapsulated logic within the inventory.Manager implementation.
services/rfq/relayer/relconfig/config_test.go (2)
  • 17-18: The renaming of the Bridge field to RFQAddress and the addition of CCTPAddress in the ChainConfig struct are correctly reflected in the test setup. This change aligns with the PR's objective to enhance RFQ relayer functionality and support inventory rebalancing through the CCTP bridge.

Also applies to: 34-35, 53-54

  • 71-96: The test cases for GetRFQAddress and GetCCTPAddress have been correctly updated to use the new fields. These tests ensure that the configuration getters return the expected values for RFQ and CCTP addresses, which is crucial for the correct operation of the relayer in different chain environments.
services/rfq/e2e/setup_test.go (1)
  • 200-200: The update to use RFQAddress instead of the Bridge field in the setupRelayer function of the IntegrationSuite correctly reflects the changes made in the PR. This ensures that the end-to-end tests are aligned with the new configuration structure and can accurately test the relayer's functionality with the correct RFQ addresses.

Also applies to: 212-212

services/rfq/relayer/relconfig/getters.go (4)
  • 90-102: The GetRFQAddress function has been correctly updated to retrieve the RFQ address for a given chain ID. The error handling and type assertion are properly implemented.
  • 104-114: The GetCCTPAddress function is correctly implemented to retrieve the CCTP address for a given chain ID, with appropriate error handling and type assertion.
  • 382-396: The GetMaintenanceBalancePct function correctly retrieves the maintenance balance percentage for a given chain and token. The error handling and validation for a positive percentage value are properly implemented.
  • 398-412: The GetInitialBalancePct function correctly retrieves the initial balance percentage for a given chain and token. The error handling and validation for a positive percentage value are properly implemented.
services/rfq/relayer/quoter/quoter.go (3)
  • 241-243: The refactoring to use GetRFQAddress for obtaining the destination RFQ address improves encapsulation and error handling. This change ensures that the method for retrieving RFQ addresses is consistent and centralized, which is beneficial for maintainability and potential future changes to how addresses are managed.
  • 279-281: Similar to the destination RFQ address, using GetRFQAddress for obtaining the origin RFQ address maintains consistency and improves error handling. This approach centralizes the retrieval of RFQ addresses, making the codebase more maintainable and adaptable to changes.
  • 297-298: The assignment of OriginFastBridgeAddress and DestFastBridgeAddress using the obtained RFQ addresses in the quote object is a logical step following the retrieval of these addresses. This ensures that the quotes are correctly associated with their respective bridge addresses, which is crucial for the RFQ process. It's good to see that the changes are consistently applied throughout the quote generation logic.
services/rfq/relayer/inventory/manager.go (4)
  • 21-21: The addition of the cctp contract import is necessary for the new rebalancing functionality. Ensure that the cctp package is correctly implemented and tested, as it plays a crucial role in the rebalancing process.
  • 66-71: The addition of txSubmitter, cctpContracts, and chainID fields to inventoryManagerImpl supports the new rebalancing functionality. Ensure that these fields are properly initialized and used securely, especially in multi-threaded contexts given the presence of mutex locks in the implementation.
  • 134-135: The addition of chainID and addr fields to tokenMetadata struct is necessary for the rebalancing logic. It's important to ensure that these fields are correctly populated during token initialization to avoid issues in rebalancing operations.
  • 268-299: The Rebalance method's implementation is central to the new functionality. It's important to ensure that the rebalancing logic is correct and efficient. Specifically, the switch-case handling different rebalancing methods should be extensible for future methods. Also, validate the correctness of the rebalance decision logic and the error handling throughout the method.

Comment on lines 316 to 332
// handleClaimCompleted handles the claim completed status and marks the claim as completed.
// Step 9: ClaimCompleted
//
// Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token
// is needed, and trigger it on the inventory manager if so.
func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) {
err = q.Inventory.Rebalance(ctx, int(request.Transaction.DestChainId), request.Transaction.DestToken)
if err != nil {
return fmt.Errorf("could not rebalance: %w", err)
}

err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimPending)
if err != nil {
return fmt.Errorf("could not update request status: %w", err)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The handleClaimCompleted function correctly implements the logic to trigger rebalancing upon a claim completion event. However, there's a potential issue with the final status update to reldb.ClaimPending after rebalancing. This status might not accurately reflect the completion of the claim process. Consider updating the status to reldb.ClaimCompleted or a similar status that accurately represents the completion of the claim process.

- err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimPending)
+ err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimCompleted)

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.

Suggested change
// handleClaimCompleted handles the claim completed status and marks the claim as completed.
// Step 9: ClaimCompleted
//
// Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token
// is needed, and trigger it on the inventory manager if so.
func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) {
err = q.Inventory.Rebalance(ctx, int(request.Transaction.DestChainId), request.Transaction.DestToken)
if err != nil {
return fmt.Errorf("could not rebalance: %w", err)
}
err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimPending)
if err != nil {
return fmt.Errorf("could not update request status: %w", err)
}
return nil
}
// handleClaimCompleted handles the claim completed status and marks the claim as completed.
// Step 9: ClaimCompleted
//
// Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token
// is needed, and trigger it on the inventory manager if so.
func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) {
err = q.Inventory.Rebalance(ctx, int(request.Transaction.DestChainId), request.Transaction.DestToken)
if err != nil {
return fmt.Errorf("could not rebalance: %w", err)
}
err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimCompleted)
if err != nil {
return fmt.Errorf("could not update request status: %w", err)
}
return nil
}

Comment on lines 363 to 380
// GetRebalanceMethod returns the rebalance method for the given chain and token.
func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceMethod, err error) {
chainConfig, ok := c.Chains[chainID]
if !ok {
return method, fmt.Errorf("no chain config for chain %d", chainID)
}
tokenConfig, ok := chainConfig.Tokens[token]
if !ok {
return method, fmt.Errorf("no token config for chain %d and token %s", chainID, token)
}
switch tokenConfig.RebalanceMethod {
case "cctp":
return RebalanceMethodCCTP, nil
case "native":
return RebalanceMethodNative, nil
}
return RebalanceMethodNone, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The GetRebalanceMethod function correctly retrieves the rebalance method for a given chain and token. However, consider handling the case where the RebalanceMethod is not recognized (other than "cctp" or "native") more explicitly by returning an error indicating an unsupported rebalance method.

  switch tokenConfig.RebalanceMethod {
  case "cctp":
      return RebalanceMethodCCTP, nil
  case "native":
      return RebalanceMethodNative, nil
+ default:
+     return RebalanceMethodNone, fmt.Errorf("unsupported rebalance method: %s", tokenConfig.RebalanceMethod)
  }

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.

Suggested change
// GetRebalanceMethod returns the rebalance method for the given chain and token.
func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceMethod, err error) {
chainConfig, ok := c.Chains[chainID]
if !ok {
return method, fmt.Errorf("no chain config for chain %d", chainID)
}
tokenConfig, ok := chainConfig.Tokens[token]
if !ok {
return method, fmt.Errorf("no token config for chain %d and token %s", chainID, token)
}
switch tokenConfig.RebalanceMethod {
case "cctp":
return RebalanceMethodCCTP, nil
case "native":
return RebalanceMethodNative, nil
}
return RebalanceMethodNone, nil
}
// GetRebalanceMethod returns the rebalance method for the given chain and token.
func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceMethod, err error) {
chainConfig, ok := c.Chains[chainID]
if !ok {
return method, fmt.Errorf("no chain config for chain %d", chainID)
}
tokenConfig, ok := chainConfig.Tokens[token]
if !ok {
return method, fmt.Errorf("no token config for chain %d and token %s", chainID, token)
}
switch tokenConfig.RebalanceMethod {
case "cctp":
return RebalanceMethodCCTP, nil
case "native":
return RebalanceMethodNative, nil
default:
return RebalanceMethodNone, fmt.Errorf("unsupported rebalance method: %s", tokenConfig.RebalanceMethod)
}
return RebalanceMethodNone, nil
}

@@ -76,7 +76,7 @@ func (r *Relayer) requestToHandler(ctx context.Context, req reldb.QuoteRequest)
// no more need for deadline middleware now, we already relayed.
qr.handlers[reldb.RelayCompleted] = r.gasMiddleware(qr.handleRelayCompleted)
qr.handlers[reldb.ProvePosted] = qr.handleProofPosted
// TODO: we probably want a claim complete state once we've seen that event on chain
qr.handlers[reldb.ClaimCompleted] = qr.handleClaimCompleted
Copy link
Contributor

Choose a reason for hiding this comment

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

The addition of a handler for the ClaimCompleted state is a significant enhancement. It's important to ensure that this new handler is thoroughly tested, especially since it's part of the quote request handling logic which is critical to the application's functionality.

Would you like me to help with writing tests for the new handleClaimCompleted method?

Comment on lines +94 to +99
// RebalanceMethod is the method to use for rebalancing.
RebalanceMethod string `yaml:"rebalance_method"`
// MaintenanceBalancePct is the percentage of the total balance under which a rebalance will be triggered.
MaintenanceBalancePct float64 `yaml:"maintenance_balance_pct"`
// InitialBalancePct is the percentage of the total balance to retain when triggering a rebalance.
InitialBalancePct float64 `yaml:"initial_balance_pct"`
Copy link
Contributor

Choose a reason for hiding this comment

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

The addition of RebalanceMethod, MaintenanceBalancePct, and InitialBalancePct fields in the TokenConfig struct for rebalancing settings is a significant enhancement. It's important to ensure that these new settings are validated to prevent configuration errors that could impact the rebalancing functionality.

Would you like me to help with implementing validation for these new configuration settings?

Comment on lines 372 to 410
func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *rebalanceData) (err error) {
// fetch the corresponding CCTP contract
contract, ok := i.cctpContracts[rebalance.dest]
if !ok {
contractAddr, err := i.cfg.GetCCTPAddress(rebalance.origin)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.origin)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
contract, err = cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient)
if err != nil {
return fmt.Errorf("could not get cctp: %w", err)
}
i.cctpContracts[rebalance.dest] = contract
}

// perform rebalance by calling sendCircleToken()
_, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.originMetadata.chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
tx, err = contract.SendCircleToken(
transactor,
i.relayerAddress,
big.NewInt(int64(rebalance.destMetadata.chainID)),
rebalance.originMetadata.addr,
rebalance.amount,
0, // TODO: inspect
[]byte{}, // TODO: inspect
)
if err != nil {
return nil, fmt.Errorf("could not send circle token: %w", err)
}
return tx, nil
})
if err != nil {
return fmt.Errorf("could not submit CCTP rebalance: %w", err)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The rebalanceCCTP method performs the actual rebalancing operation using the CCTP bridge. Ensure that the contract interaction logic is secure and correctly handles potential errors. The use of placeholders (0 and []byte{}) in the SendCircleToken call should be reviewed and replaced with actual values if necessary.

Consider replacing the placeholders in the SendCircleToken call with actual values or configurations, as these might impact the functionality of the rebalancing operation.

Copy link

codecov bot commented Feb 20, 2024

Codecov Report

Attention: Patch coverage is 16.55172% with 605 lines in your changes are missing coverage. Please review.

Project coverage is 48.07063%. Comparing base (fdef3fa) to head (3285fe6).
Report is 45 commits behind head on master.

Files Patch % Lines
services/rfq/relayer/inventory/manager.go 28.68526% 168 Missing and 11 partials ⚠️
services/rfq/relayer/inventory/rebalance.go 0.00000% 175 Missing ⚠️
services/rfq/relayer/relconfig/getters.go 19.09091% 85 Missing and 4 partials ⚠️
services/rfq/relayer/reldb/base/rebalance.go 0.00000% 57 Missing ⚠️
services/rfq/relayer/service/relayer.go 0.00000% 24 Missing ⚠️
services/rfq/relayer/service/statushandler.go 0.00000% 18 Missing ⚠️
services/rfq/relayer/reldb/base/model.go 5.88235% 16 Missing ⚠️
services/rfq/relayer/reldb/db.go 13.33333% 13 Missing ⚠️
ethergo/example/deploymanager.go 0.00000% 9 Missing ⚠️
services/rfq/relayer/relapi/server.go 35.71429% 6 Missing and 3 partials ⚠️
... and 5 more
Additional details and impacted files
@@                 Coverage Diff                 @@
##              master       #2073         +/-   ##
===================================================
- Coverage   48.80073%   48.07063%   -0.73011%     
===================================================
  Files            370         372          +2     
  Lines          27225       27807        +582     
  Branches          83          83                 
===================================================
+ Hits           13286       13367         +81     
- Misses         12554       13039        +485     
- Partials        1385        1401         +16     
Flag Coverage Δ
agents 47.44950% <ø> (-0.15407%) ⬇️
cctp-relayer 63.00863% <ø> (ø)
ethergo 58.68858% <54.16667%> (+0.23644%) ⬆️
explorer 24.80122% <ø> (ø)
omnirpc 53.23194% <ø> (ø)
promexporter 74.80916% <ø> (ø)
rfq 32.24060% <15.26391%> (-5.73717%) ⬇️
scribe 52.02703% <ø> (-0.18427%) ⬇️
stiprelayer 3.33333% <ø> (ø)
tools 21.98661% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 0

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between d2acc79 and 4ad03dc.
Files selected for processing (1)
  • services/rfq/relayer/relconfig/rebalancemethod_string.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • services/rfq/relayer/relconfig/rebalancemethod_string.go

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 4

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 4ad03dc and 7c0cf7e.
Files selected for processing (3)
  • services/rfq/relayer/inventory/manager.go (11 hunks)
  • services/rfq/relayer/relconfig/config_test.go (4 hunks)
  • services/rfq/relayer/service/statushandler.go (4 hunks)
Files skipped from review as they are similar to previous changes (2)
  • services/rfq/relayer/relconfig/config_test.go
  • services/rfq/relayer/service/statushandler.go
Additional comments: 4
services/rfq/relayer/inventory/manager.go (4)
  • 21-21: The addition of the cctp import is necessary for the new rebalancing functionality. This change aligns with the PR objectives to enhance inventory management using the CCTP bridge.
  • 66-71: The addition of txSubmitter, cctpContracts, and chainID fields to inventoryManagerImpl supports the new rebalancing functionality. Ensure these fields are properly initialized and used securely throughout the class.
  • 149-157: The modification of the NewInventoryManager function signature to include the txSubmitter parameter is necessary for the rebalancing functionality. Ensure that all instantiations of NewInventoryManager are updated accordingly.
  • 134-135: The update to initializeTokens to include chain ID and token address in tokenMetadata is a positive change for clarity and functionality. Ensure that these new fields are utilized correctly throughout the codebase, especially in the context of rebalancing.

Also applies to: 461-471

Comment on lines 203 to 245
if address != chain.EthAddress && token.startAllowance.Cmp(big.NewInt(0)) == 0 {
chainID := chainID // capture func literal
address := address // capture func literal
// init an approval in submitter. Note: in the case where submitter hasn't finished from last boot, this will double submit approvals unfortanutely
_, err = submitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
erc20, err := ierc20.NewIERC20(address, backendClient)

erc20, err := ierc20.NewIERC20(address, backendClient)
if err != nil {
return fmt.Errorf("could not get erc20: %w", err)
}

// init an approval for RFQ bridge in submitter. Note: in the case where submitter hasn't finished from last boot, this will double submit approvals unfortanutely
_, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
rfqAddr, err := i.cfg.GetRFQAddress(chainID)
if err != nil {
return nil, fmt.Errorf("could not get erc20: %w", err)
return nil, fmt.Errorf("could not get rfq address: %w", err)
}
tx, err = erc20.Approve(transactor, common.HexToAddress(rfqAddr), abi.MaxInt256)
if err != nil {
return nil, fmt.Errorf("could not approve rfq: %w", err)
}
return tx, nil
})
if err != nil {
return fmt.Errorf("could not submit RFQ approval: %w", err)
}

approveAmount, err := erc20.Approve(transactor, common.HexToAddress(i.cfg.Chains[chainID].Bridge), abi.MaxInt256)
// approve CCTP bridge, if configured
_, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
cctpAddr, err := i.cfg.GetCCTPAddress(chainID)
if err != nil {
return nil, fmt.Errorf("could not approve: %w", err)
return nil, fmt.Errorf("could not get cctp address: %w", err)
}
tx, err = erc20.Approve(transactor, common.HexToAddress(cctpAddr), abi.MaxInt256)
if err != nil {
return nil, fmt.Errorf("could not approve cctp: %w", err)
}

return approveAmount, nil
return tx, nil
})
if err != nil {
return fmt.Errorf("could not submit approval: %w", err)
return fmt.Errorf("could not submit CCTP approval: %w", err)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [190-242]

The updated logic in ApproveAllTokens to handle approvals for both RFQ and CCTP bridges is crucial for the new functionality. Ensure that error handling is robust, especially in the context of transaction submissions. Consider adding more detailed logging for each step to aid in debugging and monitoring.

Consider enhancing error logging with more context, such as the chain ID and token address, to improve traceability in case of failures.

Comment on lines 269 to 303
// Rebalance checks whether a given token should be rebalanced, and executes the rebalance if necessary.
// Note that if there are multiple tokens whose balance is below the maintenance balance, only the lowest balance
// will be rebalanced.
func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token common.Address) error {
method, err := i.cfg.GetRebalanceMethod(chainID, token.Hex())
if err != nil {
return fmt.Errorf("could not get rebalance method: %w", err)
}
if method == relconfig.RebalanceMethodNone {
return nil
}

err = i.refreshBalances(ctx)
if err != nil {
return fmt.Errorf("could not refresh balances: %w", err)
}

rebalance, err := i.getRebalance(chainID, token)
if err != nil {
return fmt.Errorf("could not get rebalance: %w", err)
}
if rebalance == nil {
return nil
}

//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
return i.rebalanceCCTP(ctx, rebalance)
case relconfig.RebalanceMethodNative:
return fmt.Errorf("native rebalance method not implemented")
default:
return fmt.Errorf("unknown rebalance method: %s", method)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The Rebalance method's implementation is key to the new inventory rebalancing functionality. Ensure that the contract interaction logic within rebalanceCCTP is secure and correctly handles potential errors. The placeholders (0 and []byte{}) in the SendCircleToken call should be reviewed and replaced with actual values if necessary.

Consider replacing the placeholders in the SendCircleToken call with actual values or configurations, as these might impact the functionality of the rebalancing operation.

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 0

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 782b869 and 6195502.
Files selected for processing (2)
  • services/rfq/go.mod (1 hunks)
  • services/rfq/relayer/relconfig/config.go (4 hunks)
Files skipped from review as they are similar to previous changes (2)
  • services/rfq/go.mod
  • services/rfq/relayer/relconfig/config.go

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 2

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 6195502 and 4055168.
Files selected for processing (24)
  • ethergo/README.md (1 hunks)
  • ethergo/example/README.md (1 hunks)
  • ethergo/example/counter/counter.abigen.go (3 hunks)
  • ethergo/example/counter/counter.contractinfo.json (1 hunks)
  • ethergo/example/counter/counter.sol (1 hunks)
  • ethergo/example/deploymanager.go (1 hunks)
  • ethergo/listener/db/doc.go (1 hunks)
  • ethergo/listener/db/service.go (1 hunks)
  • ethergo/listener/db/store.go (1 hunks)
  • ethergo/listener/export_test.go (2 hunks)
  • ethergo/listener/listener.go (9 hunks)
  • ethergo/listener/listener_test.go (1 hunks)
  • ethergo/listener/suite_test.go (1 hunks)
  • services/rfq/e2e/rfq_test.go (10 hunks)
  • services/rfq/go.mod (4 hunks)
  • services/rfq/relayer/chain/chain.go (1 hunks)
  • services/rfq/relayer/inventory/rebalance.go (1 hunks)
  • services/rfq/relayer/relapi/server.go (3 hunks)
  • services/rfq/relayer/reldb/base/model.go (3 hunks)
  • services/rfq/relayer/reldb/base/store.go (3 hunks)
  • services/rfq/relayer/reldb/db.go (4 hunks)
  • services/rfq/relayer/reldb/db_test.go (1 hunks)
  • services/rfq/relayer/service/relayer.go (6 hunks)
  • services/rfq/testutil/deployers.go (3 hunks)
Files skipped from review due to trivial changes (1)
  • ethergo/listener/db/doc.go
Files skipped from review as they are similar to previous changes (8)
  • services/rfq/go.mod
  • services/rfq/relayer/chain/chain.go
  • services/rfq/relayer/inventory/rebalance.go
  • services/rfq/relayer/relapi/server.go
  • services/rfq/relayer/reldb/base/model.go
  • services/rfq/relayer/reldb/base/store.go
  • services/rfq/relayer/service/relayer.go
  • services/rfq/testutil/deployers.go
Additional comments: 36
services/rfq/relayer/reldb/db_test.go (1)
  • 13-13: The change from reldb.ErrNoLatestBlockForChainID to listener.ErrNoLatestBlockForChainID aligns with the refactoring effort to centralize or reorganize error definitions. Ensure that listener.ErrNoLatestBlockForChainID is consistently used across the project for similar error scenarios.
ethergo/example/deploymanager.go (1)
  • 12-30: The introduction of the DeployManager struct, its constructor function NewDeployManager, and the method GetCounter effectively utilize modern Go features, such as generics, and adhere to best practices. The use of t.Helper() in test-related functions is appropriate and enhances test output readability.
ethergo/listener/export_test.go (1)
  • 23-36: The refactoring of the TestChainListenerArgs struct and the NewTestChainListener function, including the reordering of fields and the use of the db package for the Store field, enhances clarity and maintainability. Ensure that all tests relying on these constructs are updated accordingly to accommodate these changes.
ethergo/example/counter/counter.sol (1)
  • 8-35: The updates to the Counter contract, including the addition of events (Incremented, Decremented, IncrementedByUser), the deployBlock variable, and event emissions within functions, adhere to Solidity best practices. These changes enhance contract observability and auditability while efficiently using storage for the deployBlock variable.
ethergo/listener/listener_test.go (1)
  • 11-57: The TestListenForEvents function effectively simulates contract interactions and tests the event listening functionality using a wait group for concurrency management. Consider addressing the TODO comment about checking for a timeout to enhance the robustness of the test.
ethergo/listener/db/store.go (1)
  • 1-71: The implementation of the Store struct and its methods PutLatestBlock and LatestBlockForChain effectively utilize gorm for ORM operations, with appropriate error handling and adherence to best practices in database schema management. The custom error ErrNoLatestBlockForChainID and the use of a custom namer for field names enhance clarity and consistency.
ethergo/listener/suite_test.go (1)
  • 1-157: The introduction of a test suite for the listener component and the NewSqliteStore function for creating a SQLite store for testing purposes demonstrate a structured approach to testing. This enhances maintainability and readability, and the focused test cases with appropriate assertions align with Go testing practices.
ethergo/README.md (1)
  • 32-41: The updates to the README.md, including the removal of references to the chain directory and the addition of the listener directory, accurately reflect significant architectural changes in the project. Keeping the documentation aligned with the codebase is crucial for clarity and user guidance.
ethergo/listener/listener.go (6)
  • 7-8: The replacement of the fastbridge package with the db2 package for database operations indicates a significant change in how database interactions are handled. Ensure that all instances where fastbridge was used are properly updated to use db2, and that db2 provides all necessary functionalities that were previously covered by fastbridge.
  • 39-44: The chainListener struct now includes a db2.ChainListenerDB type for the store field and an initialBlock parameter. This change is crucial for integrating the new database interactions and handling the initial block from which the listener starts. Ensure that the initialBlock parameter is correctly utilized throughout the listener's logic, especially in methods that deal with block ranges and event fetching.
  • 59-66: The NewChainListener function signature has been updated to include the initialBlock parameter and use the new db2.ChainListenerDB type. This change is necessary for initializing the listener with the correct starting block and database interface. Verify that all calls to NewChainListener throughout the codebase have been updated to pass the correct initialBlock value and that the db2.ChainListenerDB is properly initialized before being passed to this function.
Verification successful

Given the lack of output from the initial scripts, it's necessary to take a different approach to verify the implementation of the changes mentioned in the review comment. Let's broaden the search to capture any usage of NewChainListener and the initialization of db2.ChainListenerDB in the codebase.


To accurately verify the implementation of the changes mentioned in the review comment, let's correct the syntax error in the previous rg commands and re-run the search without specifying an incorrect flag.


The verification process has confirmed that the NewChainListener function signature has been correctly updated to include the initialBlock parameter and use the db2.ChainListenerDB type across the codebase. Calls to NewChainListener correctly pass the initialBlock value, and db2.ChainListenerDB is properly initialized before being passed to this function. Based on the provided evidence, the changes mentioned in the review comment have been successfully implemented.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for calls to NewChainListener and verify the correct initialBlock value is passed.
ast-grep --lang go --pattern $'NewChainListener($_, $_, $_, $_, $_)'
# Ensure db2.ChainListenerDB is properly initialized before being passed to NewChainListener.
ast-grep --lang go --pattern $'db2.ChainListenerDB($_)'

Length of output: 126



Script:

#!/bin/bash
# Broaden the search for occurrences of NewChainListener to ensure no usage is missed.
rg "NewChainListener" --go
# Search for any initialization or reference to db2.ChainListenerDB to verify proper usage.
rg "db2.ChainListenerDB" --go

Length of output: 914



Script:

#!/bin/bash
# Correct the search for occurrences of NewChainListener.
rg "NewChainListener"
# Correct the search for any initialization or reference to db2.ChainListenerDB.
rg "db2.ChainListenerDB"

Length of output: 1752

* 99-99: The `Address` method now returns the `address` directly instead of calling `contract.Address()`. This simplification is beneficial for performance and readability. However, ensure that the `address` field is always correctly set during the listener's initialization to prevent potential nil pointer dereferences or incorrect address usage. * 128-128: The modification in the `doPoll` method to return `nil` when the `latestBlock` is the same as the `startBlock` is a logical optimization to avoid unnecessary processing. This change should help in reducing the load on the system for chains with slow block times. Ensure that this logic does not interfere with the listener's ability to catch up on missed events in case of temporary downtimes or delays. * 185-191: > 📝 **NOTE** > This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [165-207]

The getMetadata method has been refactored to remove deployBlock retrieval and to handle block comparisons based on startBlock and initialBlock. This change aligns with the introduction of the initialBlock parameter and simplifies the metadata fetching process. Verify that the removal of deployBlock retrieval does not affect any functionalities that relied on this information and that the block comparisons are correctly implemented to ensure the listener starts from the appropriate block.

services/rfq/relayer/reldb/db.go (4)
  • 8-9: The addition of the math/big package import is necessary for handling big integers in the Rebalance struct, specifically for the OriginAmount field. This is a good practice for dealing with large numeric values in blockchain-related operations. Ensure that all operations involving OriginAmount properly utilize the math/big package's functionalities to handle big integers correctly.
  • 19-28: The introduction of StoreRebalance and UpdateRebalanceStatus methods in the Writer interface is crucial for managing rebalance actions in the database. Ensure that the implementations of these methods correctly handle database transactions, including error handling and rollback mechanisms in case of failures, to maintain data integrity.
  • 41-42: The HasPendingRebalance method in the Reader interface is a valuable addition for checking the existence of pending rebalances for given chain IDs. This method can help in decision-making processes related to initiating new rebalances. Ensure that this method efficiently queries the database to minimize performance impacts, especially when dealing with a large number of rebalance records.
  • 157-207: The definition of the Rebalance struct and the RebalanceStatus enum introduces a structured way to represent rebalance actions and their statuses within the database. This structured approach enhances code readability and maintainability. Ensure that the RebalanceID field is correctly used as a unique identifier for rebalance actions and that the RebalanceStatus enum covers all possible states of a rebalance action.
ethergo/example/counter/counter.contractinfo.json (1)
  • 1-1: The addition of event declarations (Incremented, Decremented, IncrementedByUser) and a new public immutable variable deployBlock in the Counter contract enhances the contract's functionality by providing event logging capabilities and access to the deployment block number. These changes are well-aligned with Solidity best practices for contract development. Ensure that the events are emitted correctly in the contract's functions and that the deployBlock variable is properly initialized in the constructor.
services/rfq/e2e/rfq_test.go (9)
  • 31-31: The introduction of the cctpDeployManager field in the IntegrationSuite struct is a key part of setting up the CCTP rebalancing feature. This change aligns with the PR objectives.
  • 38-38: Adding a store field of type reldb.Service to the IntegrationSuite struct is crucial for testing the rebalancing feature, as it likely involves database interactions to track pending rebalances.
  • 76-76: The initialization of cctpDeployManager within the SetupTest method is correctly done, ensuring that the CCTP rebalancing feature can be tested effectively.
  • 107-110: The logic for adjusting token amounts to account for decimals is a good practice, ensuring that tests accurately reflect real-world scenarios.
  • 114-116: The process of minting initial USDC to the relayer wallet on the destination chain is correctly implemented. However, ensure that error handling is robust and that the transaction is properly awaited for confirmation.
  • 147-147: The logic to verify that the destination amount is greater than the requested RFQ amount is crucial for ensuring the quote meets the user's needs. This check is well-placed within the flow of the test.
  • 161-168: The use of Bridge function to simulate a bridging transaction is correctly implemented. Pay attention to the subtraction of a small amount (10_000_000) from realRFQAmount for DestAmount, ensuring a realistic test scenario.
  • 213-213: The balance check to verify the rebalance has occurred by comparing the balance against a threshold is a smart way to validate the rebalancing logic. However, ensure that the threshold calculation (1.5 * realStartAmount) aligns with the expected rebalancing logic.
Verification successful

The verification process confirms that the balance threshold calculation indeed aligns with the rebalancing logic, using a multiplier of 1.5 times the realStartAmount, as mentioned in the review comment. This aligns with the expected behavior described in the comment.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that the balance threshold calculation aligns with rebalancing logic
grep "balanceThresh" services/rfq/e2e/rfq_test.go

Length of output: 196

* 234-241: Checking for pending rebalances on both the destination and origin chains is a critical part of validating the rebalancing feature. Ensure that the `HasPendingRebalance` method is correctly implemented and returns accurate results.
Verification successful

The rg command successfully identified the HasPendingRebalance method within the codebase, specifically in services/rfq/relayer/reldb/db.go and its implementation in services/rfq/relayer/reldb/base/rebalance.go. This method is designed to check if there is a pending rebalance for the given chain IDs, aligning with the usage observed in the services/rfq/e2e/rfq_test.go file. The method accepts a context and a variadic number of uint64 chain IDs, returning a boolean indicating the presence of a pending rebalance and an error if applicable.

Given this information, the review comment regarding the criticality of checking for pending rebalances on both the destination and origin chains and ensuring the HasPendingRebalance method's correct implementation and accurate results is validated by the method's presence and its usage in the codebase.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify implementation of HasPendingRebalance method
ast-grep --lang go --pattern $'HasPendingRebalance($_, $_)'

Length of output: 60



Script:

#!/bin/bash
# Broaden the search for HasPendingRebalance method
ast-grep --lang go --pattern $'func HasPendingRebalance'

Length of output: 402


Script:

#!/bin/bash
# Fallback: Use rg to search for HasPendingRebalance in the codebase
rg 'HasPendingRebalance'

Length of output: 968

ethergo/example/README.md (3)
  • 198-224: The documentation for creating a typecast getter DeployManager is clear and provides a good example of how to avoid naked casts of contract handles. This enhances code readability and maintainability.
  • 241-246: The example provided for the GetCounter method in the DeployManager demonstrates a practical use of the typecast getter. This method simplifies the process of retrieving contract handles and improves code clarity.
  • 249-269: The addition of a test to ensure that dependencies are correctly listed in each deployer is a good practice. This test helps maintain the integrity of the deployment process by verifying that all dependencies are accounted for.
ethergo/example/counter/counter.abigen.go (5)
  • 34-34: The ABI string has been updated to include a constructor, new events, and functions. Ensure that this ABI string accurately reflects the latest version of the smart contract it represents. Changes in the ABI string can significantly affect how the Go code interacts with the contract on the blockchain.
  • 217-232: The DeployBlock function has been added as a new method to retrieve the block number at which the contract was deployed. This is a useful addition for tracking contract deployment without external tools. Ensure that the Solidity contract has a corresponding deployBlock method that returns the expected uint256 type.
  • 373-438: The addition of CounterDecrementedIterator and related logic for handling the Decremented event is correctly implemented following Go and Ethereum binding standards. This allows for efficient iteration over logs of the Decremented event. Ensure that the event names and types match those defined in the smart contract.
  • 507-572: Similar to the CounterDecrementedIterator, the CounterIncrementedIterator is well-implemented for the Incremented event. This consistency in handling different events is crucial for maintainability and ease of understanding the code. Again, verify that the event details align with the smart contract.
  • 641-706: The CounterIncrementedByUserIterator for the IncrementedByUser event follows the established pattern for event iterators. This addition is important for applications that need to track increments made by specific users. As with other events, ensure the contract's event definitions are consistent with these bindings.

@@ -14,23 +15,27 @@ import (
"github.com/synapsecns/sanguine/ethergo/backends/anvil"
"github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner"
"github.com/synapsecns/sanguine/ethergo/signer/wallet"
cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil"
Copy link
Contributor

Choose a reason for hiding this comment

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

Tip

Codebase Verification

The verification process did not find the cctp-relayer/testutil dependency in the services/cctp-relayer/go.mod file. This suggests that the dependency necessary for the new rebalancing feature might not be properly declared, potentially leading to issues with dependency resolution.

Analysis chain

The addition of the cctpTest package import is necessary for the new rebalancing feature. Ensure that the go.mod file is updated accordingly to include this new dependency.



---

<!--
[observations] The script successfully located multiple `go.mod` files across the repository, indicating a multi-module setup. The relevant `go.mod` file for our verification is `services/cctp-relayer/go.mod`, given the context of the import path `github.com/synapsecns/sanguine/services/cctp-relayer/testutil`.
[decomposition] The next step is to verify the presence of the `cctp-relayer/testutil` dependency in the `services/cctp-relayer/go.mod` file, as it's the most relevant to the import statement in question.
[analysis] Since the `go.mod` file within the `services/cctp-relayer` directory is directly related to the import path mentioned, checking this file for the dependency is the correct approach. This will confirm whether the dependency has been properly declared.
[actions] Plan to generate a new script to check the `services/cctp-relayer/go.mod` file for the `cctp-relayer/testutil` dependency.
-->

</details>

<details>
<summary>Scripts Executed</summary>

The following scripts were executed for the analysis:



---

Script:
```shell
#!/bin/bash
# Verify if go.mod contains cctp-relayer/testutil as a dependency
grep "cctp-relayer/testutil" go.mod

Length of output: 75



Script:

#!/bin/bash
# Find the go.mod file in the repository
fd go.mod

# Assuming go.mod is found, re-run the grep command with the correct path

Length of output: 489



Script:

#!/bin/bash
# Verify if services/cctp-relayer/go.mod contains cctp-relayer/testutil as a dependency
grep "cctp-relayer/testutil" services/cctp-relayer/go.mod

Length of output: 57

Comment on lines +303 to +324
for _, rpcAddr := range []string{i.originBackend.RPCAddress(), i.destBackend.RPCAddress()} {
anvilClient, err := anvil.Dial(i.GetTestContext(), rpcAddr)
i.NoError(err)

// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
go func() {
for {
select {
case <-i.GetTestContext().Done():
return
case <-time.After(time.Second * 4):
// increase time by 30 mintutes every second, should be enough to get us a fastish e2e test
// we don't need to worry about deadline since we're only doing this on origin
err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30)
i.NoError(err)

// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
}
}
}
}()
}()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The refactoring to handle multiple RPC addresses in a loop is a significant improvement in terms of code maintainability and readability. Ensure that error handling within the loop is robust and that the loop correctly terminates under all expected conditions. Additionally, consider abstracting this logic into a helper function for better readability and reuse, as previously suggested.

- // Current loop implementation
+ // Suggestion: Abstract this logic into a helper function for improved maintainability

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.

Suggested change
for _, rpcAddr := range []string{i.originBackend.RPCAddress(), i.destBackend.RPCAddress()} {
anvilClient, err := anvil.Dial(i.GetTestContext(), rpcAddr)
i.NoError(err)
// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
go func() {
for {
select {
case <-i.GetTestContext().Done():
return
case <-time.After(time.Second * 4):
// increase time by 30 mintutes every second, should be enough to get us a fastish e2e test
// we don't need to worry about deadline since we're only doing this on origin
err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30)
i.NoError(err)
// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
}
}
}
}()
}()
}
for _, rpcAddr := range []string{i.originBackend.RPCAddress(), i.destBackend.RPCAddress()} {
anvilClient, err := anvil.Dial(i.GetTestContext(), rpcAddr)
i.NoError(err)
go func() {
for {
select {
case <-i.GetTestContext().Done():
return
case <-time.After(time.Second * 4):
// Suggestion: Abstract this logic into a helper function for improved maintainability
// increase time by 30 mintutes every second, should be enough to get us a fastish e2e test
// we don't need to worry about deadline since we're only doing this on origin
err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30)
i.NoError(err)
// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
}
}
}()
}

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 4

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 4055168 and ecbdc0e.
Files selected for processing (3)
  • services/rfq/relayer/inventory/export_test.go (1 hunks)
  • services/rfq/relayer/inventory/manager.go (14 hunks)
  • services/rfq/relayer/inventory/rebalance.go (1 hunks)
Files skipped from review as they are similar to previous changes (2)
  • services/rfq/relayer/inventory/export_test.go
  • services/rfq/relayer/inventory/rebalance.go
Additional comments: 2
services/rfq/relayer/inventory/manager.go (2)
  • 69-74: The addition of txSubmitter, rebalanceManagers, and chainID fields to the inventoryManagerImpl struct is a crucial part of supporting the new rebalancing functionality. However, it's important to ensure that these fields are properly initialized and used throughout the class to avoid null pointer exceptions or logic errors.

Please verify that these new fields are correctly initialized in all relevant constructors or initialization methods and that their usage throughout the class is consistent and error-free.

  • 269-330: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [260-296]

The ApproveAllTokens method has been enhanced to manage approvals for both RFQ and CCTP bridges. This is a critical update for ensuring that token approvals are correctly managed across different bridges. However, it's important to ensure that error handling is robust and that the method does not inadvertently approve tokens more than once, especially in cases where the submitter hasn't finished from the last boot (as noted in the comments). Consider adding checks to prevent double submissions of approvals.

Please verify that the logic to prevent double submissions of approvals is robust and test this method thoroughly to ensure it behaves as expected under various conditions.

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 4

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between ecbdc0e and d8deade.
Files selected for processing (1)
  • services/rfq/relayer/inventory/manager.go (14 hunks)
Additional comments: 1
services/rfq/relayer/inventory/manager.go (1)
  • 69-74: The introduction of txSubmitter, rebalanceManagers, and chainID fields in the inventoryManagerImpl struct is a necessary update to support the new rebalancing functionality. However, ensure that these fields are properly initialized and used throughout the implementation to avoid any null pointer exceptions or logic errors.
Verification successful

Based on the provided context and the output from the shell scripts, it's clear that the introduction of txSubmitter, rebalanceManagers, and chainID fields in the inventoryManagerImpl struct is part of a larger update to support new rebalancing functionality. The output shows that these fields are properly initialized in the NewInventoryManager function and are used throughout the implementation, which aligns with the requirements mentioned in the review comment.

The initialization of these fields is confirmed by their presence in the NewInventoryManager function signature and the assignment within this function. The usage of these fields throughout the implementation is evident from the numerous references to txSubmitter, rebalanceManagers, and chainID across different files and contexts, indicating their integration into the broader system logic.

Given this information, it can be concluded that the introduction and utilization of these fields have been correctly implemented as per the requirements outlined in the review comment.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that all new fields are properly initialized in the NewInventoryManager function.
rg --type go 'NewInventoryManager\('
# Verify that these fields are used correctly throughout the implementation.
rg --type go 'txSubmitter|rebalanceManagers|chainID'

Length of output: 363239

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 6

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between d8deade and 996a82b.
Files selected for processing (5)
  • services/rfq/relayer/inventory/manager.go (14 hunks)
  • services/rfq/relayer/inventory/manager_test.go (1 hunks)
  • services/rfq/relayer/relconfig/config.go (4 hunks)
  • services/rfq/relayer/relconfig/config_test.go (5 hunks)
  • services/rfq/relayer/relconfig/getters.go (5 hunks)
Files skipped from review as they are similar to previous changes (3)
  • services/rfq/relayer/inventory/manager_test.go
  • services/rfq/relayer/relconfig/config_test.go
  • services/rfq/relayer/relconfig/getters.go
Additional comments: 4
services/rfq/relayer/relconfig/config.go (3)
  • 47-50: The conversion of DBSelectorIntervalSeconds to DBSelectorInterval and the introduction of RebalanceInterval as time.Duration types are positive changes for clarity and type safety. However, ensure that all places in the codebase that use these fields are updated to work with time.Duration instead of integers representing seconds.
Verification successful

The conversion of DBSelectorIntervalSeconds to DBSelectorInterval and the introduction of RebalanceInterval as time.Duration types have been correctly implemented across the codebase. The presence of getter functions for both configurations, along with default values and their usage in logical parts of the application, confirms that these fields are handled as time.Duration appropriately. This ensures clarity and type safety, as intended by the changes. All places in the codebase that use these fields appear to have been updated to work with time.Duration instead of integers representing seconds, as per the verification results.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Search for usages of DBSelectorInterval and RebalanceInterval to ensure they are correctly handled as time.Duration
rg --type go 'DBSelectorInterval|RebalanceInterval' services/rfq/

Length of output: 1664

* 55-58: Renaming the `Bridge` field to `RFQAddress` and adding the `CCTPAddress` field in the `ChainConfig` struct are significant for clarity and functionality. Ensure that all references to the old `Bridge` field are updated to `RFQAddress` across the codebase. * 85-86: The addition of `CCTPStartBlock` in the `ChainConfig` struct is crucial for specifying the start block for CCTP event listening. Ensure that this new field is properly utilized where event listening is set up.
Verification successful

The examination of GetCCTPStartBlock usage in rebalance.go confirms that the CCTPStartBlock field is indeed utilized within the system, specifically in obtaining an initialBlock for a given chainID. This usage strongly suggests its role in setting up event listening, aligning with the intent of the original comment. While the direct evidence of event listening setup was not explicitly shown, the context of its usage supports the intended utilization of specifying the start block for CCTP event listening.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify CCTPStartBlock is utilized in event listening setup
rg --type go 'CCTPStartBlock' services/rfq/

Length of output: 681



Script:

#!/bin/bash
# Examine the usage of GetCCTPStartBlock in rebalance.go
rg --type go 'GetCCTPStartBlock' services/rfq/relayer/inventory/rebalance.go

Length of output: 132

services/rfq/relayer/inventory/manager.go (1)
  • 69-74: The introduction of txSubmitter, rebalanceManagers, and chainID fields in the inventoryManagerImpl struct is a crucial update for supporting the new rebalancing functionality. Ensure that these fields are properly initialized and used throughout the class to avoid any null pointer exceptions or logic errors.

Comment on lines +99 to +106
// RebalanceMethod is the method to use for rebalancing.
RebalanceMethod string `yaml:"rebalance_method"`
// MaintenanceBalancePct is the percentage of the total balance under which a rebalance will be triggered.
MaintenanceBalancePct float64 `yaml:"maintenance_balance_pct"`
// InitialBalancePct is the percentage of the total balance to retain when triggering a rebalance.
InitialBalancePct float64 `yaml:"initial_balance_pct"`
// MaxRebalanceAmount is the maximum amount to rebalance in human-readable units.
MaxRebalanceAmount string `yaml:"max_rebalance_amount"`
Copy link
Contributor

Choose a reason for hiding this comment

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

The introduction of rebalancing settings (RebalanceMethod, MaintenanceBalancePct, InitialBalancePct, and MaxRebalanceAmount) in the TokenConfig struct is a key enhancement. It's crucial to implement validation for these new settings to prevent configuration errors that could impact rebalancing functionality. Consider adding a validation function in the Config struct or as part of the LoadConfig function to ensure these settings are within acceptable ranges.

Would you like me to help with implementing validation for these new configuration settings?

Comment on lines +157 to +188
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}

i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
db: db,
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}

err := i.initializeTokens(ctx, cfg)
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}

// TODO: move
go func() {
return &i, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The NewInventoryManager function has been significantly modified to accommodate the new rebalancing functionality. While the changes are necessary, the function now has a high number of parameters, which could impact readability and maintainability. Consider refactoring this function to reduce its complexity, possibly by introducing a struct to encapsulate some of the parameters or by using a builder pattern.

- func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
+ // Consider introducing a struct to encapsulate some parameters or using a builder pattern.

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.

Suggested change
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}
i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
db: db,
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}
err := i.initializeTokens(ctx, cfg)
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}
// TODO: move
go func() {
return &i, nil
// Consider introducing a struct to encapsulate some parameters or using a builder pattern.
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}
i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}
return &i, nil

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 6

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between 996a82b and b0f9d22.
Files selected for processing (1)
  • services/rfq/relayer/inventory/rebalance.go (1 hunks)
Additional comments: 3
services/rfq/relayer/inventory/rebalance.go (3)
  • 23-28: The RebalanceData struct is well-defined and encapsulates all necessary information for a rebalance action. It's clear and straightforward, making it easy to understand the data involved in a rebalance operation.
  • 30-36: The RebalanceManager interface is concise and captures the essential operations needed for rebalancing: starting the manager and executing a rebalance action. This design adheres to the interface segregation principle, ensuring that the interface is specific to the rebalance functionality.
  • 57-68: The constructor function newRebalanceManagerCCTP is correctly implemented, initializing maps for cctpContracts and chainListeners to avoid nil map assignment errors. This is a good practice for ensuring the struct is ready for use immediately after construction.

Comment on lines +38 to +54
type rebalanceManagerCCTP struct {
// cfg is the config
cfg relconfig.Config
// handler is the metrics handler
handler metrics.Handler
// chainClient is an omnirpc client
chainClient submitter.ClientFetcher
// txSubmitter is the transaction submitter
txSubmitter submitter.TransactionSubmitter
// cctpContracts is the map of cctp contracts (used for rebalancing)
cctpContracts map[int]*cctp.SynapseCCTP
// relayerAddress contains the relayer address
relayerAddress common.Address
// chainListeners is the map of chain listeners for CCTP events
chainListeners map[int]listener.ContractListener
// db is the database
db reldb.Service
Copy link
Contributor

Choose a reason for hiding this comment

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

The rebalanceManagerCCTP struct is well-organized, with clear field names indicating their purposes. However, consider documenting each field to improve code readability and maintainability, especially for complex fields like cctpContracts and chainListeners.

+ // cfg holds the configuration settings for the rebalance manager.
  cfg relconfig.Config
+ // handler is responsible for managing metrics.
  handler metrics.Handler
+ // chainClient fetches clients for different chains.
  chainClient submitter.ClientFetcher
+ // txSubmitter submits transactions to the blockchain.
  txSubmitter submitter.TransactionSubmitter
+ // cctpContracts maps chain IDs to their respective CCTP contracts.
  cctpContracts map[int]*cctp.SynapseCCTP
+ // relayerAddress is the Ethereum address of the relayer.
  relayerAddress common.Address
+ // chainListeners maps chain IDs to their respective contract listeners.
  chainListeners map[int]listener.ContractListener
+ // db provides access to the relational database.
  db reldb.Service

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.

Suggested change
type rebalanceManagerCCTP struct {
// cfg is the config
cfg relconfig.Config
// handler is the metrics handler
handler metrics.Handler
// chainClient is an omnirpc client
chainClient submitter.ClientFetcher
// txSubmitter is the transaction submitter
txSubmitter submitter.TransactionSubmitter
// cctpContracts is the map of cctp contracts (used for rebalancing)
cctpContracts map[int]*cctp.SynapseCCTP
// relayerAddress contains the relayer address
relayerAddress common.Address
// chainListeners is the map of chain listeners for CCTP events
chainListeners map[int]listener.ContractListener
// db is the database
db reldb.Service
type rebalanceManagerCCTP struct {
// cfg holds the configuration settings for the rebalance manager.
cfg relconfig.Config
// handler is responsible for managing metrics.
handler metrics.Handler
// chainClient fetches clients for different chains.
chainClient submitter.ClientFetcher
// txSubmitter submits transactions to the blockchain.
txSubmitter submitter.TransactionSubmitter
// cctpContracts maps chain IDs to their respective CCTP contracts.
cctpContracts map[int]*cctp.SynapseCCTP
// relayerAddress is the Ethereum address of the relayer.
relayerAddress common.Address
// chainListeners maps chain IDs to their respective contract listeners.
chainListeners map[int]listener.ContractListener
// db provides access to the relational database.
db reldb.Service

Comment on lines +70 to +94
func (c *rebalanceManagerCCTP) Start(ctx context.Context) (err error) {
err = c.initContracts(ctx)
if err != nil {
return fmt.Errorf("could not initialize contracts: %w", err)
}

err = c.initListeners(ctx)
if err != nil {
return fmt.Errorf("could not initialize listeners: %w", err)
}

g, _ := errgroup.WithContext(ctx)
for cid := range c.cfg.Chains {
// capture func literal
chainID := cid
g.Go(func() error {
return c.listen(ctx, chainID)
})
}

err = g.Wait()
if err != nil {
return fmt.Errorf("error listening to contract: %w", err)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The Start method correctly initializes contracts and listeners before starting to listen for CCTP events. The use of errgroup for concurrent listening across multiple chains is a good practice for performance. However, ensure proper error handling and logging within the goroutines to avoid silent failures.

+ // Add logging for goroutine errors
  g.Go(func() error {
    err := c.listen(ctx, chainID)
+   if err != nil {
+       logger.Errorf("Error listening on chain %d: %v", chainID, err)
+   }
    return err
  })

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.

Suggested change
func (c *rebalanceManagerCCTP) Start(ctx context.Context) (err error) {
err = c.initContracts(ctx)
if err != nil {
return fmt.Errorf("could not initialize contracts: %w", err)
}
err = c.initListeners(ctx)
if err != nil {
return fmt.Errorf("could not initialize listeners: %w", err)
}
g, _ := errgroup.WithContext(ctx)
for cid := range c.cfg.Chains {
// capture func literal
chainID := cid
g.Go(func() error {
return c.listen(ctx, chainID)
})
}
err = g.Wait()
if err != nil {
return fmt.Errorf("error listening to contract: %w", err)
}
return nil
func (c *rebalanceManagerCCTP) Start(ctx context.Context) (err error) {
err = c.initContracts(ctx)
if err != nil {
return fmt.Errorf("could not initialize contracts: %w", err)
}
err = c.initListeners(ctx)
if err != nil {
return fmt.Errorf("could not initialize listeners: %w", err)
}
g, _ := errgroup.WithContext(ctx)
for cid := range c.cfg.Chains {
// capture func literal
chainID := cid
g.Go(func() error {
// Add logging for goroutine errors
err := c.listen(ctx, chainID)
if err != nil {
logger.Errorf("Error listening on chain %d: %v", chainID, err)
}
return err
})
}
err = g.Wait()
if err != nil {
return fmt.Errorf("error listening to contract: %w", err)
}
return nil

Comment on lines +97 to +113
func (c *rebalanceManagerCCTP) initContracts(ctx context.Context) (err error) {
for chainID := range c.cfg.Chains {
contractAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
contract, err := cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient)
if err != nil {
return fmt.Errorf("could not get cctp: %w", err)
}
c.cctpContracts[chainID] = contract
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The initContracts method demonstrates good error handling with detailed error messages. However, consider parallelizing the initialization of contracts for different chains to improve startup performance, similar to how listening is handled concurrently.

+ // Consider using errgroup for parallel initialization

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.

Suggested change
func (c *rebalanceManagerCCTP) initContracts(ctx context.Context) (err error) {
for chainID := range c.cfg.Chains {
contractAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
contract, err := cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient)
if err != nil {
return fmt.Errorf("could not get cctp: %w", err)
}
c.cctpContracts[chainID] = contract
}
return nil
func (c *rebalanceManagerCCTP) initContracts(ctx context.Context) (err error) {
// Consider using errgroup for parallel initialization
for chainID := range c.cfg.Chains {
contractAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
contract, err := cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient)
if err != nil {
return fmt.Errorf("could not get cctp: %w", err)
}
c.cctpContracts[chainID] = contract
}
return nil
}

Comment on lines +116 to +136
func (c *rebalanceManagerCCTP) initListeners(ctx context.Context) (err error) {
for chainID := range c.cfg.GetChains() {
cctpAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
initialBlock, err := c.cfg.GetCCTPStartBlock(chainID)
if err != nil {
return fmt.Errorf("could not get cctp start block: %w", err)
}
chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), initialBlock, c.handler)
if err != nil {
return fmt.Errorf("could not get chain listener: %w", err)
}
c.chainListeners[chainID] = chainListener
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the initContracts method, initListeners also has good error handling. Again, parallelizing the initialization of listeners for different chains could enhance performance.

+ // Consider using errgroup for parallel initialization of listeners

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.

Suggested change
func (c *rebalanceManagerCCTP) initListeners(ctx context.Context) (err error) {
for chainID := range c.cfg.GetChains() {
cctpAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
initialBlock, err := c.cfg.GetCCTPStartBlock(chainID)
if err != nil {
return fmt.Errorf("could not get cctp start block: %w", err)
}
chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), initialBlock, c.handler)
if err != nil {
return fmt.Errorf("could not get chain listener: %w", err)
}
c.chainListeners[chainID] = chainListener
}
return nil
func (c *rebalanceManagerCCTP) initListeners(ctx context.Context) (err error) {
// Consider using errgroup for parallel initialization of listeners
for chainID := range c.cfg.GetChains() {
cctpAddr, err := c.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not get cctp address: %w", err)
}
chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
initialBlock, err := c.cfg.GetCCTPStartBlock(chainID)
if err != nil {
return fmt.Errorf("could not get cctp start block: %w", err)
}
chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), initialBlock, c.handler)
if err != nil {
return fmt.Errorf("could not get chain listener: %w", err)
}
c.chainListeners[chainID] = chainListener
}
return nil
}

Comment on lines 139 to 184
func (c *rebalanceManagerCCTP) Execute(parentCtx context.Context, rebalance *RebalanceData) (err error) {
contract, ok := c.cctpContracts[rebalance.OriginMetadata.ChainID]
if !ok {
return fmt.Errorf("could not find cctp contract for chain %d", rebalance.OriginMetadata.ChainID)
}
ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Execute", trace.WithAttributes(
attribute.Int("rebalance_origin", int(rebalance.OriginMetadata.ChainID)),
attribute.Int("rebalance_dest", int(rebalance.DestMetadata.ChainID)),
attribute.String("rebalance_amount", rebalance.Amount.String()),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)

// perform rebalance by calling sendCircleToken()
_, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
tx, err = contract.SendCircleToken(
transactor,
c.relayerAddress,
big.NewInt(int64(rebalance.DestMetadata.ChainID)),
rebalance.OriginMetadata.Addr,
rebalance.Amount,
0, // TODO: inspect
[]byte{}, // TODO: inspect
)
if err != nil {
return nil, fmt.Errorf("could not send circle token: %w", err)
}
return tx, nil
})
if err != nil {
return fmt.Errorf("could not submit CCTP rebalance: %w", err)
}

// store the rebalance in the db
model := reldb.Rebalance{
Origin: uint64(rebalance.OriginMetadata.ChainID),
Destination: uint64(rebalance.DestMetadata.ChainID),
OriginAmount: rebalance.Amount,
Status: reldb.RebalanceInitiated,
}
err = c.db.StoreRebalance(ctx, model)
if err != nil {
return fmt.Errorf("could not store rebalance: %w", err)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The Execute method is well-implemented, with clear error handling and transaction submission logic. However, the TODO comments on lines 161 and 162 indicate unfinished work related to transaction parameters. Address these TODOs to ensure the rebalance operation is fully functional and secure.

Ensure the transaction parameters are correctly set and remove the TODO comments after addressing them.

Comment on lines +187 to +258
// nolint:cyclop
func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (err error) {
listener, ok := c.chainListeners[chainID]
if !ok {
return fmt.Errorf("could not find listener for chain %d", chainID)
}
ethClient, err := c.chainClient.GetClient(parentCtx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress)
parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient)
if err != nil {
return fmt.Errorf("could not get cctp events: %w", err)
}

err = listener.Listen(parentCtx, func(parentCtx context.Context, log types.Log) (err error) {
ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Listen", trace.WithAttributes(
attribute.Int(metrics.ChainID, chainID),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)

switch log.Topics[0] {
case cctp.CircleRequestSentTopic:
parsedEvent, err := parser.ParseCircleRequestSent(log)
if err != nil {
logger.Warnf("could not parse circle request sent: %w", err)
return nil
}
if parsedEvent.Sender != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestSent"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
origin := uint64(chainID)
err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &origin, reldb.RebalancePending)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
case cctp.CircleRequestFulfilledTopic:
parsedEvent, err := parser.ParseCircleRequestFulfilled(log)
if err != nil {
logger.Warnf("could not parse circle request fulfilled: %w", err)
return nil
}
if parsedEvent.Recipient != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestFulfilled"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
default:
logger.Warnf("unknown event %s", log.Topics[0])
}
return nil
})
if err != nil {
return fmt.Errorf("could not listen to contract: %w", err)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The listen method correctly handles CCTP events and updates rebalance statuses based on those events. However, the logging for unknown events (line 250) could be improved by including more context about the event for easier debugging.

- logger.Warnf("unknown event %s", log.Topics[0])
+ logger.Warnf("unknown event %s for chain %d", log.Topics[0], chainID)

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.

Suggested change
// nolint:cyclop
func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (err error) {
listener, ok := c.chainListeners[chainID]
if !ok {
return fmt.Errorf("could not find listener for chain %d", chainID)
}
ethClient, err := c.chainClient.GetClient(parentCtx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress)
parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient)
if err != nil {
return fmt.Errorf("could not get cctp events: %w", err)
}
err = listener.Listen(parentCtx, func(parentCtx context.Context, log types.Log) (err error) {
ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Listen", trace.WithAttributes(
attribute.Int(metrics.ChainID, chainID),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)
switch log.Topics[0] {
case cctp.CircleRequestSentTopic:
parsedEvent, err := parser.ParseCircleRequestSent(log)
if err != nil {
logger.Warnf("could not parse circle request sent: %w", err)
return nil
}
if parsedEvent.Sender != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestSent"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
origin := uint64(chainID)
err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &origin, reldb.RebalancePending)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
case cctp.CircleRequestFulfilledTopic:
parsedEvent, err := parser.ParseCircleRequestFulfilled(log)
if err != nil {
logger.Warnf("could not parse circle request fulfilled: %w", err)
return nil
}
if parsedEvent.Recipient != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestFulfilled"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
default:
logger.Warnf("unknown event %s", log.Topics[0])
}
return nil
})
if err != nil {
return fmt.Errorf("could not listen to contract: %w", err)
}
return nil
}
// nolint:cyclop
func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (err error) {
listener, ok := c.chainListeners[chainID]
if !ok {
return fmt.Errorf("could not find listener for chain %d", chainID)
}
ethClient, err := c.chainClient.GetClient(parentCtx, big.NewInt(int64(chainID)))
if err != nil {
return fmt.Errorf("could not get chain client: %w", err)
}
cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress)
parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient)
if err != nil {
return fmt.Errorf("could not get cctp events: %w", err)
}
err = listener.Listen(parentCtx, func(parentCtx context.Context, log types.Log) (err error) {
ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Listen", trace.WithAttributes(
attribute.Int(metrics.ChainID, chainID),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)
switch log.Topics[0] {
case cctp.CircleRequestSentTopic:
parsedEvent, err := parser.ParseCircleRequestSent(log)
if err != nil {
logger.Warnf("could not parse circle request sent: %w", err)
return nil
}
if parsedEvent.Sender != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestSent"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
origin := uint64(chainID)
err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &origin, reldb.RebalancePending)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
case cctp.CircleRequestFulfilledTopic:
parsedEvent, err := parser.ParseCircleRequestFulfilled(log)
if err != nil {
logger.Warnf("could not parse circle request fulfilled: %w", err)
return nil
}
if parsedEvent.Recipient != c.relayerAddress {
return nil
}
span.SetAttributes(
attribute.String("log_type", "CircleRequestFulfilled"),
attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])),
)
err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted)
if err != nil {
logger.Warnf("could not update rebalance status: %w", err)
return nil
}
default:
logger.Warnf("unknown event %s for chain %d", log.Topics[0], chainID)
}
return nil
})
if err != nil {
return fmt.Errorf("could not listen to contract: %w", err)
}
return nil
}

Copy link
Contributor

@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.

Review Status

Actionable comments generated: 5

Configuration used: .coderabbit.yaml

Commits Files that changed from the base of the PR and between b0f9d22 and 3285fe6.
Files selected for processing (2)
  • services/rfq/relayer/inventory/manager.go (14 hunks)
  • services/rfq/relayer/inventory/rebalance.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • services/rfq/relayer/inventory/rebalance.go
Additional comments: 1
services/rfq/relayer/inventory/manager.go (1)
  • 66-77: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [55-74]

The inventoryManagerImpl struct has been updated with new fields: txSubmitter, rebalanceManagers, and chainID. These changes are essential for supporting the new rebalancing functionality. Ensure that these fields are properly initialized and used throughout the implementation to avoid any potential issues related to uninitialized fields or incorrect usage.

Comment on lines +37 to +51
// Start starts the inventory manager.
Start(ctx context.Context) (err error)
// GetCommittableBalance gets the total balance available for quotes
// this does not include on-chain balances committed in previous quotes that may be
// refunded in the event of a revert.
GetCommittableBalance(ctx context.Context, chainID int, token common.Address, options ...BalanceFetchArgOption) (*big.Int, error)
// GetCommittableBalances gets the total balances committable for all tracked tokens.
GetCommittableBalances(ctx context.Context, options ...BalanceFetchArgOption) (map[int]map[common.Address]*big.Int, error)
// ApproveAllTokens approves all tokens for the relayer address.
ApproveAllTokens(ctx context.Context, submitter submitter.TransactionSubmitter) error
ApproveAllTokens(ctx context.Context) error
// HasSufficientGas checks if there is sufficient gas for a given route.
HasSufficientGas(ctx context.Context, origin, dest int) (bool, error)
// Rebalance checks whether a given token should be rebalanced, and
// executes the rebalance if necessary.
Rebalance(ctx context.Context, chainID int, token common.Address) error
Copy link
Contributor

Choose a reason for hiding this comment

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

The addition of the Start and Rebalance methods, alongside the modification to the ApproveAllTokens method, significantly enhances the inventory manager's functionality. It's crucial to ensure these methods are thoroughly tested, especially the Rebalance method, due to its critical role in maintaining optimal inventory levels. Consider adding unit tests to cover various scenarios and edge cases for these methods.

Would you like assistance in creating unit tests for these methods?

Comment on lines +153 to +188
// NewInventoryManager creates a new inventory manager.
// TODO: too many args here.
//
//nolint:gocognit
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}

i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
db: db,
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}

err := i.initializeTokens(ctx, cfg)
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}

// TODO: move
go func() {
return &i, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The NewInventoryManager function has been significantly modified to accommodate the new rebalancing functionality. While the changes are necessary, the function now has a high number of parameters, which could impact readability and maintainability. Consider refactoring this function to reduce its complexity, possibly by introducing a struct to encapsulate some of the parameters or by using a builder pattern.

- func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
+ // Consider introducing a struct to encapsulate some parameters or using a builder pattern.

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.

Suggested change
// NewInventoryManager creates a new inventory manager.
// TODO: too many args here.
//
//nolint:gocognit
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}
i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
db: db,
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}
err := i.initializeTokens(ctx, cfg)
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}
// TODO: move
go func() {
return &i, nil
// NewInventoryManager creates a new inventory manager.
// TODO: too many args here.
//
//nolint:gocognit
// Consider introducing a struct to encapsulate some parameters or using a builder pattern.
func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) {
rebalanceMethods, err := cfg.GetRebalanceMethods()
if err != nil {
return nil, fmt.Errorf("could not get rebalance methods: %w", err)
}
rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager)
for method := range rebalanceMethods {
//nolint:exhaustive
switch method {
case relconfig.RebalanceMethodCCTP:
rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db)
default:
return nil, fmt.Errorf("unsupported rebalance method: %s", method)
}
}
i := inventoryManagerImpl{
relayerAddress: relayer,
handler: handler,
cfg: cfg,
chainClient: clientFetcher,
txSubmitter: txSubmitter,
rebalanceManagers: rebalanceManagers,
db: db,
}
err = i.initializeTokens(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("could not initialize tokens: %w", err)
}
return &i, nil

Comment on lines +192 to +254
func (i *inventoryManagerImpl) Start(ctx context.Context) error {
g, _ := errgroup.WithContext(ctx)
for _, rebalanceManager := range i.rebalanceManagers {
rebalanceManager := rebalanceManager
g.Go(func() error {
err := rebalanceManager.Start(ctx)
if err != nil {
return fmt.Errorf("could not start rebalance manager: %w", err)
}
return nil
})
}

// continuously refresh balances
g.Go(func() error {
for {
select {
case <-ctx.Done():
return
case <-time.After(defaultPollPeriod * time.Second):
return fmt.Errorf("context canceled: %w", ctx.Err())
case <-time.After(250 * time.Millisecond):
// this returning an error isn't really possible unless a config error happens
// TODO: need better error handling.
err = i.refreshBalances(ctx)
err := i.refreshBalances(ctx)
if err != nil {
logger.Errorf("could not refresh balances")
return
//nolint:nilerr
return nil
}
}
}
}()
})

return &i, nil
// continuously check for rebalances
rebalanceInterval := i.cfg.GetRebalanceInterval()
if rebalanceInterval > 0 {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return fmt.Errorf("context canceled: %w", ctx.Err())
case <-time.After(rebalanceInterval):
err := i.refreshBalances(ctx)
if err != nil {
return fmt.Errorf("could not refresh balances: %w", err)
}
for chainID, chainConfig := range i.cfg.Chains {
for tokenName, tokenConfig := range chainConfig.Tokens {
err = i.Rebalance(ctx, chainID, common.HexToAddress(tokenConfig.Address))
if err != nil {
logger.Errorf("could not rebalance %s on chain %d: %v", tokenName, chainID, err)
}
}
}
}
}
})
}

err := g.Wait()
if err != nil {
return fmt.Errorf("error starting inventory manager: %w", err)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

The implementation of the Start method is comprehensive, handling the start-up of rebalance managers and continuously refreshing balances and checking for rebalances. However, the error handling within the goroutines could be improved by logging the errors or handling them in a way that doesn't potentially swallow them (e.g., the nil return in line 218). Consider implementing a more robust error handling strategy.

- //nolint:nilerr
- return nil
+ // Implement error handling strategy here.

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.

Suggested change
func (i *inventoryManagerImpl) Start(ctx context.Context) error {
g, _ := errgroup.WithContext(ctx)
for _, rebalanceManager := range i.rebalanceManagers {
rebalanceManager := rebalanceManager
g.Go(func() error {
err := rebalanceManager.Start(ctx)
if err != nil {
return fmt.Errorf("could not start rebalance manager: %w", err)
}
return nil
})
}
// continuously refresh balances
g.Go(func() error {
for {
select {
case <-ctx.Done():
return
case <-time.After(defaultPollPeriod * time.Second):
return fmt.Errorf("context canceled: %w", ctx.Err())
case <-time.After(250 * time.Millisecond):
// this returning an error isn't really possible unless a config error happens
// TODO: need better error handling.
err = i.refreshBalances(ctx)
err := i.refreshBalances(ctx)
if err != nil {
logger.Errorf("could not refresh balances")
return
//nolint:nilerr
return nil
}
}
}
}()
})
return &i, nil
// continuously check for rebalances
rebalanceInterval := i.cfg.GetRebalanceInterval()
if rebalanceInterval > 0 {
g.Go(func() error {
for {
select {
case <-ctx.Done():
return fmt.Errorf("context canceled: %w", ctx.Err())
case <-time.After(rebalanceInterval):
err := i.refreshBalances(ctx)
if err != nil {
return fmt.Errorf("could not refresh balances: %w", err)
}
for chainID, chainConfig := range i.cfg.Chains {
for tokenName, tokenConfig := range chainConfig.Tokens {
err = i.Rebalance(ctx, chainID, common.HexToAddress(tokenConfig.Address))
if err != nil {
logger.Errorf("could not rebalance %s on chain %d: %v", tokenName, chainID, err)
}
}
}
}
}
})
}
err := g.Wait()
if err != nil {
return fmt.Errorf("error starting inventory manager: %w", err)
}
return nil
// Implement error handling strategy here.

Comment on lines 269 to 330
}

for address, token := range tokenMap {
// if startAllowance is 0
if address != chain.EthAddress && token.startAllowance.Cmp(big.NewInt(0)) == 0 {
chainID := chainID // capture func literal
address := address // capture func literal
// init an approval in submitter. Note: in the case where submitter hasn't finished from last boot, this will double submit approvals unfortanutely
_, err = submitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
erc20, err := ierc20.NewIERC20(address, backendClient)
if err != nil {
return nil, fmt.Errorf("could not get erc20: %w", err)
}

approveAmount, err := erc20.Approve(transactor, common.HexToAddress(i.cfg.Chains[chainID].Bridge), abi.MaxInt256)
if err != nil {
return nil, fmt.Errorf("could not approve: %w", err)
}
// approve RFQ contract.
// Note: in the case where submitter hasn't finished from last boot,
// this will double submit approvals unfortunately.
if address != chain.EthAddress && token.StartAllowanceRFQ.Cmp(big.NewInt(0)) == 0 {
tokenAddr := address // capture func literal
contractAddr, err := i.cfg.GetRFQAddress(chainID)
if err != nil {
return fmt.Errorf("could not get RFQ address: %w", err)
}
err = i.approve(ctx, tokenAddr, common.HexToAddress(contractAddr), backendClient)
if err != nil {
return fmt.Errorf("could not approve RFQ contract: %w", err)
}
}

return approveAmount, nil
})
// approve CCTP contract
if address != chain.EthAddress && token.StartAllowanceCCTP.Cmp(big.NewInt(0)) == 0 {
tokenAddr := address // capture func literal
contractAddr, err := i.cfg.GetCCTPAddress(chainID)
if err != nil {
return fmt.Errorf("could not submit approval: %w", err)
return fmt.Errorf("could not get CCTP address: %w", err)
}
err = i.approve(ctx, tokenAddr, common.HexToAddress(contractAddr), backendClient)
if err != nil {
return fmt.Errorf("could not approve CCTP contract: %w", err)
}
}
}
}
return nil
}

// approve submits an ERC20 approval for a given token and contract address.
func (i *inventoryManagerImpl) approve(ctx context.Context, tokenAddr, contractAddr common.Address, backendClient client.EVM) (err error) {
erc20, err := ierc20.NewIERC20(tokenAddr, backendClient)
if err != nil {
return fmt.Errorf("could not get erc20: %w", err)
}
chainID, err := backendClient.ChainID(ctx)
if err != nil {
return fmt.Errorf("could not get chain id: %w", err)
}

_, err = i.txSubmitter.SubmitTransaction(ctx, chainID, func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) {
tx, err = erc20.Approve(transactor, contractAddr, abi.MaxInt256)
if err != nil {
return nil, fmt.Errorf("could not approve: %w", err)
}
return tx, nil
})
if err != nil {
return fmt.Errorf("could not submit approval: %w", err)
}
return nil
}

// HasSufficientGas checks if there is sufficient gas for a given route.
func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, dest int) (sufficient bool, err error) {
gasThresh, err := i.cfg.GetMinGasToken(dest)
Copy link
Contributor

Choose a reason for hiding this comment

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

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [260-296]

The ApproveAllTokens method has been enhanced to manage approvals for both RFQ and CCTP bridges. This is a crucial update for supporting the new rebalancing functionality. Ensure that the error handling is robust, especially considering the potential for double submissions of approvals as noted in the comments. Additionally, consider optimizing the approval process to avoid unnecessary transactions if possible.

Consider adding logic to check existing allowances before attempting to set new approvals, to minimize unnecessary transactions.

Comment on lines +347 to +402
// Rebalance checks whether a given token should be rebalanced, and executes the rebalance if necessary.
// Note that if there are multiple tokens whose balance is below the maintenance balance, only the lowest balance
// will be rebalanced.
func (i *inventoryManagerImpl) Rebalance(parentCtx context.Context, chainID int, token common.Address) error {
// evaluate the rebalance method
method, err := i.cfg.GetRebalanceMethod(chainID, token.Hex())
if err != nil {
return fmt.Errorf("could not get rebalance method: %w", err)
}
if method == relconfig.RebalanceMethodNone {
return nil
}
ctx, span := i.handler.Tracer().Start(parentCtx, "Rebalance", trace.WithAttributes(
attribute.Int(metrics.ChainID, chainID),
attribute.String("token", token.Hex()),
attribute.String("rebalance_method", method.String()),
))
defer func(err error) {
metrics.EndSpanWithErr(span, err)
}(err)

// build the rebalance action
rebalance, err := getRebalance(span, i.cfg, i.tokens, chainID, token)
if err != nil {
return fmt.Errorf("could not get rebalance: %w", err)
}
if rebalance == nil {
return nil
}
span.SetAttributes(
attribute.String("rebalance_origin", strconv.Itoa(rebalance.OriginMetadata.ChainID)),
attribute.String("rebalance_dest", strconv.Itoa(rebalance.DestMetadata.ChainID)),
attribute.String("rebalance_amount", rebalance.Amount.String()),
)

// make sure there are no pending rebalances that touch the given path
pending, err := i.db.HasPendingRebalance(ctx, uint64(rebalance.OriginMetadata.ChainID), uint64(rebalance.DestMetadata.ChainID))
if err != nil {
return fmt.Errorf("could not check pending rebalance: %w", err)
}
span.SetAttributes(attribute.Bool("rebalance_pending", pending))
if pending {
return nil
}

// execute the rebalance
manager, ok := i.rebalanceManagers[method]
if !ok {
return fmt.Errorf("no rebalance manager for method: %s", method)
}
err = manager.Execute(ctx, rebalance)
if err != nil {
return fmt.Errorf("could not execute rebalance: %w", err)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The Rebalance method implements the core functionality of token rebalancing. Given its impact on the inventory management system's reliability and accuracy, it's crucial to ensure this method is thoroughly tested. The method involves several steps, including evaluating the rebalance method, building the rebalance action, checking for pending rebalances, and executing the rebalance. Each of these steps has potential failure points that need to be carefully handled.

Would you like assistance in creating unit tests for the Rebalance method?

@dwasse dwasse merged commit d37732f into master Mar 4, 2024
54 of 55 checks passed
@dwasse dwasse deleted the feat/relayer-rebalance branch March 4, 2024 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants