167 lines
7.5 KiB
Rust
167 lines
7.5 KiB
Rust
//! acc_redemption - NAC 原生协议实现
|
|
//! 从 acc_remaining_protocols.rs 提取
|
|
use crate::primitives::{Address, Hash, Timestamp};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum ACCRedemptionError {
|
|
RedemptionNotFound(Hash),
|
|
InsufficientRedemptionFund { available: u128, requested: u128 },
|
|
RedemptionWindowClosed,
|
|
InvalidConstitutionalReceipt,
|
|
Unauthorized(Address),
|
|
RedemptionAlreadyProcessed(Hash),
|
|
}
|
|
impl std::fmt::Display for ACCRedemptionError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::RedemptionNotFound(h) => write!(f, "赎回请求不存在: {}", h.to_hex()),
|
|
Self::InsufficientRedemptionFund { available, requested } => write!(f, "赎回资金不足: 可用 {},请求 {}", available, requested),
|
|
Self::RedemptionWindowClosed => write!(f, "赎回窗口已关闭"),
|
|
Self::InvalidConstitutionalReceipt => write!(f, "宪法收据无效"),
|
|
Self::Unauthorized(a) => write!(f, "未授权: {}", a.to_hex()),
|
|
Self::RedemptionAlreadyProcessed(h) => write!(f, "赎回请求已处理: {}", h.to_hex()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum RedemptionStatus { Pending, Processing, Completed, Rejected, Cancelled }
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct RedemptionRequest {
|
|
pub redemption_id: Hash,
|
|
pub asset_id: Hash,
|
|
pub redeemer: Address,
|
|
pub amount: u128,
|
|
pub redemption_price_xtzh: u128,
|
|
pub total_redemption_xtzh: u128,
|
|
pub status: RedemptionStatus,
|
|
pub requested_at: Timestamp,
|
|
pub processed_at: Option<Timestamp>,
|
|
pub constitutional_receipt: Hash,
|
|
pub rejection_reason: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum RedemptionProtocolEvent {
|
|
RedemptionRequested { redemption_id: Hash, asset_id: Hash, redeemer: Address, amount: u128, timestamp: Timestamp },
|
|
RedemptionCompleted { redemption_id: Hash, total_xtzh: u128, timestamp: Timestamp },
|
|
RedemptionRejected { redemption_id: Hash, reason: String, timestamp: Timestamp },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ACCRedemptionProtocol {
|
|
pub protocol_uid: String,
|
|
pub lens_protocol_vector: String,
|
|
pub requests: HashMap<Hash, RedemptionRequest>,
|
|
pub redemption_fund: HashMap<Hash, u128>,
|
|
pub redemption_window_open: bool,
|
|
pub pending_events: Vec<RedemptionProtocolEvent>,
|
|
pub created_at: Timestamp,
|
|
pub updated_at: Timestamp,
|
|
}
|
|
impl ACCRedemptionProtocol {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
protocol_uid: "nac.acc.ACCRedemptionProtocol.v1".to_string(),
|
|
lens_protocol_vector: "ACC-Redemption".to_string(),
|
|
requests: HashMap::new(), redemption_fund: HashMap::new(),
|
|
redemption_window_open: true, pending_events: Vec::new(),
|
|
created_at: Timestamp::now(), updated_at: Timestamp::now(),
|
|
}
|
|
}
|
|
pub fn fund_redemption_pool(&mut self, asset_id: Hash, amount_xtzh: u128, constitutional_receipt: Hash) -> Result<(), ACCRedemptionError> {
|
|
if constitutional_receipt.is_zero() { return Err(ACCRedemptionError::InvalidConstitutionalReceipt); }
|
|
*self.redemption_fund.entry(asset_id).or_insert(0) += amount_xtzh;
|
|
Ok(())
|
|
}
|
|
pub fn request_redemption(
|
|
&mut self, asset_id: Hash, redeemer: Address, amount: u128,
|
|
redemption_price_xtzh: u128, constitutional_receipt: Hash, timestamp: Timestamp,
|
|
) -> Result<Hash, ACCRedemptionError> {
|
|
if !self.redemption_window_open { return Err(ACCRedemptionError::RedemptionWindowClosed); }
|
|
let total = amount.saturating_mul(redemption_price_xtzh);
|
|
let available = self.redemption_fund.get(&asset_id).copied().unwrap_or(0);
|
|
if available < total { return Err(ACCRedemptionError::InsufficientRedemptionFund { available, requested: total }); }
|
|
let mut data = Vec::new();
|
|
data.extend_from_slice(asset_id.as_bytes());
|
|
data.extend_from_slice(redeemer.as_bytes());
|
|
data.extend_from_slice(×tamp.as_secs().to_be_bytes());
|
|
let redemption_id = Hash::sha3_384(&data);
|
|
let request = RedemptionRequest {
|
|
redemption_id, asset_id, redeemer: redeemer.clone(), amount,
|
|
redemption_price_xtzh, total_redemption_xtzh: total,
|
|
status: RedemptionStatus::Pending,
|
|
requested_at: timestamp.clone(), processed_at: None,
|
|
constitutional_receipt, rejection_reason: None,
|
|
};
|
|
self.requests.insert(redemption_id, request);
|
|
self.pending_events.push(RedemptionProtocolEvent::RedemptionRequested { redemption_id, asset_id, redeemer, amount, timestamp });
|
|
self.updated_at = Timestamp::now();
|
|
Ok(redemption_id)
|
|
}
|
|
pub fn complete_redemption(&mut self, redemption_id: Hash, constitutional_receipt: Hash, timestamp: Timestamp) -> Result<u128, ACCRedemptionError> {
|
|
if constitutional_receipt.is_zero() { return Err(ACCRedemptionError::InvalidConstitutionalReceipt); }
|
|
let request = self.requests.get_mut(&redemption_id).ok_or(ACCRedemptionError::RedemptionNotFound(redemption_id))?;
|
|
if request.status != RedemptionStatus::Pending { return Err(ACCRedemptionError::RedemptionAlreadyProcessed(redemption_id)); }
|
|
let total = request.total_redemption_xtzh;
|
|
let asset_id = request.asset_id;
|
|
if let Some(fund) = self.redemption_fund.get_mut(&asset_id) { *fund = fund.saturating_sub(total); }
|
|
request.status = RedemptionStatus::Completed;
|
|
request.processed_at = Some(timestamp.clone());
|
|
self.pending_events.push(RedemptionProtocolEvent::RedemptionCompleted { redemption_id, total_xtzh: total, timestamp });
|
|
Ok(total)
|
|
}
|
|
pub fn get_request(&self, id: &Hash) -> Option<&RedemptionRequest> { self.requests.get(id) }
|
|
pub fn drain_pending_events(&mut self) -> Vec<RedemptionProtocolEvent> { std::mem::take(&mut self.pending_events) }
|
|
}
|
|
|
|
impl ACCRedemptionProtocol {
|
|
/// 取消赎回申请
|
|
pub fn cancel_redemption(
|
|
&mut self,
|
|
redemption_id: &Hash,
|
|
requester: &Address,
|
|
constitutional_receipt: Hash,
|
|
timestamp: Timestamp,
|
|
) -> Result<(), ACCRedemptionError> {
|
|
if constitutional_receipt.is_zero() {
|
|
return Err(ACCRedemptionError::InvalidConstitutionalReceipt);
|
|
}
|
|
let req = self.requests.get_mut(redemption_id)
|
|
.ok_or(ACCRedemptionError::RedemptionNotFound(redemption_id.clone()))?;
|
|
if &req.redeemer != requester {
|
|
return Err(ACCRedemptionError::Unauthorized(requester.clone()));
|
|
}
|
|
req.status = RedemptionStatus::Cancelled;
|
|
req.processed_at = Some(timestamp);
|
|
Ok(())
|
|
}
|
|
|
|
/// 获取待处理申请列表
|
|
pub fn get_pending_requests(&self) -> Vec<&RedemptionRequest> {
|
|
self.requests.values()
|
|
.filter(|r| r.status == RedemptionStatus::Pending)
|
|
.collect()
|
|
}
|
|
|
|
/// 获取赎回池余额
|
|
pub fn get_pool_balance(&self, asset_id: &Hash) -> u128 {
|
|
self.redemption_fund.get(asset_id).copied().unwrap_or(0)
|
|
}
|
|
|
|
/// 获取所有申请(按资产)
|
|
pub fn get_requests_by_asset(&self, asset_id: &Hash) -> Vec<&RedemptionRequest> {
|
|
self.requests.values()
|
|
.filter(|r| &r.asset_id == asset_id)
|
|
.collect()
|
|
}
|
|
|
|
/// 获取申请总数
|
|
pub fn total_requests(&self) -> usize {
|
|
self.requests.len()
|
|
}
|
|
}
|