check for 0 input sats and tokens and filter out, and fix potential overflow issues

This commit is contained in:
Håvard Kittelsen 2025-12-19 12:01:18 +01:00
parent 1b7801dff8
commit c247541410
2 changed files with 49 additions and 9 deletions

View file

@ -3,15 +3,16 @@
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later. // This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
use log::warn;
use poolperiod::PoolPeriod; use poolperiod::PoolPeriod;
use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State}; use rocket::{get, http::Status, response::status::Custom, serde::json::Json, State};
use serde_json::json;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
db::{cauldron::pool::get_pool_period_snapshot, DB}, db::{cauldron::pool::get_pool_period_snapshot, DB},
timeutil::time_now, timeutil::time_now,
}; };
use serde_json::json;
pub mod apyaggregator; pub mod apyaggregator;
pub mod poolperiod; pub mod poolperiod;
@ -81,14 +82,17 @@ pub fn aggregate_apy(
.get() .get()
.map_err(|e| Custom(Status::InternalServerError, format!("DB error: {e}")))?; .map_err(|e| Custom(Status::InternalServerError, format!("DB error: {e}")))?;
let pools: anyhow::Result<Vec<PoolPeriod>> = let pools: Vec<PoolPeriod> = get_pool_period_snapshot(&conn, token, pkh, start, end)
get_pool_period_snapshot(&conn, token, pkh, start, end) .map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?
.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))? .into_iter()
.into_iter() .filter_map(|(start, end)| match PoolPeriod::new(start.clone(), end) {
.map(|(start, end)| PoolPeriod::new(start, end)) Ok(period) => Some(period),
.collect(); Err(e) => {
warn!("Skipping invalid pool {}: {e}", start.pool_id);
let pools = pools.map_err(|e| Custom(Status::InternalServerError, format!("Error: {e}")))?; None
}
})
.collect();
let pools_count = pools.len(); let pools_count = pools.len();
let apy = apyaggregator::APYAggregator::aggregate_apy(pools.into_iter(), Some(start as u64)) let apy = apyaggregator::APYAggregator::aggregate_apy(pools.into_iter(), Some(start as u64))

View file

@ -177,4 +177,40 @@ mod tests {
"expected {expected_apy} != actual {pool_apy}" "expected {expected_apy} != actual {pool_apy}"
); );
} }
#[test]
fn test_zero_sats_rejected() {
let start = PoolSnapshot::dummy(1000, 0, 100); // zero sats
let end = PoolSnapshot::dummy(2000, 100, 100);
let result = PoolPeriod::new(start, end);
assert!(result.is_err());
}
#[test]
fn test_zero_token_amount_rejected() {
let start = PoolSnapshot::dummy(1000, 100, 0); // zero token_amount
let end = PoolSnapshot::dummy(2000, 100, 100);
let result = PoolPeriod::new(start, end);
assert!(result.is_err());
}
#[test]
fn test_large_k_no_overflow() {
// Values that would overflow u64 if multiplied directly
// 10^10 * 10^10 = 10^20 > u64::MAX (~1.8 * 10^19)
let start = PoolSnapshot::dummy(1000, 10_000_000_000, 10_000_000_000);
let end = PoolSnapshot::dummy(2000, 10_000_000_000, 10_000_000_000);
let result = PoolPeriod::new(start, end);
assert!(result.is_ok(), "Should handle large values without overflow");
}
#[test]
fn test_yield_calculation_valid() {
let start = PoolSnapshot::dummy(1000, 1000, 1000);
let end = PoolSnapshot::dummy(2000, 1100, 1100); // 10% increase each
let period = PoolPeriod::new(start, end).unwrap();
let yield_result = period.pool_yield().unwrap();
// sqrt(1.1 * 1.1) - 1 = 1.1 - 1 = 0.1 = 10%
assert!((yield_result - dec!(10)).abs() < dec!(0.01));
}
} }