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

feat:Support parsing multicall in governance functions #255

Merged
merged 9 commits into from
Feb 20, 2024

Conversation

cbrit
Copy link
Member

@cbrit cbrit commented Feb 20, 2024

Closes #256

Copy link

coderabbitai bot commented Feb 20, 2024

Walkthrough

The project introduced enhancements to support scheduled cork proposals with multicalls, expanding governance capabilities. It involved updating contract functionality and ABI definitions, adding new test types, and refining the code structure for better governance call handling. Additionally, the Makefile was updated for version control, and performance improvements were made in the message handling.

Changes

Files Change Summary
.github/workflows/integration_tests.yml Added "ScheduledCorkMulticallProposal" test type to the matrix.
Makefile Included SOMMELIER_VERSION variable and added e2e_scheduled_cork_proposal_multicall_test.
integration_tests/cellar_v2_2_abi.go
integration_tests/ethereum/contracts/MockCellarV2.2.sol
Modified ABI and added functions/events for adaptor and position catalogue management.
integration_tests/proposal_test.go Added TestScheduledCorkMulticallProposal function for integration testing.
proto/steward/v4/cellar_v2.proto Reorganized structure to include call_type field in governance messages.
src/cellars/cellar_v2_2.rs
src/cellars/cellar_v2_5.rs
Introduced types and functions for governance calls handling.
src/commands.rs Temporarily disabled CorkProposalCmd functionality.
src/cork/proposals.rs Updated get_encoded_governance_call function to use call_type.
src/somm_send.rs Improved get_fee function signature for performance and flexibility.

Related issues

  • Multicall support for governance functions #256: The changes in this PR address the objectives of implementing multicall support for governance functions to optimize gas usage and enable batch execution of multiple governance functions in a single transaction.

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:

Note: Auto-reply has been disabled for this repository by the repository owner. The CodeRabbit bot will not respond to your comments unless it is explicitly tagged.

  • 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 from git 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.

Comment on lines +200 to +441
s.Require().Eventuallyf(func() bool {
s.T().Log("querying add adaptor cellar event...")
ethClient, err := ethclient.Dial(fmt.Sprintf("http://%s", s.ethResource.GetHostPort("8545/tcp")))
if err != nil {
return false
}

// For non-anonymous events, the first log topic is a keccak256 hash of the
// event signature.
eventSignature := []byte("AddAdaptorToCatalogue(address)")
mockEventSignatureTopic := crypto.Keccak256Hash(eventSignature)
query := ethereum.FilterQuery{
FromBlock: nil,
ToBlock: nil,
Addresses: []common.Address{
v2_2Cellar,
},
Topics: [][]common.Hash{
{
mockEventSignatureTopic,
},
},
}

logs, err := ethClient.FilterLogs(context.Background(), query)
if err != nil {
ethClient.Close()
return false
}

vault_abi, err := CellarV22MetaData.GetAbi()
s.Require().NoError(err)

if len(logs) > 0 {
s.T().Logf("found %d logs!", len(logs))
for _, log := range logs {
if len(log.Data) > 0 {
var event CellarV22AddAdaptorToCatalogue
err := vault_abi.UnpackIntoInterface(&event, "AddAdaptorToCatalogue", log.Data)
s.Require().NoError(err, "failed to unpack AddAdaptorToCatalogue event from log data")
s.Require().Equal(common.HexToAddress(adaptorContract.Hex()), event.Adaptor)

return true
}
}
}

return false
}, 3*time.Minute, 10*time.Second, "add adaptor cellar event never seen")

s.Require().Eventuallyf(func() bool {
s.T().Log("querying add position cellar event...")
ethClient, err := ethclient.Dial(fmt.Sprintf("http://%s", s.ethResource.GetHostPort("8545/tcp")))
if err != nil {
return false
}

// For non-anonymous events, the first log topic is a keccak256 hash of the
// event signature.
eventSignature := []byte("AddPositionToCatalogue(uint32)")
mockEventSignatureTopic := crypto.Keccak256Hash(eventSignature)
query := ethereum.FilterQuery{
FromBlock: nil,
ToBlock: nil,
Addresses: []common.Address{
v2_2Cellar,
},
Topics: [][]common.Hash{
{
mockEventSignatureTopic,
},
},
}

logs, err := ethClient.FilterLogs(context.Background(), query)
if err != nil {
ethClient.Close()
return false
}

vault_abi, err := CellarV22MetaData.GetAbi()
s.Require().NoError(err)

if len(logs) > 0 {
s.T().Logf("found %d logs!", len(logs))
for _, log := range logs {
if len(log.Data) > 0 {
var event CellarV22AddPositionToCatalogue
err := vault_abi.UnpackIntoInterface(&event, "AddPositionToCatalogue", log.Data)
s.Require().NoError(err, "failed to unpack AddPositionToCatalogue event from log data")
s.Require().Equal(uint32(1), event.Position)

return true
}
}
}

return false
}, 3*time.Minute, 10*time.Second, "add position cellar event never seen")
}
Copy link

Choose a reason for hiding this comment

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

The new test function TestScheduledCorkMulticallProposal is well-structured and follows a logical sequence of steps to test the multicall functionality in governance proposals. However, there are a few areas where improvements can be made:

  1. Error Handling and Logging: Ensure that all errors are handled appropriately. For instance, after dialing the Ethereum client (ethclient.Dial), it's good practice to defer closing the client connection to prevent resource leaks. This is observed in lines 344 and 394 but should include a defer ethClient.Close() after successful connection establishment.

  2. Repeated Code: There's a noticeable repetition in querying Ethereum logs for different events (AddAdaptorToCatalogue and AddPositionToCatalogue). Consider refactoring this into a helper function that takes the event signature and expected outcome as parameters. This will improve code maintainability and readability.

  3. Magic Numbers: The test uses hardcoded values, such as the targetBlockHeight increment (currentHeight + 90) and sleep durations. It's advisable to define these as constants or configurable parameters at the beginning of your test file or function. This makes the test more adaptable and easier to maintain, especially if adjustments are needed based on the blockchain's performance or test environment changes.

  4. Test Coverage and Assertions: While the test checks for the presence of specific events to confirm the multicall execution, it might be beneficial to extend the assertions to cover more aspects of the multicall's effects. For example, verifying the state changes (if any) in the smart contracts involved in the multicall could provide a more comprehensive test coverage.

  5. Documentation: Adding comments to explain the rationale behind certain steps or values (e.g., why targetBlockHeight is set to currentHeight + 90) can enhance the test's readability and maintainability. This is particularly useful for complex tests involving multiple blockchain interactions.

Consider implementing these suggestions to enhance the test's effectiveness, maintainability, and readability.

Comment on lines 459 to 496
Ok(result)
}

/// Encodes a call to a CellarV2.5 contract
pub fn get_encoded_governance_call(
function: GovernanceFunction,
call_type: GovernanceCallType,
cellar_id: &str,
proposal_id: u64,
) -> Result<Vec<u8>, Error> {
match call_type {
GovernanceCallType::FunctionCall(f) => {
get_encoded_governance_function(f, cellar_id, proposal_id)
}
GovernanceCallType::Multicall(m) => {
let mut multicall = MulticallCall::default();
m.function_calls
.iter()
.map(|f| get_encoded_governance_function(f.clone(), cellar_id, proposal_id))
.collect::<Result<Vec<Vec<u8>>, Error>>()?
.iter()
.for_each(|f| multicall.data.push(Bytes::from(f.clone())));

Ok(multicall.encode())
}
}
}

pub fn get_encoded_governance_function(
call: GovernanceFunctionCall,
cellar_id: &str,
proposal_id: u64,
) -> Result<Vec<u8>, Error> {
let function = call
.function
.ok_or_else(|| sp_call_error("call data is empty".to_string()))?;
match function {
GovernanceFunction::AddAdaptorToCatalogue(params) => {
log_governance_cellar_call(
Copy link

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 [486-738]

The implementation of get_encoded_governance_function and its handling of various governance functions is comprehensive. However, there are a few areas to consider:

  1. Error Handling: Ensure that all potential error cases are handled gracefully, especially when parsing addresses and converting strings to U256. It's crucial to provide clear error messages for better debugging and user experience.

  2. Performance: When encoding governance functions, especially in the case of multicalls, consider the performance implications of cloning and iterating over function calls. If possible, explore more efficient ways to handle these operations to minimize the computational overhead.

  3. Best Practices: The use of string_to_u256 and sp_call_parse_address functions repeatedly suggests that a utility function or a higher-level abstraction could simplify these operations and reduce code duplication.

  4. Security: Given the governance context, ensure that all security aspects are thoroughly reviewed, particularly the permissions and conditions under which these governance calls can be made. This includes validating inputs and ensuring that only authorized entities can execute sensitive operations.

Consider refactoring to reduce code duplication and improve error handling. Additionally, ensure comprehensive security reviews are conducted for governance-related functionality.

Comment on lines 284 to 291
pub fn get_encoded_governance_function(
call: GovernanceFunctionCall,
cellar_id: &str,
proposal_id: u64,
) -> Result<Vec<u8>, Error> {
let function = call
.function
.ok_or_else(|| sp_call_error("call data is empty".to_string()))?;
Copy link

Choose a reason for hiding this comment

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

The function get_encoded_governance_function is well-structured and covers a wide range of governance functions. It's essential to ensure that the error message "call data is empty" is clear and actionable for the developers or operators who might encounter it. Consider adding more context to the error message, such as the specific governance function that failed to provide call data.

- .ok_or_else(|| sp_call_error("call data is empty".to_string()))?;
+ .ok_or_else(|| sp_call_error(format!("call data is empty for function {:?}", function)))?;

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
pub fn get_encoded_governance_function(
call: GovernanceFunctionCall,
cellar_id: &str,
proposal_id: u64,
) -> Result<Vec<u8>, Error> {
let function = call
.function
.ok_or_else(|| sp_call_error("call data is empty".to_string()))?;
let function = call
.function
.ok_or_else(|| sp_call_error(format!("call data is empty for function {:?}", function)))?;

Comment on lines 260 to 282
/// Encodes a call to a CellarV2.2 contract
pub fn get_encoded_governance_call(
function: GovernanceFunction,
call_type: GovernanceCallType,
cellar_id: &str,
proposal_id: u64,
) -> Result<Vec<u8>, Error> {
match call_type {
GovernanceCallType::FunctionCall(f) => {
get_encoded_governance_function(f, cellar_id, proposal_id)
}
GovernanceCallType::Multicall(m) => {
let mut multicall = MulticallCall::default();
m.function_calls
.iter()
.map(|f| get_encoded_governance_function(f.clone(), cellar_id, proposal_id))
.collect::<Result<Vec<Vec<u8>>, Error>>()?
.iter()
.for_each(|f| multicall.data.push(Bytes::from(f.clone())));

Ok(multicall.encode())
}
}
}
Copy link

Choose a reason for hiding this comment

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

In the get_encoded_governance_call function, the use of default() for initializing MulticallCall followed by iterative data pushing is efficient. However, consider pre-allocating the vector size for multicall.data based on the number of function_calls to improve performance by reducing the number of reallocations.

let mut multicall = MulticallCall::default();
multicall.data.reserve(m.function_calls.len());

Comment on lines 940 to 941
// Represents function `setRebalanceDeviation(uint265)`
SetRebalanceDeviation set_rebalance_deviation = 3;
Copy link

Choose a reason for hiding this comment

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

There's a typo in the SetRebalanceDeviation message definition where uint265 should likely be uint256. This typo could lead to compilation errors or unintended behavior.

-            // Represents function `setRebalanceDeviation(uint265)`
+            // Represents function `setRebalanceDeviation(uint256)`

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
// Represents function `setRebalanceDeviation(uint265)`
SetRebalanceDeviation set_rebalance_deviation = 3;
// Represents function `setRebalanceDeviation(uint256)`
SetRebalanceDeviation set_rebalance_deviation = 3;

@@ -42,7 +42,7 @@ message CellarV1 {
SetLiquidityLimit set_liquidity_limit = 10;
// Represents function `setShareLockPeriod()`
SetShareLockPeriod set_share_lock_period = 11;
// Represents function `setRebalanceDeviation(uint265)`
// Represents function `setRebalanceDeviation(uint256)`
Copy link

Choose a reason for hiding this comment

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

While the specific change to setRebalanceDeviation is correct, it's noted that numeric values such as index, new_limit, and new_deviation are represented as string types throughout the protobuf definitions. This approach might be intentional to handle large numbers or specific formats (e.g., hexadecimal). However, if these fields are expected to be strictly numeric and within the range of standard numeric types in protobuf (e.g., int32, int64), consider using more appropriate numeric types for clarity and type safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multicall support for governance functions
2 participants