Health token adjustment: Deal with odd I80F48 behavior

The identity
  a * b = -((-a) * b)
does not hold for I80F48, probably it's rounding to -inf.

This meant that withdrawing the full native token balance could
fail because the magnitude of -position * price was bigger than
position * price.
This commit is contained in:
Christian Kamm 2022-09-01 14:20:12 +02:00
parent 3b93a38395
commit 511db72f8e
2 changed files with 20 additions and 1 deletions

View File

@ -652,7 +652,12 @@ impl HealthCache {
pub fn adjust_token_balance(&mut self, token_index: TokenIndex, change: I80F48) -> Result<()> {
let entry_index = self.token_entry_index(token_index)?;
let mut entry = &mut self.token_infos[entry_index];
entry.balance = cm!(entry.balance + change * entry.oracle_price);
// Work around the fact that -((-x) * y) == x * y does not hold for I80F48:
// We need to make sure that if balance is before * price, then change = -before
// brings it to exactly zero.
let removed_contribution = (-change) * entry.oracle_price;
entry.balance = cm!(entry.balance - removed_contribution);
Ok(())
}

View File

@ -38,3 +38,17 @@ pub fn format_zero_terminated_utf8_bytes(
.trim_matches(char::from(0)),
)
}
#[cfg(test)]
mod tests {
use fixed::types::I80F48;
#[test]
pub fn test_i80f48_mul_rounding() {
// It's not desired, but I80F48 seems to round to -inf
let price = I80F48::from_num(0.04);
let x = I80F48::from_bits(96590783907000000);
assert_eq!((x * price).to_string(), "13.726375969298193");
assert_eq!(((-x) * price).to_string(), "-13.726375969298196");
}
}