579 lines
17 KiB
Rust
579 lines
17 KiB
Rust
//! CBP质押赎回机制
|
||
//!
|
||
//! 实现CBP节点的质押赎回条件、流程、计算和记录功能
|
||
|
||
use crate::{CbpNode, CbpStatus, CbppL1Error};
|
||
use nac_udm::primitives::Address;
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 赎回状态
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum RedemptionStatus {
|
||
/// 待处理
|
||
Pending,
|
||
/// 处理中
|
||
Processing,
|
||
/// 已完成
|
||
Completed,
|
||
/// 已取消
|
||
Cancelled,
|
||
/// 失败
|
||
Failed,
|
||
}
|
||
|
||
/// 赎回类型
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum RedemptionType {
|
||
/// 全额赎回
|
||
Full,
|
||
/// 部分赎回
|
||
Partial,
|
||
/// 紧急赎回
|
||
Emergency,
|
||
}
|
||
|
||
/// 赎回请求
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionRequest {
|
||
/// CBP地址
|
||
pub address: Address,
|
||
/// 赎回类型
|
||
pub redemption_type: RedemptionType,
|
||
/// 请求赎回金额
|
||
pub requested_amount: u64,
|
||
/// 实际赎回金额
|
||
pub actual_amount: Option<u64>,
|
||
/// 处罚金额
|
||
pub penalty_amount: Option<u64>,
|
||
/// 请求时间
|
||
pub requested_at: u64,
|
||
/// 赎回状态
|
||
pub status: RedemptionStatus,
|
||
/// 完成时间
|
||
pub completed_at: Option<u64>,
|
||
/// 失败原因
|
||
pub failure_reason: Option<String>,
|
||
}
|
||
|
||
impl RedemptionRequest {
|
||
pub fn new(
|
||
address: Address,
|
||
redemption_type: RedemptionType,
|
||
requested_amount: u64,
|
||
timestamp: u64,
|
||
) -> Self {
|
||
Self {
|
||
address,
|
||
redemption_type,
|
||
requested_amount,
|
||
actual_amount: None,
|
||
penalty_amount: None,
|
||
requested_at: timestamp,
|
||
status: RedemptionStatus::Pending,
|
||
completed_at: None,
|
||
failure_reason: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 赎回记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionRecord {
|
||
/// CBP地址
|
||
pub address: Address,
|
||
/// 赎回类型
|
||
pub redemption_type: RedemptionType,
|
||
/// 原始质押金额
|
||
pub original_stake: u64,
|
||
/// 赎回金额
|
||
pub redeemed_amount: u64,
|
||
/// 处罚金额
|
||
pub penalty_amount: u64,
|
||
/// 请求时间
|
||
pub requested_at: u64,
|
||
/// 完成时间
|
||
pub completed_at: u64,
|
||
/// 声誉分数
|
||
pub reputation_at_redemption: f64,
|
||
}
|
||
|
||
/// 赎回条件
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionConditions {
|
||
/// 最小锁定期(秒)
|
||
pub min_lock_period: u64,
|
||
/// 部分赎回最小保留金额
|
||
pub min_remaining_stake: u64,
|
||
/// 紧急赎回处罚率(百分比)
|
||
pub emergency_penalty_rate: u8,
|
||
/// 声誉处罚阈值
|
||
pub reputation_penalty_threshold: f64,
|
||
/// 低声誉处罚率(百分比)
|
||
pub low_reputation_penalty_rate: u8,
|
||
}
|
||
|
||
impl Default for RedemptionConditions {
|
||
fn default() -> Self {
|
||
Self {
|
||
min_lock_period: 30 * 24 * 3600, // 30天
|
||
min_remaining_stake: 50_000_000_000, // 50 XIC
|
||
emergency_penalty_rate: 20, // 20%
|
||
reputation_penalty_threshold: 0.5,
|
||
low_reputation_penalty_rate: 10, // 10%
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 赎回管理器
|
||
pub struct RedemptionManager {
|
||
/// 赎回请求
|
||
requests: HashMap<Address, RedemptionRequest>,
|
||
/// 赎回记录
|
||
records: Vec<RedemptionRecord>,
|
||
/// 赎回条件
|
||
conditions: RedemptionConditions,
|
||
}
|
||
|
||
impl RedemptionManager {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
requests: HashMap::new(),
|
||
records: Vec::new(),
|
||
conditions: RedemptionConditions::default(),
|
||
}
|
||
}
|
||
|
||
pub fn with_conditions(conditions: RedemptionConditions) -> Self {
|
||
Self {
|
||
requests: HashMap::new(),
|
||
records: Vec::new(),
|
||
conditions,
|
||
}
|
||
}
|
||
|
||
/// 提交赎回请求
|
||
pub fn submit_redemption_request(
|
||
&mut self,
|
||
node: &CbpNode,
|
||
redemption_type: RedemptionType,
|
||
requested_amount: u64,
|
||
timestamp: u64,
|
||
) -> Result<(), CbppL1Error> {
|
||
// 检查节点状态
|
||
if node.status == CbpStatus::Exited {
|
||
return Err(CbppL1Error::InvalidStatus("Node already exited".to_string()));
|
||
}
|
||
|
||
// 检查是否已有待处理的请求
|
||
if let Some(existing) = self.requests.get(&node.address) {
|
||
if existing.status == RedemptionStatus::Pending
|
||
|| existing.status == RedemptionStatus::Processing {
|
||
return Err(CbppL1Error::RedemptionRequestExists);
|
||
}
|
||
}
|
||
|
||
// 检查锁定期
|
||
if timestamp < node.registered_at + self.conditions.min_lock_period {
|
||
return Err(CbppL1Error::LockPeriodNotMet);
|
||
}
|
||
|
||
// 检查赎回金额
|
||
if requested_amount > node.stake_amount {
|
||
return Err(CbppL1Error::InvalidRedemptionAmount);
|
||
}
|
||
|
||
// 检查部分赎回的最小保留金额
|
||
if redemption_type == RedemptionType::Partial {
|
||
let remaining = node.stake_amount - requested_amount;
|
||
if remaining < self.conditions.min_remaining_stake {
|
||
return Err(CbppL1Error::InsufficientRemainingStake);
|
||
}
|
||
}
|
||
|
||
// 创建赎回请求
|
||
let request = RedemptionRequest::new(
|
||
node.address,
|
||
redemption_type,
|
||
requested_amount,
|
||
timestamp,
|
||
);
|
||
self.requests.insert(node.address, request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 处理赎回请求
|
||
pub fn process_redemption(
|
||
&mut self,
|
||
address: &Address,
|
||
node: &CbpNode,
|
||
timestamp: u64,
|
||
) -> Result<(u64, u64), CbppL1Error> {
|
||
// 检查请求状态并保存信息
|
||
let (redemption_type, requested_amount, requested_at) = {
|
||
let request = self.requests.get_mut(address)
|
||
.ok_or(CbppL1Error::RedemptionRequestNotFound)?;
|
||
|
||
// 检查请求状态
|
||
if request.status != RedemptionStatus::Pending {
|
||
return Err(CbppL1Error::InvalidStatus(
|
||
format!("Request status is {:?}, expected Pending", request.status)
|
||
));
|
||
}
|
||
|
||
// 更新状态为处理中
|
||
request.status = RedemptionStatus::Processing;
|
||
|
||
(request.redemption_type, request.requested_amount, request.requested_at)
|
||
};
|
||
|
||
// 计算赎回金额和处罚
|
||
let (actual_amount, penalty) = self.calculate_redemption(
|
||
redemption_type,
|
||
requested_amount,
|
||
node.reputation,
|
||
);
|
||
|
||
// 更新请求
|
||
let request = self.requests.get_mut(address).unwrap();
|
||
request.actual_amount = Some(actual_amount);
|
||
request.penalty_amount = Some(penalty);
|
||
request.status = RedemptionStatus::Completed;
|
||
request.completed_at = Some(timestamp);
|
||
|
||
// 创建赎回记录
|
||
let record = RedemptionRecord {
|
||
address: *address,
|
||
redemption_type,
|
||
original_stake: node.stake_amount,
|
||
redeemed_amount: actual_amount,
|
||
penalty_amount: penalty,
|
||
requested_at,
|
||
completed_at: timestamp,
|
||
reputation_at_redemption: node.reputation,
|
||
};
|
||
self.records.push(record);
|
||
|
||
Ok((actual_amount, penalty))
|
||
}
|
||
|
||
/// 计算赎回金额
|
||
fn calculate_redemption(
|
||
&self,
|
||
redemption_type: RedemptionType,
|
||
requested_amount: u64,
|
||
reputation: f64,
|
||
) -> (u64, u64) {
|
||
let mut penalty = 0u64;
|
||
|
||
// 紧急赎回处罚
|
||
if redemption_type == RedemptionType::Emergency {
|
||
penalty += (requested_amount as f64 * self.conditions.emergency_penalty_rate as f64 / 100.0) as u64;
|
||
}
|
||
|
||
// 低声誉处罚
|
||
if reputation < self.conditions.reputation_penalty_threshold {
|
||
penalty += (requested_amount as f64 * self.conditions.low_reputation_penalty_rate as f64 / 100.0) as u64;
|
||
}
|
||
|
||
let actual_amount = requested_amount.saturating_sub(penalty);
|
||
(actual_amount, penalty)
|
||
}
|
||
|
||
/// 取消赎回请求
|
||
pub fn cancel_redemption_request(&mut self, address: &Address) -> Result<(), CbppL1Error> {
|
||
let request = self.requests.get_mut(address)
|
||
.ok_or(CbppL1Error::RedemptionRequestNotFound)?;
|
||
|
||
// 只能取消待处理的请求
|
||
if request.status != RedemptionStatus::Pending {
|
||
return Err(CbppL1Error::InvalidStatus(
|
||
format!("Cannot cancel request with status {:?}", request.status)
|
||
));
|
||
}
|
||
|
||
request.status = RedemptionStatus::Cancelled;
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取赎回请求
|
||
pub fn get_redemption_request(&self, address: &Address) -> Option<&RedemptionRequest> {
|
||
self.requests.get(address)
|
||
}
|
||
|
||
/// 获取所有待处理的请求
|
||
pub fn get_pending_requests(&self) -> Vec<&RedemptionRequest> {
|
||
self.requests.values()
|
||
.filter(|r| r.status == RedemptionStatus::Pending)
|
||
.collect()
|
||
}
|
||
|
||
/// 获取赎回记录
|
||
pub fn get_redemption_records(&self, address: Option<&Address>) -> Vec<&RedemptionRecord> {
|
||
if let Some(addr) = address {
|
||
self.records.iter()
|
||
.filter(|r| &r.address == addr)
|
||
.collect()
|
||
} else {
|
||
self.records.iter().collect()
|
||
}
|
||
}
|
||
|
||
/// 获取赎回统计
|
||
pub fn get_redemption_statistics(&self) -> RedemptionStatistics {
|
||
let total_redemptions = self.records.len();
|
||
let full_redemptions = self.records.iter()
|
||
.filter(|r| r.redemption_type == RedemptionType::Full)
|
||
.count();
|
||
let partial_redemptions = self.records.iter()
|
||
.filter(|r| r.redemption_type == RedemptionType::Partial)
|
||
.count();
|
||
let emergency_redemptions = self.records.iter()
|
||
.filter(|r| r.redemption_type == RedemptionType::Emergency)
|
||
.count();
|
||
let total_redeemed = self.records.iter()
|
||
.map(|r| r.redeemed_amount)
|
||
.sum();
|
||
let total_penalties = self.records.iter()
|
||
.map(|r| r.penalty_amount)
|
||
.sum();
|
||
|
||
RedemptionStatistics {
|
||
total_redemptions,
|
||
full_redemptions,
|
||
partial_redemptions,
|
||
emergency_redemptions,
|
||
total_redeemed,
|
||
total_penalties,
|
||
}
|
||
}
|
||
|
||
/// 更新赎回条件
|
||
pub fn update_conditions(&mut self, conditions: RedemptionConditions) {
|
||
self.conditions = conditions;
|
||
}
|
||
|
||
/// 获取赎回条件
|
||
pub fn get_conditions(&self) -> &RedemptionConditions {
|
||
&self.conditions
|
||
}
|
||
}
|
||
|
||
impl Default for RedemptionManager {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
/// 赎回统计
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionStatistics {
|
||
/// 总赎回数
|
||
pub total_redemptions: usize,
|
||
/// 全额赎回数
|
||
pub full_redemptions: usize,
|
||
/// 部分赎回数
|
||
pub partial_redemptions: usize,
|
||
/// 紧急赎回数
|
||
pub emergency_redemptions: usize,
|
||
/// 总赎回金额
|
||
pub total_redeemed: u64,
|
||
/// 总处罚金额
|
||
pub total_penalties: u64,
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_node() -> CbpNode {
|
||
CbpNode {
|
||
address: Address::new([1u8; 32]),
|
||
did: "did:nac:test".to_string(),
|
||
stake_amount: 100_000_000_000,
|
||
kyc_level: 2,
|
||
hardware_score: 8000,
|
||
constitution_score: 80,
|
||
status: CbpStatus::Active,
|
||
registered_at: 1000,
|
||
last_active_at: 2000,
|
||
blocks_produced: 100,
|
||
reputation: 0.8,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_submit_redemption_request() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
// 满足锁定期
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
let result = manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
);
|
||
assert!(result.is_ok());
|
||
|
||
let request = manager.get_redemption_request(&node.address).unwrap();
|
||
assert_eq!(request.status, RedemptionStatus::Pending);
|
||
}
|
||
|
||
#[test]
|
||
fn test_lock_period_not_met() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
// 不满足锁定期
|
||
let timestamp = node.registered_at + 10 * 24 * 3600;
|
||
let result = manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
);
|
||
assert!(result.is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_partial_redemption_insufficient_remaining() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
// 赎回金额太多,剩余不足
|
||
let result = manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Partial,
|
||
60_000_000_000, // 剩余40XIC,小于最小保留50XIC
|
||
timestamp,
|
||
);
|
||
assert!(result.is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_redemption_full() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
).unwrap();
|
||
|
||
let result = manager.process_redemption(&node.address, &node, timestamp + 100);
|
||
assert!(result.is_ok());
|
||
|
||
let (actual, penalty) = result.unwrap();
|
||
assert_eq!(actual + penalty, node.stake_amount);
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_redemption_emergency() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Emergency,
|
||
node.stake_amount,
|
||
timestamp,
|
||
).unwrap();
|
||
|
||
let result = manager.process_redemption(&node.address, &node, timestamp + 100);
|
||
assert!(result.is_ok());
|
||
|
||
let (actual, penalty) = result.unwrap();
|
||
// 紧急赎回有20%处罚
|
||
assert_eq!(penalty, node.stake_amount * 20 / 100);
|
||
assert_eq!(actual, node.stake_amount - penalty);
|
||
}
|
||
|
||
#[test]
|
||
fn test_process_redemption_low_reputation() {
|
||
let mut manager = RedemptionManager::new();
|
||
let mut node = create_test_node();
|
||
node.reputation = 0.3; // 低于0.5阈值
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
).unwrap();
|
||
|
||
let result = manager.process_redemption(&node.address, &node, timestamp + 100);
|
||
assert!(result.is_ok());
|
||
|
||
let (actual, penalty) = result.unwrap();
|
||
// 低声誉有10%处罚
|
||
assert_eq!(penalty, node.stake_amount * 10 / 100);
|
||
}
|
||
|
||
#[test]
|
||
fn test_cancel_redemption_request() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
).unwrap();
|
||
|
||
let result = manager.cancel_redemption_request(&node.address);
|
||
assert!(result.is_ok());
|
||
|
||
let request = manager.get_redemption_request(&node.address).unwrap();
|
||
assert_eq!(request.status, RedemptionStatus::Cancelled);
|
||
}
|
||
|
||
#[test]
|
||
fn test_redemption_statistics() {
|
||
let mut manager = RedemptionManager::new();
|
||
let node = create_test_node();
|
||
|
||
let timestamp = node.registered_at + 31 * 24 * 3600;
|
||
manager.submit_redemption_request(
|
||
&node,
|
||
RedemptionType::Full,
|
||
node.stake_amount,
|
||
timestamp,
|
||
).unwrap();
|
||
manager.process_redemption(&node.address, &node, timestamp + 100).unwrap();
|
||
|
||
let stats = manager.get_redemption_statistics();
|
||
assert_eq!(stats.total_redemptions, 1);
|
||
assert_eq!(stats.full_redemptions, 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_update_conditions() {
|
||
let mut manager = RedemptionManager::new();
|
||
|
||
let new_conditions = RedemptionConditions {
|
||
min_lock_period: 60 * 24 * 3600, // 60天
|
||
min_remaining_stake: 80_000_000_000,
|
||
emergency_penalty_rate: 30,
|
||
reputation_penalty_threshold: 0.6,
|
||
low_reputation_penalty_rate: 15,
|
||
};
|
||
|
||
manager.update_conditions(new_conditions.clone());
|
||
|
||
let conditions = manager.get_conditions();
|
||
assert_eq!(conditions.min_lock_period, 60 * 24 * 3600);
|
||
assert_eq!(conditions.emergency_penalty_rate, 30);
|
||
}
|
||
}
|