use anchor_lang::prelude::{*, borsh::BorshSchema};
use std::io::Write;
use crate::drift::SafeMath;
use crate::errors::ReflectErrorCodes;
use crate::reflect::helpers::{calc_all_cuts, compute_receipt_token};
#[repr(C)]
#[derive(BorshSchema, AnchorDeserialize, AnchorSerialize, Default, Debug, Clone, InitSpace, PartialEq, Eq)]
pub struct AutoCompound {
pub deposited_vault_value: u64,
pub net_user_flow_since_capture: i64,
pub last_pool_value: u64,
pub queued_recipient_shares: u64,
}
impl AutoCompound {
pub fn update_total_capturable_yield(&mut self, value: u64){
self.queued_recipient_shares = value;
}
pub fn calculate_capturable_yield(&self, current_value: u64) -> Result<i64> {
let expected_value = if self.net_user_flow_since_capture >= 0 {
self.last_pool_value.safe_add(self.net_user_flow_since_capture as u64)?
} else {
self.last_pool_value.safe_sub((-self.net_user_flow_since_capture) as u64)?
};
let yield_or_loss = (current_value as i128) - (expected_value as i128);
if yield_or_loss > i64::MAX as i128 {
return Err(ReflectErrorCodes::MathError.into());
} else if yield_or_loss < i64::MIN as i128 {
return Err(ReflectErrorCodes::MathError.into());
}
Ok(yield_or_loss as i64)
}
pub fn update_pool_value(&mut self, value: u64){
self.last_pool_value = value;
}
pub fn update_pool(&mut self, current_total_usdc: u64, cuts_bp: &[u16], token_supply: u64) -> Result<()> {
let y: i64 = self.calculate_capturable_yield(current_total_usdc)?;
if y < 0 {
self.deposited_vault_value = self.deposited_vault_value.safe_sub(y.unsigned_abs())?;
} else if y > 0 {
let profit_usdc: u64 = y as u64;
let amounts_usdc: Vec<u64> = calc_all_cuts(profit_usdc, cuts_bp.to_vec())?;
let pool_keep_usdc = amounts_usdc.get(0).copied().unwrap_or(0);
let recipients_usdc = profit_usdc.safe_sub(pool_keep_usdc)?;
self.deposited_vault_value = self.deposited_vault_value.safe_add(profit_usdc)?;
if recipients_usdc > 0 {
require!(token_supply > 0, ReflectErrorCodes::MathError);
require!(self.deposited_vault_value > recipients_usdc, ReflectErrorCodes::MathError);
let v_minus_r: u64 = self.deposited_vault_value.safe_sub(recipients_usdc)?;
let shares_to_mint: u64 = compute_receipt_token(recipients_usdc, v_minus_r, token_supply)?;
self.queued_recipient_shares = self.queued_recipient_shares.safe_add(shares_to_mint)?;
}
}
self.last_pool_value = current_total_usdc;
self.net_user_flow_since_capture = 0;
Ok(())
}
pub fn deserialize(buf: &mut &[u8]) -> Result<Self> {
let deposited_vault_value = u64::deserialize(buf)?;
let net_user_flow_since_capture = i64::deserialize(buf)?;
let last_pool_value = u64::deserialize(buf)?;
let queued_recipient_shares = u64::deserialize(buf)?;
Ok(AutoCompound {
deposited_vault_value,
net_user_flow_since_capture,
last_pool_value,
queued_recipient_shares,
})
}
pub fn try_serialise<W: Write>(&self, writer: &mut W) -> Result<()> {
self.deposited_vault_value.serialize(writer)?;
self.net_user_flow_since_capture.serialize(writer)?;
self.last_pool_value.serialize(writer)?;
self.queued_recipient_shares.serialize(writer)?;
Ok(())
}
}
pub const AUTOCOMPOUND_START: usize = 1026;
pub const USDC_CONTROLLER_SIZE: usize = 10000;
pub fn deserialise_autocompound(data_usdc_controller: &[u8]) -> Result<AutoCompound> {
if data_usdc_controller.len() < AUTOCOMPOUND_START + 40 {
return Err(ReflectErrorCodes::InsufficientData.into());
}
let mut slice = &data_usdc_controller[AUTOCOMPOUND_START..];
AutoCompound::deserialize(&mut slice)
.map_err(|_| ReflectErrorCodes::DeserializationError.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialise_autocompound_insufficient_data() {
let short_data = vec![0u8; AUTOCOMPOUND_START + 39]; let result = deserialise_autocompound(&short_data);
assert!(result.is_err());
}
#[test]
fn test_deserialise_autocompound_corrupted_data() {
let bad_data = vec![0xFF; 100]; let result = deserialise_autocompound(&bad_data);
assert!(result.is_err());
}
#[test]
fn mainnet_debug_autocompound_from_file() {
use std::fs;
use std::path::PathBuf;
const LOCAL_CONTROLLER: &str = "usdc_controller_account.bin";
fn get_test_assets_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("./test_assets/mainnet/");
path
}
let assets_dir = get_test_assets_dir();
let data = fs::read(assets_dir.join(LOCAL_CONTROLLER)).expect(&format!("Missing: {}", LOCAL_CONTROLLER));
println!("\n=== AutoCompound decode ===");
println!("buffer len: {}", data.len());
println!("AUTOCOMPOUND_START: {}", AUTOCOMPOUND_START);
let ac = deserialise_autocompound(&data).expect("Failed to deserialize AutoCompound");
println!("deposited_vault_value : {}", ac.deposited_vault_value);
println!("net_user_flow_since_capture : {}", ac.net_user_flow_since_capture);
println!("last_pool_value : {}", ac.last_pool_value);
println!("queued_recipient_shares : {}", ac.queued_recipient_shares);
println!("V (USDC) : {:.6}", ac.deposited_vault_value as f64 / 1_000_000.0);
println!("baseline (USDC) : {:.6}", ac.last_pool_value as f64 / 1_000_000.0);
assert!(data.len() >= AUTOCOMPOUND_START + 40, "fixture too small for AutoCompound");
}
fn plausible(ac: &AutoCompound) -> bool {
let v = ac.deposited_vault_value as u128;
let b = ac.last_pool_value as u128;
let q = ac.queued_recipient_shares as u128;
let nf = ac.net_user_flow_since_capture;
if v == 0 && b == 0 && q == 0 && nf == 0 { return false; }
if v > 1_000_000_000_000_000u128 || b > 1_000_000_000_000_000u128 { return false; }
if v > 0 && (b < v / 4 || b > v.saturating_mul(4)) { return false; }
if v > 0 && (nf.unsigned_abs() as u128) > v.saturating_mul(2) { return false; }
true
}
pub fn deserialise_autocompound_dynamic(buf: &[u8]) -> Result<(AutoCompound, usize)> {
use anchor_lang::prelude::AnchorDeserialize;
if buf.len() >= 8 + 32 {
if let Ok(mut cur) = <&[u8] as TryFrom<&[u8]>>::try_from(&buf[8..]) {
if let Ok(ac) = AutoCompound::deserialize(&mut cur) {
if plausible(&ac) {
return Ok((ac, 8));
}
}
}
}
if buf.len() >= AUTOCOMPOUND_START + 32 {
let mut cur = &buf[AUTOCOMPOUND_START..];
if let Ok(ac) = AutoCompound::deserialize(&mut cur) {
if plausible(&ac) {
return Ok((ac, AUTOCOMPOUND_START));
}
}
}
let end = buf.len().saturating_sub(32);
for start in (0..=end).step_by(8) {
let mut cur = &buf[start..];
if let Ok(ac) = AutoCompound::deserialize(&mut cur) {
if plausible(&ac) {
return Ok((ac, start));
}
}
}
Err(ReflectErrorCodes::DeserializationError.into())
}
#[cfg(test)]
mod tests_no_strategy {
use super::*;
use std::fs;
use std::path::PathBuf;
use anchor_lang::prelude::AnchorDeserialize;
const LOCAL_CONTROLLER: &str = "usdc_controller_account.bin";
fn get_test_assets_dir() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("./test_assets/local/");
path
}
fn log_ac(label: &str, ac: &AutoCompound, start: usize, data: &[u8]) {
println!("\n--- {} ---", label);
println!("AutoCompound @ offset : {}", start);
println!("deposited_vault_value : {}", ac.deposited_vault_value);
println!("net_user_flow_since_capture: {}", ac.net_user_flow_since_capture);
println!("last_pool_value : {}", ac.last_pool_value);
println!("queued_recipient_shares : {}", ac.queued_recipient_shares);
println!("V (USDC) : {:.6}", ac.deposited_vault_value as f64 / 1_000_000.0);
println!("baseline (USDC) : {:.6}", ac.last_pool_value as f64 / 1_000_000.0);
let end = (start + 32).min(data.len());
print!("raw bytes [{}..{}]:", start, end);
for b in &data[start..end] {
print!(" {:02x}", b);
}
println!();
}
fn try_decode_at(data: &[u8], start: usize) -> Option<AutoCompound> {
if data.len() < start + 32 {
return None;
}
let mut cur = &data[start..];
AnchorDeserialize::deserialize(&mut cur).ok()
}
#[test]
fn debug_autocompound_no_strategy() {
let assets_dir = get_test_assets_dir();
let data = fs::read(assets_dir.join(LOCAL_CONTROLLER))
.expect(&format!("Missing: {}", LOCAL_CONTROLLER));
println!("\n=== AutoCompound (no-Strategy) decode ===");
println!("buffer len : {}", data.len());
println!("AUTOCOMPOUND_START : {}", AUTOCOMPOUND_START);
assert!(data.len() >= 8, "account too short for discriminator");
let disc = &data[..8];
print!("discriminator bytes :");
for b in disc { print!(" {:02x}", b); }
println!();
if let Some(ac) = try_decode_at(&data, 8) {
log_ac("decode @8 (after discriminator)", &ac, 8, &data);
} else {
println!("\n--- decode @8 failed ---");
}
if let Some(ac) = try_decode_at(&data, AUTOCOMPOUND_START) {
log_ac("decode @AUTOCOMPOUND_START", &ac, AUTOCOMPOUND_START, &data);
} else {
println!("\n--- decode @AUTOCOMPOUND_START failed ---");
}
let mut found = None;
for start in (0..data.len().saturating_sub(32)).step_by(8) {
if let Some(ac) = try_decode_at(&data, start) {
if !(ac.deposited_vault_value == 0
&& ac.last_pool_value == 0
&& ac.net_user_flow_since_capture == 0
&& ac.queued_recipient_shares == 0)
{
log_ac("decode @scan (plausible)", &ac, start, &data);
found = Some(start);
break;
}
}
}
if found.is_none() {
println!("\n--- scan failed to find a plausible AutoCompound ---");
}
}
}
#[test]
fn ddd() {
use std::fs;
use std::path::PathBuf;
const LOCAL_CONTROLLER: &str = "usdc_controller_account.bin";
fn assets_dir() -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("./test_assets/local/");
p
}
let data = fs::read(assets_dir().join(LOCAL_CONTROLLER))
.expect(&format!("Missing: {}", LOCAL_CONTROLLER));
println!("\n=== AutoCompound decode ===");
println!("buffer len: {}", data.len());
println!("AUTOCOMPOUND_START: {}", AUTOCOMPOUND_START);
let (ac, start) = deserialise_autocompound_dynamic(&data)
.expect("failed to locate/deserialize AutoCompound");
println!("AutoCompound @ offset : {}", start);
println!("deposited_vault_value : {}", ac.deposited_vault_value);
println!("net_user_flow_since_capture: {}", ac.net_user_flow_since_capture);
println!("last_pool_value : {}", ac.last_pool_value);
println!("queued_recipient_shares : {}", ac.queued_recipient_shares);
println!("V (USDC) : {:.6}", ac.deposited_vault_value as f64 / 1_000_000.0);
println!("baseline (USDC) : {:.6}", ac.last_pool_value as f64 / 1_000_000.0);
let end = (start + 32).min(data.len());
print!("raw bytes [{}..{}]:", start, end);
for b in &data[start..end] { print!(" {:02x}", b); }
println!();
}
}
#[cfg(test)]
mod mainnet_log_autocompound {
use std::fs;
use std::path::PathBuf;
use crate::reflect::{deserialise_autocompound, AUTOCOMPOUND_START};
const CONTROLLER_BIN: &str = "usdc_controller_account.bin";
fn find_assets_dir() -> PathBuf {
let base = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mainnet = base.join("test_assets/mainnet");
if mainnet.join(CONTROLLER_BIN).exists() {
return mainnet;
}
let local = base.join("test_assets/local");
assert!(
local.join(CONTROLLER_BIN).exists(),
"Could not find {} in test_assets/mainnet or test_assets/local",
CONTROLLER_BIN
);
local
}
fn dump_hex(label: &str, bytes: &[u8]) {
print!("{label}:");
for b in bytes { print!(" {:02x}", b); }
println!();
}
#[test]
fn mainnet_log_autocompound() {
let dir = find_assets_dir();
let path = dir.join(CONTROLLER_BIN);
let data = fs::read(&path).expect("failed to read controller dump");
println!("Using assets dir: {}", dir.display());
println!("buffer len : {}", data.len());
println!("AUTOCOMPOUND_START : {}", AUTOCOMPOUND_START);
let s = AUTOCOMPOUND_START;
let before = s.saturating_sub(16);
let end32 = (s + 32).min(data.len());
let end48 = (s + 48).min(data.len());
dump_hex("bytes [start-16 .. start)", &data[before..s]);
dump_hex("bytes [start .. start+32]", &data[s..end32]);
dump_hex("bytes [start+32 .. +48] ", &data[end32..end48]);
let ac = deserialise_autocompound(&data).expect("deserialize AutoCompound");
println!("deposited_vault_value : {}", ac.deposited_vault_value);
println!("net_user_flow_since_capture: {}", ac.net_user_flow_since_capture);
println!("last_pool_value : {}", ac.last_pool_value);
println!("queued_recipient_shares : {}", ac.queued_recipient_shares);
println!("V (USDC) : {:.6}", ac.deposited_vault_value as f64 / 1_000_000.0);
println!("baseline (USDC) : {:.6}", ac.last_pool_value as f64 / 1_000_000.0);
}
}