ScanningAccountRetriever: Don't mut-borrow oracles (#170)

This allows using the same oracle for different banks, like for ETH and
soETH.
This commit is contained in:
Christian Kamm 2022-08-15 11:32:54 +02:00 committed by GitHub
parent 551e101b08
commit c9a5fb5fc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 58 additions and 37 deletions

View File

@ -38,6 +38,10 @@ impl<'a, 'info: 'a> AccountInfoRef<'a, 'info> {
//data: account_info.try_borrow_data()?,
})
}
pub fn borrow_slice(ais: &'a [AccountInfo<'info>]) -> Result<Vec<Self>> {
ais.iter().map(Self::borrow).collect()
}
}
pub struct AccountInfoRefMut<'a, 'info: 'a> {
@ -57,6 +61,10 @@ impl<'a, 'info: 'a> AccountInfoRefMut<'a, 'info> {
.map_err(|_| ProgramError::AccountBorrowFailed)?,
})
}
pub fn borrow_slice(ais: &'a [AccountInfo<'info>]) -> Result<Vec<Self>> {
ais.iter().map(Self::borrow).collect()
}
}
impl<'info, 'a> AccountReader for AccountInfoRef<'info, 'a> {

View File

@ -72,10 +72,7 @@ pub fn new_fixed_order_account_retriever<'a, 'info>(
require_eq!(ais.len(), expected_ais);
Ok(FixedOrderAccountRetriever {
ais: ais
.iter()
.map(AccountInfoRef::borrow)
.collect::<Result<Vec<_>>>()?,
ais: AccountInfoRef::borrow_slice(ais)?,
n_banks: active_token_len,
begin_perp: cm!(active_token_len * 2),
begin_serum3: cm!(active_token_len * 2 + active_perp_len),
@ -174,7 +171,10 @@ impl<T: KeyedAccountReader> AccountRetriever for FixedOrderAccountRetriever<T> {
/// and retrieves accounts needed for the health computation by doing a linear
/// scan for each request.
pub struct ScanningAccountRetriever<'a, 'info> {
ais: Vec<AccountInfoRefMut<'a, 'info>>,
banks: Vec<AccountInfoRefMut<'a, 'info>>,
oracles: Vec<AccountInfoRef<'a, 'info>>,
perp_markets: Vec<AccountInfoRef<'a, 'info>>,
serum3_oos: Vec<AccountInfoRef<'a, 'info>>,
token_index_map: HashMap<TokenIndex, usize>,
perp_index_map: HashMap<PerpMarketIndex, usize>,
}
@ -212,11 +212,12 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
})()
.with_context(|| format!("scanning banks, health account index {}", i))
})?;
let n_banks = token_index_map.len();
// skip all banks and oracles, then find number of PerpMarket accounts
let skip = token_index_map.len() * 2;
let mut perp_index_map = HashMap::with_capacity(ais.len() - skip);
ais[skip..]
let perps_start = n_banks * 2;
let mut perp_index_map = HashMap::with_capacity(ais.len() - perps_start);
ais[perps_start..]
.iter()
.enumerate()
.map_while(can_load_as::<PerpMarket>)
@ -224,32 +225,30 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
(|| {
let perp_market = loaded?;
require_keys_eq!(perp_market.group, *group);
perp_index_map.insert(perp_market.perp_market_index, cm!(skip + i));
perp_index_map.insert(perp_market.perp_market_index, i);
Ok(())
})()
.with_context(|| {
format!("scanning perp markets, health account index {}", i + skip)
format!(
"scanning perp markets, health account index {}",
i + perps_start
)
})
})?;
let n_perps = perp_index_map.len();
let serum3_start = perps_start + n_perps;
Ok(Self {
ais: ais
.iter()
.map(AccountInfoRefMut::borrow)
.collect::<Result<Vec<_>>>()?,
banks: AccountInfoRefMut::borrow_slice(&ais[..n_banks])?,
oracles: AccountInfoRef::borrow_slice(&ais[n_banks..2 * n_banks])?,
perp_markets: AccountInfoRef::borrow_slice(&ais[perps_start..perps_start + n_perps])?,
serum3_oos: AccountInfoRef::borrow_slice(&ais[serum3_start..])?,
token_index_map,
perp_index_map,
})
}
fn n_banks(&self) -> usize {
self.token_index_map.len()
}
fn begin_serum3(&self) -> usize {
2 * self.token_index_map.len() + self.perp_index_map.len()
}
#[inline]
fn bank_index(&self, token_index: TokenIndex) -> Result<usize> {
Ok(*self
@ -272,12 +271,10 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
token_index1: TokenIndex,
token_index2: TokenIndex,
) -> Result<(&mut Bank, I80F48, Option<(&mut Bank, I80F48)>)> {
let n_banks = self.n_banks();
if token_index1 == token_index2 {
let index = self.bank_index(token_index1)?;
let (bank_part, oracle_part) = self.ais.split_at_mut(index + 1);
let bank = bank_part[index].load_mut_fully_unchecked::<Bank>()?;
let oracle = &oracle_part[n_banks - 1];
let bank = self.banks[index].load_mut_fully_unchecked::<Bank>()?;
let oracle = &self.oracles[index];
require_keys_eq!(bank.oracle, *oracle.key);
let price = oracle_price(oracle, bank.oracle_config.conf_filter, bank.mint_decimals)?;
return Ok((bank, price, None));
@ -291,13 +288,12 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
};
// split_at_mut after the first bank and after the second bank
let (first_bank_part, second_part) = self.ais.split_at_mut(first + 1);
let (second_bank_part, oracles_part) = second_part.split_at_mut(second - first);
let (first_bank_part, second_bank_part) = self.banks.split_at_mut(first + 1);
let bank1 = first_bank_part[first].load_mut_fully_unchecked::<Bank>()?;
let bank2 = second_bank_part[second - (first + 1)].load_mut_fully_unchecked::<Bank>()?;
let oracle1 = &oracles_part[cm!(n_banks + first - (second + 1))];
let oracle2 = &oracles_part[cm!(n_banks + second - (second + 1))];
let oracle1 = &self.oracles[first];
let oracle2 = &self.oracles[second];
require_keys_eq!(bank1.oracle, *oracle1.key);
require_keys_eq!(bank2.oracle, *oracle2.key);
let mint_decimals1 = bank1.mint_decimals;
@ -313,8 +309,8 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
pub fn scanned_bank_and_oracle(&self, token_index: TokenIndex) -> Result<(&Bank, I80F48)> {
let index = self.bank_index(token_index)?;
let bank = self.ais[index].load_fully_unchecked::<Bank>()?;
let oracle = &self.ais[cm!(self.n_banks() + index)];
let bank = self.banks[index].load_fully_unchecked::<Bank>()?;
let oracle = &self.oracles[index];
require_keys_eq!(bank.oracle, *oracle.key);
Ok((
bank,
@ -324,11 +320,12 @@ impl<'a, 'info> ScanningAccountRetriever<'a, 'info> {
pub fn scanned_perp_market(&self, perp_market_index: PerpMarketIndex) -> Result<&PerpMarket> {
let index = self.perp_market_index(perp_market_index)?;
self.ais[index].load_fully_unchecked::<PerpMarket>()
self.perp_markets[index].load_fully_unchecked::<PerpMarket>()
}
pub fn scanned_serum_oo(&self, key: &Pubkey) -> Result<&OpenOrders> {
let oo = self.ais[self.begin_serum3()..]
let oo = self
.serum3_oos
.iter()
.find(|ai| ai.key == key)
.ok_or_else(|| error_msg!("no serum3 open orders for key {}", key))?;
@ -1143,6 +1140,10 @@ mod tests {
let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1);
let (mut bank2, mut oracle2) = mock_bank_and_oracle(group, 4, 5.0, 0.5, 0.3);
let (mut bank3, _) = mock_bank_and_oracle(group, 5, 1.0, 0.5, 0.3);
// bank3 reuses the bank2 oracle, to ensure the ScanningAccountRetriever doesn't choke on that
bank3.data().oracle = oracle2.pubkey;
let mut oo1 = TestAccount::<OpenOrders>::new_zeroed();
let oo1key = oo1.pubkey;
@ -1152,20 +1153,26 @@ mod tests {
perp1.data().group = group;
perp1.data().perp_market_index = 9;
let oracle2_account_info = oracle2.as_account_info();
let ais = vec![
bank1.as_account_info(),
bank2.as_account_info(),
bank3.as_account_info(),
oracle1.as_account_info(),
oracle2.as_account_info(),
oracle2_account_info.clone(),
oracle2_account_info,
perp1.as_account_info(),
oo1.as_account_info(),
];
let mut retriever = ScanningAccountRetriever::new(&ais, &group).unwrap();
assert_eq!(retriever.n_banks(), 2);
assert_eq!(retriever.begin_serum3(), 5);
assert_eq!(retriever.banks.len(), 3);
assert_eq!(retriever.token_index_map.len(), 3);
assert_eq!(retriever.oracles.len(), 3);
assert_eq!(retriever.perp_markets.len(), 1);
assert_eq!(retriever.perp_index_map.len(), 1);
assert_eq!(retriever.serum3_oos.len(), 1);
{
let (b1, o1, opt_b2o2) = retriever.banks_mut_and_oracles(1, 4).unwrap();
@ -1194,6 +1201,12 @@ mod tests {
retriever.banks_mut_and_oracles(4, 2).unwrap_err();
{
let (b, o) = retriever.scanned_bank_and_oracle(5).unwrap();
assert_eq!(b.token_index, 5);
assert_eq!(o, 5 * I80F48::ONE);
}
let oo = retriever.serum_oo(0, &oo1key).unwrap();
assert_eq!(identity(oo.native_pc_total), 20);