use hopper_runtime::error::ProgramError;
#[inline(always)]
pub fn update_twap_cumulative(
cumulative: u128,
price: u64,
last_ts: i64,
now_ts: i64,
) -> Result<u128, ProgramError> {
if now_ts <= last_ts {
return Ok(cumulative);
}
let elapsed = (now_ts - last_ts) as u128;
let increment = (price as u128)
.checked_mul(elapsed)
.ok_or(ProgramError::ArithmeticOverflow)?;
cumulative
.checked_add(increment)
.ok_or(ProgramError::ArithmeticOverflow)
}
#[inline(always)]
pub fn compute_twap(
cumulative_start: u128,
cumulative_end: u128,
ts_start: i64,
ts_end: i64,
) -> Result<u64, ProgramError> {
if ts_end <= ts_start {
return Err(ProgramError::InvalidArgument);
}
let elapsed = (ts_end - ts_start) as u128;
let diff = cumulative_end
.checked_sub(cumulative_start)
.ok_or(ProgramError::ArithmeticOverflow)?;
let twap = diff / elapsed;
if twap > u64::MAX as u128 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(twap as u64)
}
#[inline(always)]
pub fn check_twap_deviation(
spot_price: u64,
twap_price: u64,
max_deviation_bps: u64,
) -> Result<(), ProgramError> {
if twap_price == 0 {
return Err(ProgramError::InvalidArgument);
}
let diff = spot_price.abs_diff(twap_price);
let deviation_bps = (diff as u128) * 10_000 / (twap_price as u128);
if deviation_bps > max_deviation_bps as u128 {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cumulative_accumulates() {
let c = update_twap_cumulative(0, 100, 0, 10).unwrap();
assert_eq!(c, 1_000);
let c = update_twap_cumulative(c, 200, 10, 20).unwrap();
assert_eq!(c, 3_000); }
#[test]
fn no_time_elapsed_unchanged() {
let c = update_twap_cumulative(500, 100, 10, 10).unwrap();
assert_eq!(c, 500);
let c = update_twap_cumulative(500, 100, 10, 5).unwrap();
assert_eq!(c, 500);
}
#[test]
fn compute_twap_basic() {
let twap = compute_twap(0, 1_000, 0, 10).unwrap();
assert_eq!(twap, 100);
}
#[test]
fn compute_twap_mixed() {
let twap = compute_twap(0, 3_000, 0, 20).unwrap();
assert_eq!(twap, 150);
}
#[test]
fn compute_twap_rejects_zero_elapsed() {
assert!(compute_twap(0, 100, 10, 10).is_err());
assert!(compute_twap(0, 100, 10, 5).is_err());
}
#[test]
fn deviation_check_passes_within_bounds() {
assert!(check_twap_deviation(105, 100, 500).is_ok());
assert!(check_twap_deviation(95, 100, 500).is_ok());
}
#[test]
fn deviation_check_rejects_excess() {
assert!(check_twap_deviation(106, 100, 500).is_err());
}
#[test]
fn deviation_check_rejects_zero_twap() {
assert!(check_twap_deviation(100, 0, 500).is_err());
}
#[test]
fn full_twap_cycle() {
let mut cumulative = 0u128;
let mut last_ts = 1_000i64;
let prices = [100u64, 120, 80, 150, 110];
for (i, &p) in prices.iter().enumerate() {
let now = last_ts + 10;
cumulative = update_twap_cumulative(cumulative, p, last_ts, now).unwrap();
last_ts = now;
let _ = i;
}
let twap = compute_twap(0, cumulative, 1_000, 1_050).unwrap();
assert_eq!(twap, 112);
assert!(check_twap_deviation(115, twap, 300).is_ok()); assert!(check_twap_deviation(150, twap, 300).is_err()); }
}