Skip to main content

wp_evm_aave_v3/
lib.rs

1//! Aave v3 protocol facade.
2//!
3//! Thin wrapper that bakes Aave v3's per-chain market addresses into the
4//! `wp-evm-aave-v3` family read functions. Adding an Aave-v3 fork (Spark,
5//! etc.) = copying this file and changing the addresses.
6
7use alloy_primitives::{address, Address};
8use alloy_provider::{network::Ethereum, Provider};
9use anyhow::Result;
10use wp_evm_aave_v3_provider as aave;
11use wp_evm_base::chain::Chain;
12
13// Re-export family types (through the provider's `data` re-export) so
14// consumers only need `wp-evm-aave-v3`. A single `pub use` both brings the
15// names into local scope (used by `CONFIG` and the fn signatures below) and
16// re-exports them — no separate `use` line, no duplicate-import nit.
17pub use wp_evm_aave_v3_provider::data::{
18    AaveV3MarketConfig, ReserveState, SupplyParams, UserAccountData, WithdrawParams,
19};
20
21const MULTICALL3: Address = address!("cA11bde05977b3631167028862bE2a173976CA11");
22
23/// Aave v3 Ethereum mainnet market configuration.
24///
25/// Addresses verified 2026-05-31 against bgd-labs/aave-address-book
26/// (`AaveV3Ethereum`).
27pub const CONFIG: AaveV3MarketConfig = AaveV3MarketConfig {
28    addresses_provider: address!("2f39d218133AFaB8F2B819B1066c7E434Ad94E9e"),
29    pool: address!("87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"),
30    protocol_data_provider: address!("0a16f2FCC0D44FaE41cc54e079281D84A363bECD"),
31    multicall: MULTICALL3,
32};
33
34/// Aave v3 market config shared by Arbitrum, Optimism, Polygon, and
35/// Avalanche — these four were deployed deterministically to one address
36/// set. Addresses verified 2026-05-31 against bgd-labs/aave-address-book.
37const SHARED_L2: AaveV3MarketConfig = AaveV3MarketConfig {
38    addresses_provider: address!("a97684ead0e402dC232d5A977953DF7ECBaB3CDb"),
39    pool: address!("794a61358D6845594F94dc1DB02A252b5b4814aD"),
40    protocol_data_provider: address!("243Aa95cAC2a25651eda86e80bEe66114413c43b"),
41    multicall: MULTICALL3,
42};
43
44/// Aave v3 Arbitrum One market config.
45pub const CONFIG_ARBITRUM: AaveV3MarketConfig = SHARED_L2;
46/// Aave v3 Optimism market config.
47pub const CONFIG_OPTIMISM: AaveV3MarketConfig = SHARED_L2;
48/// Aave v3 Polygon PoS market config.
49pub const CONFIG_POLYGON: AaveV3MarketConfig = SHARED_L2;
50/// Aave v3 Avalanche C-Chain market config.
51pub const CONFIG_AVALANCHE: AaveV3MarketConfig = SHARED_L2;
52
53/// Aave v3 Base market config (its own deployment — distinct addresses).
54/// Addresses verified 2026-05-31 against bgd-labs/aave-address-book.
55pub const CONFIG_BASE: AaveV3MarketConfig = AaveV3MarketConfig {
56    addresses_provider: address!("e20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64D"),
57    pool: address!("A238Dd80C259a72e81d7e4664a9801593F98d1c5"),
58    protocol_data_provider: address!("0F43731EB8d45A581f4a36DD74F5f358bc90C73A"),
59    multicall: MULTICALL3,
60};
61
62/// Pick the Aave v3 market config for a curated chain.
63///
64/// Returns `None` for chains without a curated Aave v3 deployment
65/// (BSC/Celo not yet added; Sonic/HyperEVM have no Aave v3).
66pub fn config_for_chain(chain: Chain) -> Option<&'static AaveV3MarketConfig> {
67    match chain {
68        Chain::Ethereum => Some(&CONFIG),
69        Chain::Arbitrum => Some(&CONFIG_ARBITRUM),
70        Chain::Optimism => Some(&CONFIG_OPTIMISM),
71        Chain::Polygon => Some(&CONFIG_POLYGON),
72        Chain::Avalanche => Some(&CONFIG_AVALANCHE),
73        Chain::Base => Some(&CONFIG_BASE),
74        Chain::Bsc | Chain::Sonic | Chain::Celo | Chain::HyperEvm => None,
75    }
76}
77
78/// Hydrate a reserve's state on the given chain. Returns an error if the
79/// chain has no curated Aave v3 config yet.
80pub async fn reserve_state<P: Provider<Ethereum>>(
81    provider: &P,
82    chain: Chain,
83    asset: Address,
84) -> Result<ReserveState> {
85    let cfg = config_for_chain(chain)
86        .ok_or_else(|| anyhow::anyhow!("no Aave v3 config for chain {}", chain.name()))?;
87    aave::hydrate::reserve_state(provider, cfg.multicall, cfg.pool, asset).await
88}
89
90/// Hydrate an account's aggregate health on the given chain.
91pub async fn user_account_data<P: Provider<Ethereum>>(
92    provider: &P,
93    chain: Chain,
94    user: Address,
95) -> Result<UserAccountData> {
96    let cfg = config_for_chain(chain)
97        .ok_or_else(|| anyhow::anyhow!("no Aave v3 config for chain {}", chain.name()))?;
98    aave::hydrate::user_account_data(provider, cfg.multicall, cfg.pool, user).await
99}
100
101/// Build a `PlanFragment` for an Aave v3 `supply` on the given chain.
102pub fn plan_supply(
103    chain: Chain,
104    params: &SupplyParams,
105) -> Result<wp_evm_aave_v3_provider::data::PlanFragment> {
106    let cfg = config_for_chain(chain)
107        .ok_or_else(|| anyhow::anyhow!("no Aave v3 config for chain {}", chain.name()))?;
108    Ok(wp_evm_aave_v3_provider::plan::plan_supply(params, cfg.pool))
109}
110
111/// Build a `PlanFragment` for an Aave v3 `withdraw` on the given chain.
112pub fn plan_withdraw(
113    chain: Chain,
114    params: &WithdrawParams,
115) -> Result<wp_evm_aave_v3_provider::data::PlanFragment> {
116    let cfg = config_for_chain(chain)
117        .ok_or_else(|| anyhow::anyhow!("no Aave v3 config for chain {}", chain.name()))?;
118    Ok(wp_evm_aave_v3_provider::plan::plan_withdraw(params, cfg.pool))
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn ethereum_config_addresses_are_set() {
127        let cfg = config_for_chain(Chain::Ethereum).expect("ethereum config");
128        assert_eq!(cfg.pool, address!("87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2"));
129        assert_eq!(cfg.multicall, MULTICALL3);
130    }
131
132    #[test]
133    fn shared_l2_chains_use_canonical_pool() {
134        // Arbitrum / Optimism / Polygon / Avalanche share one deterministic
135        // Aave v3 deployment.
136        for chain in [Chain::Arbitrum, Chain::Optimism, Chain::Polygon, Chain::Avalanche] {
137            let cfg = config_for_chain(chain).unwrap_or_else(|| panic!("config for {chain}"));
138            assert_eq!(
139                cfg.pool,
140                address!("794a61358D6845594F94dc1DB02A252b5b4814aD"),
141                "wrong pool for {chain}"
142            );
143            assert_eq!(
144                cfg.addresses_provider,
145                address!("a97684ead0e402dC232d5A977953DF7ECBaB3CDb"),
146                "wrong addresses_provider for {chain}"
147            );
148            assert_eq!(cfg.multicall, MULTICALL3, "wrong multicall for {chain}");
149        }
150    }
151
152    #[test]
153    fn base_has_its_own_deployment() {
154        let cfg = config_for_chain(Chain::Base).expect("base config");
155        assert_eq!(cfg.pool, address!("A238Dd80C259a72e81d7e4664a9801593F98d1c5"));
156        assert_eq!(cfg.addresses_provider, address!("e20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64D"));
157        assert_eq!(cfg.multicall, MULTICALL3);
158    }
159
160    #[test]
161    fn chains_without_aave_v3_return_none() {
162        // No Aave v3 deployment / not yet curated.
163        for chain in [Chain::Bsc, Chain::Sonic, Chain::Celo, Chain::HyperEvm] {
164            assert!(config_for_chain(chain).is_none(), "{chain} must be None");
165        }
166    }
167
168    #[test]
169    fn supply_plan_uses_ethereum_pool() {
170        use alloy_primitives::{address, U256};
171        let f = plan_supply(
172            Chain::Ethereum,
173            &SupplyParams {
174                asset: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
175                amount: U256::from(100u64),
176                on_behalf_of: address!("000000000000000000000000000000000000dEaD"),
177            },
178        )
179        .expect("ethereum supply plan");
180        assert_eq!(f.calls[0].target, CONFIG.pool);
181        assert_eq!(f.approvals[0].spender, CONFIG.pool);
182    }
183
184    #[test]
185    fn supply_plan_unsupported_chain_errors() {
186        use alloy_primitives::{address, U256};
187        let err = plan_supply(
188            Chain::Bsc,
189            &SupplyParams {
190                asset: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
191                amount: U256::from(1u64),
192                on_behalf_of: address!("000000000000000000000000000000000000dEaD"),
193            },
194        )
195        .unwrap_err();
196        assert!(format!("{err:#}").contains("no Aave v3 config for chain bsc"));
197    }
198}