use std::collections::HashMap;
use std::sync::Mutex;
use alloy::primitives::{Address, U256};
use crate::deployment::Deployment;
use crate::lane::{LaneKey, PoolId};
use crate::store::StoreError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct BuyerLaneProgress {
pub last_amount: U256,
pub last_bytes: U256,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuyerPoolState {
pub pool_id: PoolId,
pub deployment: Deployment,
pub owner: Address,
pub token: Address,
pub deposit: U256,
lanes: HashMap<LaneKey, BuyerLaneProgress>,
redeemed_elsewhere: U256,
}
impl BuyerPoolState {
#[must_use]
pub fn new(
pool_id: PoolId,
deployment: Deployment,
owner: Address,
token: Address,
deposit: U256,
) -> Self {
Self {
pool_id,
deployment,
owner,
token,
deposit,
lanes: HashMap::new(),
redeemed_elsewhere: U256::ZERO,
}
}
#[must_use]
pub fn adopt(
pool_id: PoolId,
deployment: Deployment,
owner: Address,
token: Address,
deposit: U256,
total_redeemed: U256,
) -> Self {
Self {
redeemed_elsewhere: total_redeemed,
..Self::new(pool_id, deployment, owner, token, deposit)
}
}
#[must_use]
pub const fn redeemed_elsewhere(&self) -> U256 {
self.redeemed_elsewhere
}
#[must_use]
pub fn pool_spend(&self) -> U256 {
self.committed_amount()
.saturating_add(self.redeemed_elsewhere)
}
pub fn seed_lane(
&mut self,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<(), BuyerProgressError> {
let new = !self.lanes.contains_key(&lane);
self.advance_lane(lane, bytes, amount)?;
if new {
self.redeemed_elsewhere = self.redeemed_elsewhere.saturating_sub(amount);
}
Ok(())
}
#[must_use]
pub fn is_on(&self, deployment: Deployment) -> bool {
self.deployment == deployment
}
#[must_use]
pub fn hydrate(
pool_id: PoolId,
deployment: Deployment,
owner: Address,
token: Address,
deposit: U256,
lanes: Vec<(LaneKey, BuyerLaneProgress)>,
redeemed_elsewhere: U256,
) -> Self {
Self {
pool_id,
deployment,
owner,
token,
deposit,
lanes: lanes.into_iter().collect(),
redeemed_elsewhere,
}
}
#[must_use]
pub fn lane_progress(&self, lane: LaneKey) -> Option<BuyerLaneProgress> {
self.lanes.get(&lane).copied()
}
pub fn lanes(&self) -> impl Iterator<Item = (LaneKey, BuyerLaneProgress)> + '_ {
self.lanes.iter().map(|(k, v)| (*k, *v))
}
#[must_use]
pub fn committed_amount(&self) -> U256 {
self.lanes
.values()
.fold(U256::ZERO, |acc, p| acc.saturating_add(p.last_amount))
}
#[must_use]
pub fn lane_count(&self) -> usize {
self.lanes.len()
}
pub fn rebase_lane(
&mut self,
lane: LaneKey,
anchor: BuyerLaneProgress,
totals: BuyerLaneProgress,
) {
let advanced =
totals.last_amount >= anchor.last_amount && totals.last_bytes >= anchor.last_bytes;
self.lanes
.insert(lane, if advanced { totals } else { anchor });
}
pub fn advance_lane(
&mut self,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<(), BuyerProgressError> {
if let Some(existing) = self.lanes.get(&lane) {
if bytes < existing.last_bytes {
return Err(BuyerProgressError::Regressed {
field: "bytes",
recorded: existing.last_bytes,
got: bytes,
});
}
if amount < existing.last_amount {
return Err(BuyerProgressError::Regressed {
field: "amount",
recorded: existing.last_amount,
got: amount,
});
}
}
self.lanes.insert(
lane,
BuyerLaneProgress {
last_amount: amount,
last_bytes: bytes,
},
);
Ok(())
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum BuyerProgressError {
#[error("buyer lane {field} regressed: recorded {recorded}, got {got}")]
Regressed {
field: &'static str,
recorded: U256,
got: U256,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BuyerLoad {
pub pools: Vec<BuyerPoolState>,
pub skipped: Vec<PoolId>,
}
#[derive(Debug, PartialEq, Eq)]
#[must_use]
pub enum AdvanceOutcome {
Advanced,
UnknownPool,
PoolMismatch,
Regressed(BuyerProgressError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub enum DepositOutcome {
Added(U256),
UnknownPool,
PoolMismatch,
}
pub trait BuyerPoolStore: Send + Sync {
fn load_all(&self) -> Result<BuyerLoad, StoreError>;
fn record(&self, state: &BuyerPoolState) -> Result<(), StoreError>;
fn forget(&self, owner: Address) -> Result<(), StoreError>;
fn get_by_pool_id(&self, pool_id: PoolId) -> Result<Option<BuyerPoolState>, StoreError>;
fn forget_if_pool(&self, owner: Address, pool_id: PoolId) -> Result<bool, StoreError>;
fn get_by_owner(&self, owner: Address) -> Result<Option<BuyerPoolState>, StoreError>;
fn advance_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<AdvanceOutcome, StoreError>;
fn rebase_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
anchor: BuyerLaneProgress,
totals: BuyerLaneProgress,
) -> Result<AdvanceOutcome, StoreError>;
fn seed_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<AdvanceOutcome, StoreError>;
fn add_deposit(
&self,
owner: Address,
pool_id: PoolId,
additional: U256,
) -> Result<DepositOutcome, StoreError>;
}
#[derive(Debug, Default)]
struct MemoryInner {
pools: HashMap<PoolId, BuyerPoolState>,
owner_index: HashMap<Address, PoolId>,
}
#[derive(Debug, Default)]
pub struct MemoryBuyerPoolStore {
inner: Mutex<MemoryInner>,
}
impl MemoryBuyerPoolStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.lock().map_or(0, |m| m.pools.len())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl BuyerPoolStore for MemoryBuyerPoolStore {
fn load_all(&self) -> Result<BuyerLoad, StoreError> {
let guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
Ok(BuyerLoad {
pools: guard.pools.values().cloned().collect(),
skipped: Vec::new(),
})
}
fn record(&self, state: &BuyerPoolState) -> Result<(), StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
guard.owner_index.insert(state.owner, state.pool_id);
guard.pools.insert(state.pool_id, state.clone());
Ok(())
}
fn forget(&self, owner: Address) -> Result<(), StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
if let Some(pool_id) = guard.owner_index.remove(&owner) {
guard.pools.remove(&pool_id);
}
Ok(())
}
fn forget_if_pool(&self, owner: Address, pool_id: PoolId) -> Result<bool, StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
if guard.owner_index.get(&owner) == Some(&pool_id) {
guard.owner_index.remove(&owner);
guard.pools.remove(&pool_id);
return Ok(true);
}
Ok(false)
}
fn get_by_pool_id(&self, pool_id: PoolId) -> Result<Option<BuyerPoolState>, StoreError> {
let guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
Ok(guard.pools.get(&pool_id).cloned())
}
fn get_by_owner(&self, owner: Address) -> Result<Option<BuyerPoolState>, StoreError> {
let guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
Ok(guard
.owner_index
.get(&owner)
.and_then(|pool_id| guard.pools.get(pool_id))
.cloned())
}
fn advance_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<AdvanceOutcome, StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
let Some(mapped) = guard.owner_index.get(&owner).copied() else {
return Ok(AdvanceOutcome::UnknownPool);
};
if mapped != pool_id {
return Ok(AdvanceOutcome::PoolMismatch);
}
let Some(state) = guard.pools.get_mut(&pool_id) else {
return Ok(AdvanceOutcome::UnknownPool);
};
match state.advance_lane(lane, bytes, amount) {
Ok(()) => Ok(AdvanceOutcome::Advanced),
Err(err) => Ok(AdvanceOutcome::Regressed(err)),
}
}
fn rebase_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
anchor: BuyerLaneProgress,
totals: BuyerLaneProgress,
) -> Result<AdvanceOutcome, StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
let Some(mapped) = guard.owner_index.get(&owner).copied() else {
return Ok(AdvanceOutcome::UnknownPool);
};
if mapped != pool_id {
return Ok(AdvanceOutcome::PoolMismatch);
}
let Some(state) = guard.pools.get_mut(&pool_id) else {
return Ok(AdvanceOutcome::UnknownPool);
};
state.rebase_lane(lane, anchor, totals);
Ok(AdvanceOutcome::Advanced)
}
fn seed_progress(
&self,
owner: Address,
pool_id: PoolId,
lane: LaneKey,
bytes: U256,
amount: U256,
) -> Result<AdvanceOutcome, StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
let Some(mapped) = guard.owner_index.get(&owner).copied() else {
return Ok(AdvanceOutcome::UnknownPool);
};
if mapped != pool_id {
return Ok(AdvanceOutcome::PoolMismatch);
}
let Some(state) = guard.pools.get_mut(&pool_id) else {
return Ok(AdvanceOutcome::UnknownPool);
};
match state.seed_lane(lane, bytes, amount) {
Ok(()) => Ok(AdvanceOutcome::Advanced),
Err(err) => Ok(AdvanceOutcome::Regressed(err)),
}
}
fn add_deposit(
&self,
owner: Address,
pool_id: PoolId,
additional: U256,
) -> Result<DepositOutcome, StoreError> {
let mut guard = self
.inner
.lock()
.map_err(|err| StoreError::Backend(format!("memory store mutex poisoned: {err}")))?;
let Some(mapped) = guard.owner_index.get(&owner).copied() else {
return Ok(DepositOutcome::UnknownPool);
};
if mapped != pool_id {
return Ok(DepositOutcome::PoolMismatch);
}
let Some(state) = guard.pools.get_mut(&pool_id) else {
return Ok(DepositOutcome::UnknownPool);
};
state.deposit = state.deposit.saturating_add(additional);
Ok(DepositOutcome::Added(state.deposit))
}
}
#[cfg(test)]
mod tests;