-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Vault leveraging aave on eth and pancake on base part 1 #340
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
Conversation
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
WalkthroughThis set of changes introduces a comprehensive end-to-end example for a cross-chain vault strategy integrating Ethereum and Base networks, with PancakeSwap and various bridging mechanisms. New Rust modules and configuration files are added to orchestrate the deployment and setup of accounts, contracts, and relayers across both chains. The strategy logic automates vault management, lending, borrowing, and token bridging, with detailed configuration for all components. Additionally, the mock standard bridge relayer is implemented, and the CCTP relayer is enhanced with incremental block processing. Several contract and struct field types are updated from 128-bit to 256-bit integers to support larger token amounts. Minor fixes and formatting adjustments are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Vault
participant Forwarder
participant Aave
participant CCTPBridge
participant StandardBridge
participant BaseForwarder
participant PancakeSwap
User->>Vault: Deposit WETH
Vault->>Forwarder: Split WETH (2/3 to Aave, 1/3 to StandardBridge)
Forwarder->>Aave: Supply WETH
Aave->>Aave: Borrow USDC (if health factor OK)
Aave->>Forwarder: Forward USDC to CCTP input
Forwarder->>CCTPBridge: Bridge USDC to Base
Forwarder->>StandardBridge: Bridge WETH to Base
CCTPBridge->>BaseForwarder: Receive USDC on Base
StandardBridge->>BaseForwarder: Receive WETH on Base
BaseForwarder->>PancakeSwap: Provide liquidity or execute swaps
Suggested reviewers
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (11)
e2e/examples/eth_to_base_vault_pancake/strategist/example_strategy.toml (1)
25-27
:min_aave_health_factor
representation is ambiguousThe value
"12"
is later commented as “Represents 1.2”. Persisting scaled numbers as plain strings is brittle and invites off-by-ten-fold errors.Recommend:
min_aave_health_factor = { value = 120, decimals = 2 } # 1.20or store the raw
1.2
and convert to ray‐units in code.e2e/examples/eth_to_base_vault_pancake/vault.rs (1)
303-307
:strategy.start()
fire-and-forget can drop tasksIf
start()
spawns background tasks without beingawait
-ed or returning aJoinHandle
, those tasks may be dropped whenmain
exits after the 120 s sleep.
Consider returning a handle andjoin!
-ing it, or makestart()
blocking.e2e/examples/eth_to_base_vault_pancake/ethereum.rs (1)
260-274
: Manual 64-byte padding is error-proneInstead of:
let mint_recipient_hex = mint_recipient_string.strip_prefix("0x").unwrap_or_default(); let padded_hex = format!("{:0>64}", mint_recipient_hex); let mintRecipient = FixedBytes::<32>::from_hex(padded_hex)?;just use
mint_recipient.to_fixed_bytes()
(or equivalent) and avoid the string gymnastics.e2e/examples/eth_to_base_vault_pancake/base.rs (2)
291-304
: Incorrect chain label in log message creates debugging confusion
set_up_forwarder_pancake_output_to_input
(and the two similar helpers below) print “on Ethereum” although they are deploying to Base (base_client
).
This can be very misleading when grepping logs from multi-chain tests.- info!("Setting up Forwarder Pancake Output to Input on Ethereum"); + info!("Setting up Forwarder Pancake Output to Input on Base");Please update the other two helpers as well.
150-170
: Redundantto_string()
➜Address::from_str(..)
round-tripsThe config objects repeatedly call
input_account.to_string()
→Address::from_str(..)
which allocates and parses hex strings dozens of times.All encoder helpers accept
alloy_primitives::Address
directly; pass the value without serialising first:- inputAccount: alloy_primitives_encoder::Address::from_str( - input_account.to_string().as_str(), - )?, + inputAccount: input_account.into(),This shaves deployment time and avoids accidental checksum/prefix bugs.
e2e/examples/eth_to_base_vault_pancake/strategist/strategy_config.rs (1)
52-55
: Consider numeric types for threshold parameters
min_aave_health_factor
is stored asString
and later parsed intoU256
each cycle.
Storing it asDecimal
/U256
in the config struct would avoid repeated parsing and catch format errors at deserialisation time.e2e/src/utils/mocks/standard_bridge_relayer.rs (5)
44-66
: Consider using environment variables for sensitive information.While this is a mock implementation for testing, it's still a good practice to avoid hardcoding mnemonic phrases directly in the source code, even in test environments.
- .phrase("test test test test test test test test test test test junk") + .phrase(std::env::var("TEST_MNEMONIC").unwrap_or_else(|_| "test test test test test test test test test test test junk".to_string()))
79-100
: Consider implementing parallel chain polling.The current implementation polls chains sequentially, which is simpler but less efficient. For better performance, especially with slow-responding nodes, consider polling both chains concurrently.
async fn cycle(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> { let worker_name = self.get_name(); - if let Err(e) = self.poll_evm_a().await { - warn!("{worker_name}: Evm A polling error: {:?}", e); - } - - if let Err(e) = self.poll_evm_b().await { - warn!("{worker_name}: Evm B polling error: {:?}", e); - } + let poll_a = self.poll_evm_a(); + let poll_b = self.poll_evm_b(); + + let (a_result, b_result) = tokio::join!(poll_a, poll_b); + + if let Err(e) = a_result { + warn!("{worker_name}: Evm A polling error: {:?}", e); + } + + if let Err(e) = b_result { + warn!("{worker_name}: Evm B polling error: {:?}", e); + } tokio::time::sleep(POLLING_PERIOD).await; Ok(()) }
169-208
: Consolidate duplicate code between send_on_evm_a and send_on_evm_b methods.Both methods implement nearly identical logic, differing only in which EVM client they use. Consider refactoring to reduce code duplication.
+ async fn send_tokens(&self, client: &EthereumClient, destination_erc20: Address, amount: U256, recipient: Address, chain_name: &str) -> Result<(), Box<dyn Error>> { + let rp = client + .get_request_provider() + .await + .expect(&format!("failed to get {chain_name} request provider")); + + let erc20 = ERC20::new(destination_erc20, &rp); + + let pre_send_balance = client + .query(erc20.balanceOf(recipient)) + .await + .expect(&format!("failed to query {chain_name} balance")); + + let send_tx = client + .execute_tx(erc20.transfer(recipient, amount).into_transaction_request()) + .await + .expect(&format!("failed to send tokens on {chain_name}")); + + let _ = rp + .get_transaction_receipt(send_tx.transaction_hash) + .await; + + let post_send_balance = client + .query(erc20.balanceOf(recipient)) + .await + .expect(&format!("failed to query {chain_name} balance")); + + let delta = post_send_balance._0 - pre_send_balance._0; + info!("[Standard Bridge] successfully sent {delta} tokens to {chain_name} address {recipient}"); + + Ok(()) + } + async fn send_on_evm_b(&self, amount: U256, recipient: Address) -> Result<(), Box<dyn Error>> { - let evm_b_rp = self - .runtime - .evm_client_b - .get_request_provider() - .await - .expect("failed to get evm B request provider"); - - let erc20 = ERC20::new(self.state.evm_b_destination_erc20, &evm_b_rp); - - let pre_send_balance = self - .runtime - .evm_client_b - .query(erc20.balanceOf(recipient)) - .await - .expect("failed to query evm B balance"); - - let send_tx = self - .runtime - .evm_client_b - .execute_tx(erc20.transfer(recipient, amount).into_transaction_request()) - .await - .expect("failed to send tokens on evm B"); - - let _ = evm_b_rp - .get_transaction_receipt(send_tx.transaction_hash) - .await; - - let post_send_balance = self - .runtime - .evm_client_b - .query(erc20.balanceOf(recipient)) - .await - .expect("failed to query evm B balance"); - - let delta = post_send_balance._0 - pre_send_balance._0; - info!("[Standard Bridge] successfully sent {delta} tokens to evm B address {recipient}"); - - Ok(()) + self.send_tokens(&self.runtime.evm_client_b, self.state.evm_b_destination_erc20, amount, recipient, "evm B") } async fn send_on_evm_a(&self, amount: U256, recipient: Address) -> Result<(), Box<dyn Error>> { - let evm_a_rp = self - .runtime - .evm_client_a - .get_request_provider() - .await - .expect("failed to get evm A request provider"); - - let erc20 = ERC20::new(self.state.evm_a_destination_erc20, &evm_a_rp); - - let pre_send_balance = self - .runtime - .evm_client_a - .query(erc20.balanceOf(recipient)) - .await - .expect("failed to query evm A balance"); - - let send_tx = self - .runtime - .evm_client_a - .execute_tx(erc20.transfer(recipient, amount).into_transaction_request()) - .await - .expect("failed to send tokens on evm A"); - - let _ = evm_a_rp - .get_transaction_receipt(send_tx.transaction_hash) - .await; - - let post_send_balance = self - .runtime - .evm_client_a - .query(erc20.balanceOf(recipient)) - .await - .expect("failed to query evm A balance"); - - let delta = post_send_balance._0 - pre_send_balance._0; - info!("[Standard Bridge] successfully sent {delta} tokens to evm A address {recipient}"); - - Ok(()) + self.send_tokens(&self.runtime.evm_client_a, self.state.evm_a_destination_erc20, amount, recipient, "evm A") }
210-304
: Consider extracting common logic from poll_evm_a and poll_evm_b.Both polling methods contain nearly identical logic, differing only in which chain they poll and which method they call to process events. Consider refactoring to reduce code duplication.
You could create a helper method that takes the necessary parameters to handle both polling scenarios:
async fn poll_chain( &mut self, client: &EthereumClient, filter: &Filter, last_block_processed: &mut Option<u64>, processed_events: &mut HashSet<Vec<u8>>, send_handler: impl Fn(U256, Address) -> Result<(), Box<dyn Error>>, ) -> Result<(), Box<dyn Error>> { let provider = client.get_request_provider().await.expect("could not get provider"); let current_block = provider.get_block_number().await?; let last_block = last_block_processed.unwrap_or(current_block.saturating_sub(1)); // Handle potential chain reorganizations if current_block <= last_block { return Ok(()); } let filter = filter.clone() .from_block(BlockNumberOrTag::Number(last_block)) .to_block(BlockNumberOrTag::Number(current_block)); // fetch the logs let logs = provider.get_logs(&filter).await?; for log in logs.iter() { let event_id = log .transaction_hash .expect("failed to find tx hash in logs") .to_vec(); if processed_events.insert(event_id) { let alloy_log = Log::new(log.address(), log.topics().into(), log.data().clone().data) .unwrap_or_default(); let erc20_deposit_initiated_log = ERC20DepositInitiated::decode_log(&alloy_log, false)?; // send tokens using the provided handler send_handler( erc20_deposit_initiated_log.amount, erc20_deposit_initiated_log.to, )?; } } // update the last block processed *last_block_processed = Some(current_block); Ok(()) }Then update the
poll_evm_a
andpoll_evm_b
methods to use this helper method.
1-305
: Add documentation for public methods and structs.The code would benefit from more comprehensive documentation, particularly for public methods and structs. Consider adding documentation comments to explain the purpose and expected behavior of each component.
For example:
+/// A mock implementation of a standard bridge relayer that forwards tokens between two EVM chains +/// based on deposit events. This is intended for testing purposes only. pub struct MockStandardBridgeRelayer { pub state: RelayerState, pub runtime: RelayerRuntime, } +/// Runtime components needed for the relayer to operate, including connections to both EVM chains. pub struct RelayerRuntime { pub evm_client_a: EthereumClient, pub evm_client_b: EthereumClient, } +/// State information tracked by the relayer to ensure events are processed only once +/// and to track the latest blocks that have been processed on each chain. pub struct RelayerState { evm_a_last_block_processed: Option<u64>, evm_a_processed_events: HashSet<Vec<u8>>, evm_a_filter: Filter, evm_a_destination_erc20: Address, evm_b_last_block_processed: Option<u64>, evm_b_processed_events: HashSet<Vec<u8>>, evm_b_filter: Filter, evm_b_destination_erc20: Address, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
contracts/encoders/evm-encoder/src/libraries/forwarder.rs
(3 hunks)docs/src/libraries/evm/forwarder.md
(1 hunks)e2e/Cargo.toml
(1 hunks)e2e/examples/eth_cctp_vault/evm.rs
(2 hunks)e2e/examples/eth_cctp_vault/strategist/example_strategy.toml
(1 hunks)e2e/examples/eth_to_base_vault_pancake/base.rs
(1 hunks)e2e/examples/eth_to_base_vault_pancake/ethereum.rs
(1 hunks)e2e/examples/eth_to_base_vault_pancake/strategist/example_strategy.toml
(1 hunks)e2e/examples/eth_to_base_vault_pancake/strategist/mod.rs
(1 hunks)e2e/examples/eth_to_base_vault_pancake/strategist/strategy.rs
(1 hunks)e2e/examples/eth_to_base_vault_pancake/strategist/strategy_config.rs
(1 hunks)e2e/examples/eth_to_base_vault_pancake/vault.rs
(1 hunks)e2e/examples/ethereum_integration_tests.rs
(2 hunks)e2e/examples/forked_anvil.rs
(0 hunks)e2e/src/utils/mocks/cctp_relayer_evm_evm.rs
(7 hunks)e2e/src/utils/mocks/mod.rs
(1 hunks)e2e/src/utils/mocks/standard_bridge_relayer.rs
(1 hunks)e2e/src/utils/solidity_contracts.rs
(1 hunks)packages/chain-client-utils/Cargo.toml
(1 hunks)packages/encoder-utils/src/libraries/forwarder/solidity_types.rs
(1 hunks)solidity/src/libraries/Forwarder.sol
(1 hunks)
💤 Files with no reviewable changes (1)
- e2e/examples/forked_anvil.rs
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: build
- GitHub Check: build-and-deploy
- GitHub Check: setup-local-ic
🔇 Additional comments (31)
packages/chain-client-utils/Cargo.toml (1)
11-11
: Approve trivial formatting update
Removed trailing whitespace on thetest-utils
feature declaration. No functional impact.e2e/examples/eth_cctp_vault/strategist/example_strategy.toml (1)
2-56
: Formatting improvements enhance readabilityThe changes in this file are purely formatting adjustments that align the equals signs across configuration blocks. This improves visual consistency and makes the configuration easier to read without changing any functionality.
e2e/src/utils/mocks/mod.rs (1)
3-3
: New standard bridge relayer module addedThe addition of the standard bridge relayer module complements the existing CCTP relayer modules. This is in line with the PR objective of implementing bridging operations using both CCTP and the Standard Bridge.
e2e/examples/eth_cctp_vault/evm.rs (2)
174-174
: Fixed variable name typoThe variable name has been corrected from
cctp_transer_cfg
tocctp_transfer_cfg
, fixing a spelling error.
193-193
: Fixed variable name typoThe variable name has been updated to use the corrected
cctp_transfer_cfg
instead ofcctp_transer_cfg
, maintaining consistency with the earlier fix.packages/encoder-utils/src/libraries/forwarder/solidity_types.rs (1)
6-6
:✅ Verification successful
Enhanced token amount capacity
Changed
maxAmount
field type fromuint128
touint256
to support larger token amounts in forwarding configurations. This change aligns with updates in Solidity contracts and other Rust encoders in the codebase.Let's verify this change is consistent with other related files:
🏁 Script executed:
#!/bin/bash # Check for related maxAmount type changes in other files rg -t rust "max.?[aA]mount.*[uU](128|256)|[uU](128|256).*max.?[aA]mount" --no-headingLength of output: 1442
Enhanced token amount capacity confirmed
All EVM‐related code now consistently uses a 256-bit amount:
packages/encoder-utils/src/libraries/forwarder/solidity_types.rs
declaresuint256 maxAmount
.- The EVM encoder (
contracts/encoders/evm-encoder/src/libraries/forwarder.rs
) serializescfg.max_amount
asU256
.- End-to-end tests (
e2e/examples/ethereum_integration_tests.rs
and Pancake integration) useU256
formaxAmount
.- No other EVM files reference a 128-bit
maxAmount
.No further updates needed—approving the change.
e2e/examples/ethereum_integration_tests.rs (2)
322-322
: Type change from integer literal to explicit U256The change from
1000
toU256::from(1000)
aligns with the broader update in the codebase where themax_amount
field type inForwardingConfig
struct was changed fromu128
toUint256
. This ensures type consistency throughout the system.
348-348
: Type change from integer literal to explicit U256Similarly to the previous change, this explicit U256 type construction ensures consistency with the updated field type in the
ForwardingConfig
struct. This is part of the system-wide enhancement to support larger token forwarding amounts.solidity/src/libraries/Forwarder.sol (1)
20-20
: Enhanced token amount supportThe field type change from
uint128
touint256
formaxAmount
increases the maximum representable token forwarding amount. This aligns with the corresponding changes in the Rust encoder libraries and provides better support for tokens with large supplies or values.The change is well-integrated with related updates across the codebase, particularly in the encoder utilities and Rust libraries that interact with this Solidity struct.
e2e/examples/eth_to_base_vault_pancake/strategist/mod.rs (1)
1-2
: New modules for cross-chain vault strategyThese new modules establish the foundation for the cross-chain vault strategy between Ethereum and Base networks with PancakeSwap integration. The
strategy
module will contain the implementation logic whilestrategy_config
will handle configuration structures.This modular approach maintains good separation of concerns, keeping the strategy logic separate from its configuration.
e2e/Cargo.toml (1)
16-18
: New example target for cross-chain vault strategyThis addition registers the new end-to-end example for a vault strategy that leverages AAVE on Ethereum and PancakeV3 on Base. The example will demonstrate the deployment and operation of cross-chain DeFi integrations.
The example target is correctly configured and points to the appropriate entry file at
examples/eth_to_base_vault_pancake/vault.rs
.docs/src/libraries/evm/forwarder.md (1)
43-43
: LGTM! Expanded maxAmount field capacity.The type change from
uint128
touint256
for themaxAmount
field increases the maximum token value that can be forwarded, which is appropriate for financial operations that may involve large amounts.contracts/encoders/evm-encoder/src/libraries/forwarder.rs (3)
3-3
: LGTM! Added Uint256 import.Added import for Uint256 from cosmwasm_std to support the type change in ForwardingConfig.
34-34
: LGTM! Updated max_amount field type.Changed the field type from
u128
toUint256
to support larger token amounts for forwarding, which aligns with the changes in the Solidity contract and documentation.
68-68
: LGTM! Updated encoding logic for Uint256.Updated the encoding logic to correctly convert the Uint256 value to U256 using its big-endian byte representation, ensuring compatibility with the Solidity library.
e2e/src/utils/solidity_contracts.rs (4)
55-61
: LGTM! Added enhanced CCTPTransfer binding.Properly defines the CCTPTransfer contract binding with RPC support and useful debugging traits.
63-69
: LGTM! Added AavePositionManager binding.Added new contract binding for AavePositionManager with appropriate annotations for RPC interaction and debugging, which is needed for the vault strategy on Ethereum.
71-77
: LGTM! Added StandardBridgeTransfer binding.Added new contract binding for StandardBridgeTransfer with RPC support, which is required for the cross-chain bridging functionality.
79-85
: LGTM! Added PancakeSwapV3PositionManager binding.Added new contract binding for PancakeSwapV3PositionManager with RPC support, which is needed for the vault strategy on Base.
e2e/src/utils/mocks/cctp_relayer_evm_evm.rs (7)
4-4
: LGTM! Added BlockNumberOrTag import.Added import to support block range filtering in event log queries.
60-60
: LGTM! Added block tracking fields.Added fields to track the last processed block for both EVM chains, enabling incremental log processing.
Also applies to: 64-64
107-107
: LGTM! Initialized block tracking fields.Properly initialized the new last block processed fields to None in the constructor.
Also applies to: 111-111
209-220
: LGTM! Implemented incremental log processing for EVM A.Enhanced the log retrieval process to only fetch logs from the last processed block to the current block, which improves efficiency and prevents duplicate processing.
243-244
: LGTM! Added state update for EVM A.Properly updates the last processed block after handling logs to maintain incremental processing state.
257-269
: LGTM! Implemented incremental log processing for EVM B.Similar to EVM A, improved the log retrieval process to only fetch logs from the last processed block to the current block.
291-292
: LGTM! Added state update for EVM B.Properly updates the last processed block after handling logs to maintain incremental processing state.
e2e/examples/eth_to_base_vault_pancake/strategist/strategy.rs (2)
157-166
: Integer division silently drops remainder – verify rounding rules
let weth_to_supply = balance / 3 * 2; let weth_to_bridge = balance / 3
Any remainder (balance % 3
) is ignored, which may cause drift in accounting.
Confirm this is intentional; otherwise use precise proportional splits (e.g.(balance * 2) / 3
).
316-360
:⚠️ Potential issueCompilation fails: floating-point literals used for
U256
constants
U256::from(1e8)
,U256::from(1e17)
,U256::from(1e6)
(lines ~318, 342, 359) are f64 literals;U256::from
expects an integer type, causing a hard compile error:expected integer, found floating-point number
Fix by using string or integer literals:
- U256::from(1e8) + U256::from_dec_str("100000000")?Apply the same pattern for the other two occurrences (
1e17
→"100000000000000000"
,1e6
→"1000000"
).Also applies to: 341-344
⛔ Skipped due to learnings
Learnt from: keyleu PR: timewave-computer/valence-protocol#334 File: e2e/examples/eth_to_base_vault_pancake/ethereum.rs:226-227 Timestamp: 2025-04-25T22:11:33.974Z Learning: In the Valence protocol codebase, `U256::from(1e6)` is valid syntax that compiles successfully despite 1e6 being written as a floating-point literal. This is used in multiple places within the project, particularly when setting token precision values.
Learnt from: keyleu PR: timewave-computer/valence-protocol#334 File: e2e/examples/eth_to_base_vault_pancake/ethereum.rs:226-227 Timestamp: 2025-04-25T22:11:33.974Z Learning: In the context of the Valence protocol, `U256::from(1e6)` is valid syntax and compiles successfully despite 1e6 being a floating-point literal in Rust.
e2e/src/utils/mocks/standard_bridge_relayer.rs (3)
1-21
: Properly organized imports and constants.The imports are well-structured, separating standard library components, external crates, and internal modules. The POLLING_PERIOD constant is appropriately defined at the top level for easy configuration.
23-32
: Good use ofsol!
macro for event definition.The Solidity event definition is well-structured and properly typed, following best practices for cross-chain event handling. The indexed parameters help with efficient event filtering.
34-77
: Clean struct organization with clear responsibility separation.The code follows good design principles by separating the relayer's state from its runtime components. This makes the code more maintainable and easier to test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on a high level this looks really nice so far! It's hard to pinpoint any errors in this so approving it. If anything comes up then it will surface during the full e2e flow
First PR for the vault that will use AAVE and PancakeV3.
Sets up the entire program and tests the following part of the flow:
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Documentation
Style
Chores