use alloy::primitives::Address;
use super::chain::OnChainChannel;
use super::receipt::now_iso8601;
use super::ChannelState;
use crate::protocol::methods::tempo::session::SettlementRoute;
use crate::protocol::traits::VerificationError;
pub(super) struct Opening {
pub(super) channel_id: String,
pub(super) chain_id: u64,
pub(super) escrow_contract: Address,
pub(super) authorized_signer: Address,
pub(super) settlement_route: Option<SettlementRoute>,
pub(super) cumulative_amount: u128,
pub(super) signature: Vec<u8>,
}
impl ChannelState {
pub(super) fn open(
existing: Option<Self>,
on_chain: &OnChainChannel,
opening: Opening,
) -> Self {
let Opening {
channel_id,
chain_id,
escrow_contract,
authorized_signer,
settlement_route,
cumulative_amount,
signature,
} = opening;
if let Some(existing) = existing {
let deposit = std::cmp::max(on_chain.deposit, existing.deposit);
let settled_on_chain = std::cmp::max(on_chain.settled, existing.settled_on_chain);
let spent = std::cmp::max(settled_on_chain, existing.spent);
if cumulative_amount > existing.highest_voucher_amount {
Self {
settlement_route: existing.settlement_route.or(settlement_route),
deposit,
settled_on_chain,
spent,
highest_voucher_amount: cumulative_amount,
highest_voucher_signature: Some(signature),
authorized_signer,
close_requested_at: on_chain.close_requested_at,
..existing
}
} else {
Self {
deposit,
settled_on_chain,
spent,
authorized_signer,
close_requested_at: on_chain.close_requested_at,
..existing
}
}
} else {
Self {
channel_id,
chain_id,
escrow_contract,
payer: on_chain.payer,
payee: on_chain.payee,
token: on_chain.token,
settlement_route,
authorized_signer,
deposit: on_chain.deposit,
settled_on_chain: on_chain.settled,
highest_voucher_amount: cumulative_amount,
highest_voucher_signature: Some(signature),
spent: on_chain.settled,
units: 0,
finalized: false,
closing: false,
close_requested_at: on_chain.close_requested_at,
created_at: now_iso8601(),
}
}
}
pub(super) fn refresh_on_chain(self, on_chain: &OnChainChannel) -> Self {
let settled_on_chain = std::cmp::max(on_chain.settled, self.settled_on_chain);
let spent = std::cmp::max(settled_on_chain, self.spent);
Self {
deposit: std::cmp::max(on_chain.deposit, self.deposit),
settled_on_chain,
spent,
close_requested_at: on_chain.close_requested_at,
..self
}
}
pub(super) fn accept_voucher(
self,
cumulative_amount: u128,
signature: Vec<u8>,
min_delta: u128,
) -> Result<Self, VerificationError> {
if cumulative_amount <= self.highest_voucher_amount {
return Err(VerificationError::delta_too_small(
"voucher does not add new funds",
));
}
let delta = cumulative_amount - self.highest_voucher_amount;
if delta < min_delta {
return Err(VerificationError::delta_too_small(format!(
"voucher delta {} below minimum {}",
delta, min_delta
)));
}
Ok(Self {
highest_voucher_amount: cumulative_amount,
highest_voucher_signature: Some(signature),
..self
})
}
pub(super) fn deduct(self, amount: u128) -> Result<Self, VerificationError> {
if self.finalized {
return Err(VerificationError::channel_closed("channel is finalized"));
}
if self.closing {
return Err(VerificationError::channel_closed("channel is closing"));
}
let available = self.highest_voucher_amount.saturating_sub(self.spent);
if available >= amount {
Ok(Self {
spent: self.spent + amount,
units: self.units + 1,
..self
})
} else {
Err(VerificationError::insufficient_balance(format!(
"requested {}, available {}",
amount, available
)))
}
}
pub(super) fn mark_pending_close(
self,
cumulative_amount: u128,
) -> Result<Self, VerificationError> {
if self.finalized {
return Err(VerificationError::channel_closed("channel is finalized"));
}
if self.closing {
return Err(VerificationError::channel_closed("channel is closing"));
}
if cumulative_amount < self.spent {
return Err(VerificationError::new(format!(
"close voucher amount must be >= {} (spent)",
self.spent,
)));
}
Ok(Self {
closing: true,
..self
})
}
pub(super) fn clear_pending_close(self) -> Self {
if self.finalized {
return self;
}
Self {
closing: false,
..self
}
}
pub(super) fn finalize_close(
self,
cumulative_amount: u128,
signature: Vec<u8>,
on_chain_deposit: u128,
) -> Self {
let update_voucher = cumulative_amount > self.highest_voucher_amount;
Self {
deposit: on_chain_deposit,
highest_voucher_amount: if update_voucher {
cumulative_amount
} else {
self.highest_voucher_amount
},
highest_voucher_signature: if update_voucher {
Some(signature)
} else {
self.highest_voucher_signature
},
finalized: true,
closing: false,
..self
}
}
}