use crate::primitives::cross2d;
use crate::types::ProtocolConfig;
pub fn is_convex(ring: &[[i64; 2]]) -> bool {
let n = ring.len();
if n < 3 {
return false;
}
let mut direction: i32 = 0;
for i in 0..n {
let prev = (i + n - 1) % n;
let next = (i + 1) % n;
let cross = cross2d(
ring[prev][0],
ring[prev][1],
ring[i][0],
ring[i][1],
ring[next][0],
ring[next][1],
);
if cross == 0 {
continue;
}
let cross_dir = if cross > 0 { 1i32 } else { -1i32 };
if direction == 0 {
direction = cross_dir;
} else if direction != cross_dir {
return false; }
}
true }
pub fn validate_edge_lengths(ring: &[[i64; 2]], config: &ProtocolConfig) -> Option<String> {
let n = ring.len();
for i in 0..n {
let j = (i + 1) % n;
let dx = (ring[j][0] as i128) - (ring[i][0] as i128);
let dy = (ring[j][1] as i128) - (ring[i][1] as i128);
let sq_len = (dx * dx + dy * dy) as u128;
if sq_len < config.min_edge_length_squared {
return Some(format!(
"edge {i}→{j} too short: {sq_len} < {}",
config.min_edge_length_squared
));
}
}
None
}
pub fn perimeter_l1(ring: &[[i64; 2]]) -> u128 {
let n = ring.len();
let mut perimeter: u128 = 0;
for i in 0..n {
let j = (i + 1) % n;
let dx = ring[j][0].abs_diff(ring[i][0]) as u128;
let dy = ring[j][1].abs_diff(ring[i][1]) as u128;
perimeter += dx + dy;
}
perimeter
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompactnessOutcome {
pub passes: bool,
pub ratio_ppm: u128,
pub min_ppm: u128,
}
pub fn check_compactness(
twice_area: u128,
perimeter: u128,
config: &ProtocolConfig,
) -> CompactnessOutcome {
let min_ppm = config.min_compactness_ppm;
let perimeter_sq = perimeter.checked_mul(perimeter);
let rhs = perimeter_sq.and_then(|p| min_ppm.checked_mul(p));
let lhs = 8_000_000u128.checked_mul(twice_area);
let passes = match (lhs, rhs) {
(None, _) => true, (Some(_), None) => false, (Some(l), Some(r)) => l >= r,
};
let ratio_ppm = match (lhs, perimeter_sq) {
(None, _) => u128::MAX,
(_, None) | (_, Some(0)) => 0,
(Some(l), Some(p_sq)) => l / p_sq,
};
CompactnessOutcome {
passes,
ratio_ppm,
min_ppm,
}
}
pub fn validate_compactness(
twice_area: u128,
perimeter: u128,
config: &ProtocolConfig,
) -> Option<String> {
let outcome = check_compactness(twice_area, perimeter, config);
if outcome.passes {
None
} else {
Some(format!(
"not compact: {} ppm < min {} ppm",
outcome.ratio_ppm, outcome.min_ppm
))
}
}
pub fn validate_part(ring: &[[i64; 2]], config: &ProtocolConfig) -> Option<String> {
let n = ring.len();
if n < 3 {
return Some(format!("part has {n} vertices, need >= 3"));
}
if n > config.max_vertices_per_part {
return Some(format!(
"part has {n} vertices, max is {}",
config.max_vertices_per_part
));
}
if !is_convex(ring) {
return Some("part is not convex".into());
}
if let Some(err) = validate_edge_lengths(ring, config) {
return Some(err);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::area::twice_area_fp2;
use crate::types::ProtocolConfig;
const M: i64 = 1_000_000;
fn merca_config() -> ProtocolConfig {
ProtocolConfig::merca()
}
#[test]
fn is_convex_accepts_square() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
assert!(is_convex(&ring));
}
#[test]
fn is_convex_rejects_l_shape() {
let ring = vec![
[0, 0],
[20 * M, 0],
[20 * M, 10 * M],
[10 * M, 10 * M],
[10 * M, 20 * M],
[0, 20 * M],
];
assert!(!is_convex(&ring));
}
#[test]
fn is_convex_accepts_weakly_convex_with_collinear() {
let ring = vec![
[0, 0],
[10 * M, 0],
[10 * M, 10 * M],
[5 * M, 10 * M],
[0, 10 * M],
];
assert!(is_convex(&ring));
}
#[test]
fn is_convex_accepts_triangle() {
let ring = vec![[0, 0], [10 * M, 0], [5 * M, 10 * M]];
assert!(is_convex(&ring));
}
#[test]
fn is_convex_rejects_two_points() {
let ring = vec![[0, 0], [1, 0]];
assert!(!is_convex(&ring));
}
#[test]
fn is_convex_accepts_convex_pentagon() {
let ring = vec![[0, 0], [2 * M, 0], [3 * M, M], [2 * M, 2 * M], [0, 2 * M]];
assert!(is_convex(&ring));
}
#[test]
fn validate_edge_lengths_valid() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
}
#[test]
fn validate_edge_lengths_rejects_short_edge() {
let ring = vec![[0, 0], [M / 2, 0], [M / 2, M], [0, M]]; assert!(validate_edge_lengths(&ring, &merca_config()).is_some());
}
#[test]
fn validate_edge_lengths_exact_threshold() {
let ring = vec![[0, 0], [M, 0], [M, M], [0, M]];
assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
}
#[test]
fn validate_edge_lengths_accepts_large_negative_coordinates() {
let ring = vec![[-1_000_000, 0], [1_000_000, 0]];
assert!(validate_edge_lengths(&ring, &merca_config()).is_none());
}
#[test]
fn validate_edge_lengths_rejects_unit_edge() {
let ring = vec![[0, 0], [1, 1], [1, 0]];
assert!(validate_edge_lengths(&ring, &merca_config()).is_some());
}
#[test]
fn perimeter_l1_uses_manhattan() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
let p = perimeter_l1(&ring);
assert_eq!(p, 4 * 10 * M as u128);
}
#[test]
fn perimeter_l1_not_euclidean() {
let ring = vec![[0, 0], [3 * M, 0], [0, 4 * M]]; let p = perimeter_l1(&ring);
assert_eq!(p, (3 + 7 + 4) * M as u128);
}
#[test]
fn perimeter_l1_handles_negative_coordinates() {
let ring = vec![[-1, -1], [-1, 1], [1, 1], [1, -1]];
assert_eq!(perimeter_l1(&ring), 8);
}
#[test]
fn perimeter_l1_large_square_does_not_overflow() {
let ring = vec![
[10_000_000 * M, 10_000_000 * M],
[11_000_000 * M, 10_000_000 * M],
[11_000_000 * M, 11_000_000 * M],
[10_000_000 * M, 11_000_000 * M],
];
assert_eq!(perimeter_l1(&ring), 4 * 1_000_000_000_000u128);
}
#[test]
fn validate_compactness_compact_square_passes() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
let twice_area = twice_area_fp2(&ring);
let perimeter = perimeter_l1(&ring);
assert!(validate_compactness(twice_area, perimeter, &merca_config()).is_none());
}
#[test]
fn validate_compactness_uses_checked_mul() {
let ring = vec![
[0, 0],
[10_000_000 * M, 0],
[10_000_000 * M, 10_000_000 * M],
[0, 10_000_000 * M],
];
let twice_area = twice_area_fp2(&ring);
let perimeter = perimeter_l1(&ring);
let _ = validate_compactness(twice_area, perimeter, &merca_config());
}
#[test]
fn validate_part_valid_square() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
assert!(validate_part(&ring, &merca_config()).is_none());
}
#[test]
fn validate_part_square_passes_both_configs() {
let ring = vec![[0, 0], [10 * M, 0], [10 * M, 10 * M], [0, 10 * M]];
let permissive = ProtocolConfig::permissive();
assert!(validate_part(&ring, &merca_config()).is_none());
assert!(validate_part(&ring, &permissive).is_none());
}
#[test]
fn validate_part_short_square_fails_merca_but_passes_permissive() {
let ring = vec![[0, 0], [M / 2, 0], [M / 2, M / 2], [0, M / 2]];
let permissive = ProtocolConfig::permissive();
assert!(validate_part(&ring, &merca_config()).is_some());
assert!(validate_part(&ring, &permissive).is_none());
}
#[test]
fn validate_part_rejects_l_shape() {
let ring = vec![
[0, 0],
[20 * M, 0],
[20 * M, 10 * M],
[10 * M, 10 * M],
[10 * M, 20 * M],
[0, 20 * M],
];
assert!(validate_part(&ring, &merca_config()).is_some());
}
#[test]
fn validate_part_rejects_too_few_vertices() {
assert!(validate_part(&[[0, 0], [M, 0]], &merca_config()).is_some());
}
}