Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ chain = "bnb"
pool = "0x6807dc923806fe8fd134338eabca509979a7e0cb"
# Aave V3 PoolDataProvider on BSC — resolves aTokens and reserve
# configuration bitmaps (paused / frozen flags) for the adapter.
data_provider = "0x41393e5e337606dc3821075Af65AeE84D7688CBD"
# Sourced from IPoolAddressesProvider.getPoolDataProvider() — the
# previous baked-in 0x41393e5e337606dc3821075Af65AeE84D7688CBD has no
# code on BSC mainnet, which made every Aave reserve probe revert with
# `buffer overrun` and silently disqualified the only flash-loan
# source. See #419.
data_provider = "0xc90Df74A7c16245c5F5C5870327Ceb38Fe5d5328"
# Aave V3 IPoolAddressesProvider on BSC — `getPool()` is called at
# startup and the result is compared against `pool` above. Mismatch
# fails the boot before the bot can burn RPC budget on a stale pool.
Expand Down
10 changes: 7 additions & 3 deletions config/fork.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,13 @@ comptroller = "0xfd36e2c2a6789db23113685031d7f16329158384"
[flashloan.aave_v3_bsc]
chain = "bnb"
pool = "0x6807dc923806fe8fd134338eabca509979a7e0cb"
# Aave V3 PoolDataProvider on BSC — matches default.toml. Resolves
# aTokens and reserve configuration bitmaps for the adapter.
data_provider = "0x41393e5e337606dc3821075Af65AeE84D7688CBD"
# Aave V3 PoolDataProvider on BSC — resolves aTokens and reserve
# configuration bitmaps for the adapter. The previous value
# 0x41393e5e337606dc3821075Af65AeE84D7688CBD has no code on BSC
# mainnet (cast code returns 0x); the live address comes from
# IPoolAddressesProvider.getPoolDataProvider() and is the one below.
# See #419.
data_provider = "0xc90Df74A7c16245c5F5C5870327Ceb38Fe5d5328"
# Aave V3 IPoolAddressesProvider — startup check is bypassed under
# profile_tag = "fork" but kept here so the fork profile mirrors the
# mainnet TOML structure 1:1.
Expand Down
53 changes: 47 additions & 6 deletions crates/charon-flashloan/src/aave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,18 +283,59 @@ impl AaveFlashLoan {
}
// Paused is not exposed via the typed accessor, so read the
// packed bitmap and check bit 60 ourselves.
let data = dp
.getReserveData(asset)
.call()
.await
.map_err(|e| FlashLoanError::rpc(format!("getReserveData: {e}")))?;
if bitmap_says_paused(data.configuration) || bitmap_says_frozen(data.configuration) {
//
// Aave V3 BSC's PoolDataProvider returns 12 fields here, not
// the 15 our `sol!` ABI declares (newer-Aave fields like
// `accruedToTreasury`, `unbacked`, `isolationModeTotalDebt`
// are absent on BSC's deploy). The strict typed decoder
// reports `buffer overrun while deserializing` on every
// quote, which silently disqualifies the only flash-loan
// source. We only need the leading `configuration` word for
// the paused/frozen bitmap check anyway, so issue the call
// ourselves and lift the first 32 bytes. See #419.
let configuration = read_uint256_first_word(self.provider.as_ref(), self.data_provider, {
IAaveV3DataProvider::getReserveDataCall { asset }
.abi_encode()
.into()
})
.await
.map_err(|e| FlashLoanError::rpc(format!("getReserveData: {e}")))?;
if bitmap_says_paused(configuration) || bitmap_says_frozen(configuration) {
return Err(FlashLoanError::ReservePaused { asset });
}
Ok(())
}
}

/// Issue an `eth_call` to `target` with `calldata` and decode the
/// first 32 bytes of the response as a `U256`. Used to read the
/// leading word of a tuple return when the on-chain ABI has more or
/// fewer fields than the workspace's `sol!` declaration would tolerate
/// — alloy's strict decoder rejects any mismatch with `buffer overrun`
/// or `ReserveMismatch`, but for paused/frozen-bitmap checks we only
/// need the first word and can safely drop the tail. See #419.
async fn read_uint256_first_word(
provider: &RootProvider<PubSubFrontend>,
target: Address,
calldata: alloy::primitives::Bytes,
) -> Result<U256> {
use alloy::rpc::types::TransactionRequest;
let tx = TransactionRequest::default()
.to(target)
.input(calldata.into());
let bytes = provider.call(&tx).await.context("eth_call failed")?;
if bytes.len() < 32 {
anyhow::bail!(
"expected at least 32 bytes from tuple return, got {} (target={})",
bytes.len(),
target
);
}
let mut buf = [0u8; 32];
buf.copy_from_slice(&bytes[..32]);
Ok(U256::from_be_bytes(buf))
}

/// Return true when bit `index` is set in the Aave packed
/// configuration `U256`.
fn bit_is_set(bitmap: U256, index: u32) -> bool {
Expand Down
Loading