520 lines
15 KiB
Rust
520 lines
15 KiB
Rust
// ACC-Redemption: 资产赎回协议(Redemption Protocol)
|
||
//
|
||
// NAC原生的资产赎回标准,用于资产退出机制
|
||
// 100% NAC原生协议,不是任何现有标准的实现
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 赎回状态
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum RedemptionStatus {
|
||
/// 待审核
|
||
Pending,
|
||
/// 已批准
|
||
Approved,
|
||
/// 处理中
|
||
Processing,
|
||
/// 已完成
|
||
Completed,
|
||
/// 已拒绝
|
||
Rejected,
|
||
/// 已取消
|
||
Cancelled,
|
||
}
|
||
|
||
/// 赎回类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum RedemptionType {
|
||
/// 全额赎回
|
||
Full,
|
||
/// 部分赎回
|
||
Partial,
|
||
/// 提前赎回
|
||
Early,
|
||
/// 到期赎回
|
||
Maturity,
|
||
}
|
||
|
||
/// 赎回请求
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionRequest {
|
||
/// 赎回ID
|
||
pub redemption_id: String,
|
||
/// 资产ID
|
||
pub asset_id: String,
|
||
/// 赎回人地址
|
||
pub redeemer: String,
|
||
/// 赎回类型
|
||
pub redemption_type: RedemptionType,
|
||
/// 赎回数量
|
||
pub amount: u128,
|
||
/// 赎回价格(USD,以分为单位)
|
||
pub redemption_price: u128,
|
||
/// 赎回状态
|
||
pub status: RedemptionStatus,
|
||
/// 请求时间
|
||
pub requested_at: u64,
|
||
/// 批准时间
|
||
pub approved_at: Option<u64>,
|
||
/// 完成时间
|
||
pub completed_at: Option<u64>,
|
||
/// 预期完成时间
|
||
pub expected_completion: u64,
|
||
/// 提前赎回费率(基点,如100表示1%)
|
||
pub early_redemption_fee: Option<u16>,
|
||
/// 实际费用
|
||
pub actual_fee: Option<u128>,
|
||
/// 审核备注
|
||
pub notes: Vec<String>,
|
||
}
|
||
|
||
/// 赎回错误类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum RedemptionError {
|
||
/// 赎回请求不存在
|
||
RequestNotFound,
|
||
/// 赎回请求已存在
|
||
RequestAlreadyExists,
|
||
/// 资产不存在
|
||
AssetNotFound,
|
||
/// 赎回金额不足
|
||
InsufficientAmount,
|
||
/// 赎回已完成
|
||
AlreadyCompleted,
|
||
/// 赎回已取消
|
||
AlreadyCancelled,
|
||
/// 未授权操作
|
||
Unauthorized,
|
||
/// 赎回窗口未开放
|
||
RedemptionWindowClosed,
|
||
/// 提前赎回费用过高
|
||
EarlyRedemptionFeeTooHigh,
|
||
}
|
||
|
||
/// 赎回状态
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionState {
|
||
/// 赎回请求映射 (redemption_id -> RedemptionRequest)
|
||
pub redemptions: HashMap<String, RedemptionRequest>,
|
||
/// 资产赎回历史 (asset_id -> [redemption_ids])
|
||
pub asset_redemptions: HashMap<String, Vec<String>>,
|
||
/// 赎回人赎回列表 (redeemer -> [redemption_ids])
|
||
pub redeemer_redemptions: HashMap<String, Vec<String>>,
|
||
}
|
||
|
||
/// ACC-Redemption接口
|
||
pub trait ACCRedemption {
|
||
/// 提交赎回请求
|
||
fn submit_redemption(&mut self, request: RedemptionRequest) -> Result<(), RedemptionError>;
|
||
|
||
/// 获取赎回请求
|
||
fn get_redemption(&self, redemption_id: &str) -> Result<RedemptionRequest, RedemptionError>;
|
||
|
||
/// 获取资产的赎回历史
|
||
fn get_asset_redemptions(&self, asset_id: &str) -> Vec<RedemptionRequest>;
|
||
|
||
/// 批准赎回请求
|
||
fn approve_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError>;
|
||
|
||
/// 拒绝赎回请求
|
||
fn reject_redemption(
|
||
&mut self,
|
||
redemption_id: &str,
|
||
reason: String,
|
||
) -> Result<(), RedemptionError>;
|
||
|
||
/// 开始处理赎回
|
||
fn process_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError>;
|
||
|
||
/// 完成赎回
|
||
fn complete_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError>;
|
||
|
||
/// 取消赎回请求
|
||
fn cancel_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError>;
|
||
|
||
/// 计算赎回费用
|
||
fn calculate_redemption_fee(&self, redemption_id: &str) -> Result<u128, RedemptionError>;
|
||
|
||
/// 更新赎回状态
|
||
fn update_status(
|
||
&mut self,
|
||
redemption_id: &str,
|
||
status: RedemptionStatus,
|
||
) -> Result<(), RedemptionError>;
|
||
|
||
/// 获取赎回人的赎回列表
|
||
fn get_redeemer_redemptions(&self, redeemer: &str) -> Vec<RedemptionRequest>;
|
||
|
||
/// 添加审核备注
|
||
fn add_note(&mut self, redemption_id: &str, note: String) -> Result<(), RedemptionError>;
|
||
|
||
/// 获取待处理的赎回请求
|
||
fn get_pending_redemptions(&self) -> Vec<RedemptionRequest>;
|
||
}
|
||
|
||
/// ACC-Redemption标准实现
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct RedemptionToken {
|
||
state: RedemptionState,
|
||
}
|
||
|
||
impl RedemptionToken {
|
||
/// 创建新的赎回管理器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
state: RedemptionState {
|
||
redemptions: HashMap::new(),
|
||
asset_redemptions: HashMap::new(),
|
||
redeemer_redemptions: HashMap::new(),
|
||
},
|
||
}
|
||
}
|
||
|
||
/// 获取状态的可变引用
|
||
pub fn state_mut(&mut self) -> &mut RedemptionState {
|
||
&mut self.state
|
||
}
|
||
|
||
/// 获取状态的不可变引用
|
||
pub fn state(&self) -> &RedemptionState {
|
||
&self.state
|
||
}
|
||
|
||
/// 验证赎回请求
|
||
fn validate_request(&self, request: &RedemptionRequest) -> Result<(), RedemptionError> {
|
||
// 检查赎回金额
|
||
if request.amount == 0 {
|
||
return Err(RedemptionError::InsufficientAmount);
|
||
}
|
||
|
||
// 检查赎回价格
|
||
if request.redemption_price == 0 {
|
||
return Err(RedemptionError::InsufficientAmount);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl Default for RedemptionToken {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl ACCRedemption for RedemptionToken {
|
||
fn submit_redemption(&mut self, mut request: RedemptionRequest) -> Result<(), RedemptionError> {
|
||
// 检查赎回请求是否已存在
|
||
if self.state.redemptions.contains_key(&request.redemption_id) {
|
||
return Err(RedemptionError::RequestAlreadyExists);
|
||
}
|
||
|
||
// 验证赎回请求
|
||
self.validate_request(&request)?;
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
request.requested_at = now;
|
||
request.status = RedemptionStatus::Pending;
|
||
|
||
// 计算提前赎回费用
|
||
if request.redemption_type == RedemptionType::Early {
|
||
if let Some(fee_rate) = request.early_redemption_fee {
|
||
let fee = request.amount * fee_rate as u128 / 10000;
|
||
request.actual_fee = Some(fee);
|
||
}
|
||
}
|
||
|
||
let redemption_id = request.redemption_id.clone();
|
||
let asset_id = request.asset_id.clone();
|
||
let redeemer = request.redeemer.clone();
|
||
|
||
// 保存赎回请求
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.clone(), request);
|
||
|
||
// 更新资产赎回历史
|
||
self.state
|
||
.asset_redemptions
|
||
.entry(asset_id)
|
||
.or_insert_with(Vec::new)
|
||
.push(redemption_id.clone());
|
||
|
||
// 更新赎回人赎回列表
|
||
self.state
|
||
.redeemer_redemptions
|
||
.entry(redeemer)
|
||
.or_insert_with(Vec::new)
|
||
.push(redemption_id);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_redemption(&self, redemption_id: &str) -> Result<RedemptionRequest, RedemptionError> {
|
||
self.state
|
||
.redemptions
|
||
.get(redemption_id)
|
||
.cloned()
|
||
.ok_or(RedemptionError::RequestNotFound)
|
||
}
|
||
|
||
fn get_asset_redemptions(&self, asset_id: &str) -> Vec<RedemptionRequest> {
|
||
self.state
|
||
.asset_redemptions
|
||
.get(asset_id)
|
||
.map(|redemption_ids| {
|
||
redemption_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.redemptions.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn approve_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
|
||
if request.status != RedemptionStatus::Pending {
|
||
return Err(RedemptionError::AlreadyCompleted);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
request.status = RedemptionStatus::Approved;
|
||
request.approved_at = Some(now);
|
||
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn reject_redemption(
|
||
&mut self,
|
||
redemption_id: &str,
|
||
reason: String,
|
||
) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
|
||
if request.status != RedemptionStatus::Pending {
|
||
return Err(RedemptionError::AlreadyCompleted);
|
||
}
|
||
|
||
request.status = RedemptionStatus::Rejected;
|
||
request.notes.push(format!("Rejected: {}", reason));
|
||
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn process_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
|
||
if request.status != RedemptionStatus::Approved {
|
||
return Err(RedemptionError::Unauthorized);
|
||
}
|
||
|
||
request.status = RedemptionStatus::Processing;
|
||
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn complete_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
|
||
if request.status != RedemptionStatus::Processing {
|
||
return Err(RedemptionError::Unauthorized);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
request.status = RedemptionStatus::Completed;
|
||
request.completed_at = Some(now);
|
||
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn cancel_redemption(&mut self, redemption_id: &str) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
|
||
if request.status == RedemptionStatus::Completed {
|
||
return Err(RedemptionError::AlreadyCompleted);
|
||
}
|
||
|
||
if request.status == RedemptionStatus::Cancelled {
|
||
return Err(RedemptionError::AlreadyCancelled);
|
||
}
|
||
|
||
request.status = RedemptionStatus::Cancelled;
|
||
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn calculate_redemption_fee(&self, redemption_id: &str) -> Result<u128, RedemptionError> {
|
||
let request = self.get_redemption(redemption_id)?;
|
||
|
||
if let Some(fee) = request.actual_fee {
|
||
Ok(fee)
|
||
} else {
|
||
Ok(0)
|
||
}
|
||
}
|
||
|
||
fn update_status(
|
||
&mut self,
|
||
redemption_id: &str,
|
||
status: RedemptionStatus,
|
||
) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
request.status = status;
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
Ok(())
|
||
}
|
||
|
||
fn get_redeemer_redemptions(&self, redeemer: &str) -> Vec<RedemptionRequest> {
|
||
self.state
|
||
.redeemer_redemptions
|
||
.get(redeemer)
|
||
.map(|redemption_ids| {
|
||
redemption_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.redemptions.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn add_note(&mut self, redemption_id: &str, note: String) -> Result<(), RedemptionError> {
|
||
let mut request = self.get_redemption(redemption_id)?;
|
||
request.notes.push(note);
|
||
self.state
|
||
.redemptions
|
||
.insert(redemption_id.to_string(), request);
|
||
Ok(())
|
||
}
|
||
|
||
fn get_pending_redemptions(&self) -> Vec<RedemptionRequest> {
|
||
self.state
|
||
.redemptions
|
||
.values()
|
||
.filter(|request| request.status == RedemptionStatus::Pending)
|
||
.cloned()
|
||
.collect()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_request() -> RedemptionRequest {
|
||
RedemptionRequest {
|
||
redemption_id: "RED-001".to_string(),
|
||
asset_id: "RWA-001".to_string(),
|
||
redeemer: "alice".to_string(),
|
||
redemption_type: RedemptionType::Full,
|
||
amount: 1000000,
|
||
redemption_price: 1000000_00,
|
||
status: RedemptionStatus::Pending,
|
||
requested_at: 0,
|
||
approved_at: None,
|
||
completed_at: None,
|
||
expected_completion: 1234567890 + 86400 * 7,
|
||
early_redemption_fee: None,
|
||
actual_fee: None,
|
||
notes: Vec::new(),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_submit_redemption() {
|
||
let mut redemption = RedemptionToken::new();
|
||
let request = create_test_request();
|
||
assert!(redemption.submit_redemption(request).is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_approve_redemption() {
|
||
let mut redemption = RedemptionToken::new();
|
||
let request = create_test_request();
|
||
redemption.submit_redemption(request).unwrap();
|
||
|
||
assert!(redemption.approve_redemption("RED-001").is_ok());
|
||
assert_eq!(
|
||
redemption.get_redemption("RED-001").unwrap().status,
|
||
RedemptionStatus::Approved
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_complete_redemption() {
|
||
let mut redemption = RedemptionToken::new();
|
||
let request = create_test_request();
|
||
redemption.submit_redemption(request).unwrap();
|
||
|
||
redemption.approve_redemption("RED-001").unwrap();
|
||
redemption.process_redemption("RED-001").unwrap();
|
||
assert!(redemption.complete_redemption("RED-001").is_ok());
|
||
|
||
assert_eq!(
|
||
redemption.get_redemption("RED-001").unwrap().status,
|
||
RedemptionStatus::Completed
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_early_redemption_fee() {
|
||
let mut redemption = RedemptionToken::new();
|
||
let mut request = create_test_request();
|
||
request.redemption_type = RedemptionType::Early;
|
||
request.early_redemption_fee = Some(100); // 1%
|
||
|
||
redemption.submit_redemption(request).unwrap();
|
||
|
||
let fee = redemption.calculate_redemption_fee("RED-001").unwrap();
|
||
assert_eq!(fee, 10000); // 1% of 1000000
|
||
}
|
||
|
||
#[test]
|
||
fn test_cancel_redemption() {
|
||
let mut redemption = RedemptionToken::new();
|
||
let request = create_test_request();
|
||
redemption.submit_redemption(request).unwrap();
|
||
|
||
assert!(redemption.cancel_redemption("RED-001").is_ok());
|
||
assert_eq!(
|
||
redemption.get_redemption("RED-001").unwrap().status,
|
||
RedemptionStatus::Cancelled
|
||
);
|
||
}
|
||
}
|