420 lines
16 KiB
Rust
420 lines
16 KiB
Rust
// acc20c_judicial.rs — ACC-20C 司法冻结与合规检查模块
|
||
//
|
||
// 本模块实现 CNNL 宪法条款 A53(合规检查)和 A55(司法冻结)要求的功能。
|
||
// 作为独立文件新增,不修改现有 main.rs 的任何逻辑。
|
||
//
|
||
// 新增路由:
|
||
// POST /acc20c/freeze — 司法冻结包装资产(A55)
|
||
// POST /acc20c/unfreeze — 解除司法冻结(A55)
|
||
// POST /acc20c/compliance/check — 合规性检查(A53)
|
||
//
|
||
// 依赖:复用 main.rs 中已定义的 SharedState、Acc20cWrappedAsset 等类型
|
||
|
||
use actix_web::{web, HttpResponse};
|
||
use chrono::Utc;
|
||
use serde::{Deserialize, Serialize};
|
||
use sha3::{Digest, Sha3_384};
|
||
|
||
use crate::{AccState, SharedState};
|
||
|
||
// ============================================================
|
||
// 请求/响应数据结构
|
||
// ============================================================
|
||
|
||
/// 司法冻结请求(CNNL 宪法条款 A55)
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct FreezeReq {
|
||
/// 要冻结的 Wrapper Token ID
|
||
pub wrapper_token_id: String,
|
||
/// 冻结发起方地址(必须是司法授权地址)
|
||
pub authority_address: String,
|
||
/// 冻结原因(司法命令编号或描述)
|
||
pub reason: String,
|
||
/// 司法辖区代码
|
||
pub jurisdiction: String,
|
||
/// 宪法收据 ID(由 CEE 颁发,证明此冻结已通过宪法审查)
|
||
pub constitutional_receipt_id: Option<String>,
|
||
}
|
||
|
||
/// 解冻请求(CNNL 宪法条款 A55)
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct UnfreezeReq {
|
||
/// 要解冻的 Wrapper Token ID
|
||
pub wrapper_token_id: String,
|
||
/// 解冻发起方地址(必须是司法授权地址)
|
||
pub authority_address: String,
|
||
/// 解冻原因
|
||
pub reason: String,
|
||
/// 宪法收据 ID(由 CEE 颁发)
|
||
pub constitutional_receipt_id: Option<String>,
|
||
}
|
||
|
||
/// 合规检查请求(CNNL 宪法条款 A53)
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct ComplianceCheckReq {
|
||
/// 要检查的 Wrapper Token ID
|
||
pub wrapper_token_id: String,
|
||
/// 检查类型:wrap / unwrap / transfer / valuation
|
||
pub check_type: String,
|
||
/// 操作发起方地址
|
||
pub initiator: String,
|
||
/// 目标地址(转移时使用)
|
||
pub target: Option<String>,
|
||
}
|
||
|
||
/// 合规检查结果
|
||
#[derive(Debug, Serialize)]
|
||
pub struct ComplianceCheckResult {
|
||
pub wrapper_token_id: String,
|
||
pub check_type: String,
|
||
pub passed: bool,
|
||
pub clauses_verified: Vec<String>,
|
||
pub failed_clause: Option<String>,
|
||
pub reason: String,
|
||
pub checked_at: i64,
|
||
pub constitutional_receipt_id: String,
|
||
}
|
||
|
||
/// 冻结/解冻操作结果
|
||
#[derive(Debug, Serialize)]
|
||
pub struct JudicialActionResult {
|
||
pub wrapper_token_id: String,
|
||
pub action: String,
|
||
pub success: bool,
|
||
pub previous_status: String,
|
||
pub new_status: String,
|
||
pub authority_address: String,
|
||
pub reason: String,
|
||
pub tx_hash: String,
|
||
pub jurisdiction: String,
|
||
pub constitutional_receipt_id: String,
|
||
pub timestamp: i64,
|
||
}
|
||
|
||
// ============================================================
|
||
// 内部辅助函数
|
||
// ============================================================
|
||
|
||
/// 生成 SHA3-384 哈希(NAC 原生 48 字节哈希)
|
||
fn sha3_384_hex(input: &str) -> String {
|
||
let mut hasher = Sha3_384::new();
|
||
hasher.update(input.as_bytes());
|
||
hex::encode(hasher.finalize())
|
||
}
|
||
|
||
/// 验证地址格式(NAC 原生 32 字节地址 = 64 个十六进制字符)
|
||
fn is_valid_nac_address(addr: &str) -> bool {
|
||
addr.len() == 64 && addr.chars().all(|c| c.is_ascii_hexdigit())
|
||
}
|
||
|
||
/// 生成宪法收据 ID(模拟 CEE 颁发)
|
||
fn generate_cr_id(action: &str, token_id: &str, authority: &str) -> String {
|
||
let input = format!(
|
||
"CR:ACC20C:{}:{}:{}:{}",
|
||
action,
|
||
token_id,
|
||
authority,
|
||
Utc::now().timestamp_millis()
|
||
);
|
||
format!("CR-{}", &sha3_384_hex(&input)[..32])
|
||
}
|
||
|
||
// ============================================================
|
||
// 路由处理函数
|
||
// ============================================================
|
||
|
||
/// POST /acc20c/freeze — 司法冻结包装资产
|
||
/// 对应 CNNL 宪法条款 A55:司法冻结操作必须通过 CEE 宪法执行引擎审查
|
||
pub async fn acc20c_freeze(
|
||
state: web::Data<SharedState>,
|
||
req: web::Json<FreezeReq>,
|
||
) -> HttpResponse {
|
||
// ── 1. 验证权限地址格式 ──────────────────────────────────
|
||
if !is_valid_nac_address(&req.authority_address) {
|
||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "无效的权限地址格式(需要 64 位十六进制 NAC 原生地址)",
|
||
"clause": "A55"
|
||
}));
|
||
}
|
||
|
||
// ── 2. 验证冻结原因不为空 ────────────────────────────────
|
||
if req.reason.trim().is_empty() {
|
||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "冻结原因不能为空(宪法条款 A55 要求)",
|
||
"clause": "A55"
|
||
}));
|
||
}
|
||
|
||
let mut s: std::sync::MutexGuard<AccState> = state.lock().unwrap();
|
||
|
||
// ── 3. 查找包装资产 ──────────────────────────────────────
|
||
let asset = match s.acc20c_wrapped_assets.get(&req.wrapper_token_id) {
|
||
Some(a) => a.clone(),
|
||
None => {
|
||
return HttpResponse::NotFound().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "包装资产不存在",
|
||
"wrapper_token_id": req.wrapper_token_id
|
||
}));
|
||
}
|
||
};
|
||
|
||
// ── 4. 检查资产是否已被冻结 ──────────────────────────────
|
||
if asset.status == "frozen" {
|
||
return HttpResponse::Conflict().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "资产已处于冻结状态",
|
||
"wrapper_token_id": req.wrapper_token_id,
|
||
"current_status": "frozen"
|
||
}));
|
||
}
|
||
|
||
// ── 5. 检查资产是否已被销毁 ──────────────────────────────
|
||
if asset.status == "burned" {
|
||
return HttpResponse::Conflict().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "已销毁的资产不能被冻结",
|
||
"wrapper_token_id": req.wrapper_token_id
|
||
}));
|
||
}
|
||
|
||
// ── 6. 生成宪法收据 ID ───────────────────────────────────
|
||
let cr_id = req.constitutional_receipt_id.clone().unwrap_or_else(|| {
|
||
generate_cr_id("FREEZE", &req.wrapper_token_id, &req.authority_address)
|
||
});
|
||
|
||
// ── 7. 生成交易哈希 ──────────────────────────────────────
|
||
let tx_input = format!(
|
||
"ACC20C:FREEZE:{}:{}:{}:{}",
|
||
req.wrapper_token_id,
|
||
req.authority_address,
|
||
req.reason,
|
||
Utc::now().timestamp_millis()
|
||
);
|
||
let tx_hash = format!("0x{}", &sha3_384_hex(&tx_input)[..64]);
|
||
|
||
let previous_status = asset.status.clone();
|
||
|
||
// ── 8. 执行冻结 ──────────────────────────────────────────
|
||
if let Some(a) = s.acc20c_wrapped_assets.get_mut(&req.wrapper_token_id) {
|
||
a.status = "frozen".to_string();
|
||
a.constitutional_receipt_id = cr_id.clone();
|
||
}
|
||
|
||
let result = JudicialActionResult {
|
||
wrapper_token_id: req.wrapper_token_id.clone(),
|
||
action: "freeze".to_string(),
|
||
success: true,
|
||
previous_status,
|
||
new_status: "frozen".to_string(),
|
||
authority_address: req.authority_address.clone(),
|
||
reason: req.reason.clone(),
|
||
tx_hash,
|
||
jurisdiction: req.jurisdiction.clone(),
|
||
constitutional_receipt_id: cr_id,
|
||
timestamp: Utc::now().timestamp_millis(),
|
||
};
|
||
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"result": result,
|
||
"clause": "A55",
|
||
"message": "资产已成功冻结,宪法条款 A55 已执行"
|
||
}))
|
||
}
|
||
|
||
/// POST /acc20c/unfreeze — 解除司法冻结
|
||
/// 对应 CNNL 宪法条款 A55:解冻操作同样需要宪法授权
|
||
pub async fn acc20c_unfreeze(
|
||
state: web::Data<SharedState>,
|
||
req: web::Json<UnfreezeReq>,
|
||
) -> HttpResponse {
|
||
// ── 1. 验证权限地址格式 ──────────────────────────────────
|
||
if !is_valid_nac_address(&req.authority_address) {
|
||
return HttpResponse::BadRequest().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "无效的权限地址格式(需要 64 位十六进制 NAC 原生地址)",
|
||
"clause": "A55"
|
||
}));
|
||
}
|
||
|
||
let mut s: std::sync::MutexGuard<AccState> = state.lock().unwrap();
|
||
|
||
// ── 2. 查找包装资产 ──────────────────────────────────────
|
||
let asset = match s.acc20c_wrapped_assets.get(&req.wrapper_token_id) {
|
||
Some(a) => a.clone(),
|
||
None => {
|
||
return HttpResponse::NotFound().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "包装资产不存在",
|
||
"wrapper_token_id": req.wrapper_token_id
|
||
}));
|
||
}
|
||
};
|
||
|
||
// ── 3. 检查资产是否处于冻结状态 ──────────────────────────
|
||
if asset.status != "frozen" {
|
||
return HttpResponse::Conflict().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "资产当前不处于冻结状态,无需解冻",
|
||
"wrapper_token_id": req.wrapper_token_id,
|
||
"current_status": asset.status
|
||
}));
|
||
}
|
||
|
||
// ── 4. 生成宪法收据 ID ───────────────────────────────────
|
||
let cr_id = req.constitutional_receipt_id.clone().unwrap_or_else(|| {
|
||
generate_cr_id("UNFREEZE", &req.wrapper_token_id, &req.authority_address)
|
||
});
|
||
|
||
// ── 5. 生成交易哈希 ──────────────────────────────────────
|
||
let tx_input = format!(
|
||
"ACC20C:UNFREEZE:{}:{}:{}:{}",
|
||
req.wrapper_token_id,
|
||
req.authority_address,
|
||
req.reason,
|
||
Utc::now().timestamp_millis()
|
||
);
|
||
let tx_hash = format!("0x{}", &sha3_384_hex(&tx_input)[..64]);
|
||
|
||
// ── 6. 执行解冻 ──────────────────────────────────────────
|
||
if let Some(a) = s.acc20c_wrapped_assets.get_mut(&req.wrapper_token_id) {
|
||
a.status = "active".to_string();
|
||
a.constitutional_receipt_id = cr_id.clone();
|
||
}
|
||
|
||
let result = JudicialActionResult {
|
||
wrapper_token_id: req.wrapper_token_id.clone(),
|
||
action: "unfreeze".to_string(),
|
||
success: true,
|
||
previous_status: "frozen".to_string(),
|
||
new_status: "active".to_string(),
|
||
authority_address: req.authority_address.clone(),
|
||
reason: req.reason.clone(),
|
||
tx_hash,
|
||
jurisdiction: asset.jurisdiction.clone(),
|
||
constitutional_receipt_id: cr_id,
|
||
timestamp: Utc::now().timestamp_millis(),
|
||
};
|
||
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"result": result,
|
||
"clause": "A55",
|
||
"message": "资产已成功解冻,宪法条款 A55 已执行"
|
||
}))
|
||
}
|
||
|
||
/// POST /acc20c/compliance/check — 合规性检查
|
||
/// 对应 CNNL 宪法条款 A53:所有 ACC-20C 操作前必须通过合规检查
|
||
pub async fn acc20c_compliance_check(
|
||
state: web::Data<SharedState>,
|
||
req: web::Json<ComplianceCheckReq>,
|
||
) -> HttpResponse {
|
||
let s: std::sync::MutexGuard<AccState> = state.lock().unwrap();
|
||
|
||
// ── 1. 查找包装资产 ──────────────────────────────────────
|
||
let asset = match s.acc20c_wrapped_assets.get(&req.wrapper_token_id) {
|
||
Some(a) => a.clone(),
|
||
None => {
|
||
return HttpResponse::NotFound().json(serde_json::json!({
|
||
"success": false,
|
||
"error": "包装资产不存在",
|
||
"wrapper_token_id": req.wrapper_token_id
|
||
}));
|
||
}
|
||
};
|
||
|
||
let now = Utc::now().timestamp_millis();
|
||
let mut clauses_verified: Vec<String> = Vec::new();
|
||
let mut passed = true;
|
||
let mut failed_clause: Option<String> = None;
|
||
let mut reason = "所有宪法条款验证通过".to_string();
|
||
|
||
// ── 2. 验证 A53:资产状态检查 ────────────────────────────
|
||
if asset.status == "frozen" {
|
||
passed = false;
|
||
failed_clause = Some("A55".to_string());
|
||
reason = format!(
|
||
"资产处于司法冻结状态,{}操作被拒绝(宪法条款 A55)",
|
||
req.check_type
|
||
);
|
||
} else if asset.status == "burned" {
|
||
passed = false;
|
||
failed_clause = Some("A53".to_string());
|
||
reason = "资产已销毁,无法执行任何操作(宪法条款 A53)".to_string();
|
||
} else {
|
||
clauses_verified.push("A53".to_string());
|
||
}
|
||
|
||
// ── 3. 验证 A54:解包冷却期检查(仅 unwrap 操作)────────
|
||
if passed && req.check_type == "unwrap" {
|
||
if asset.unlock_timestamp > now {
|
||
let remaining_secs = (asset.unlock_timestamp - now) / 1000;
|
||
passed = false;
|
||
failed_clause = Some("A54".to_string());
|
||
reason = format!(
|
||
"解包冷却期未结束,还需等待 {} 秒(宪法条款 A54)",
|
||
remaining_secs
|
||
);
|
||
} else {
|
||
clauses_verified.push("A54".to_string());
|
||
}
|
||
}
|
||
|
||
// ── 4. 验证 A56:转移目标地址格式(仅 transfer 操作)────
|
||
if passed && req.check_type == "transfer" {
|
||
match &req.target {
|
||
None => {
|
||
passed = false;
|
||
failed_clause = Some("A56".to_string());
|
||
reason = "转移操作必须提供目标地址(宪法条款 A56)".to_string();
|
||
}
|
||
Some(target) => {
|
||
if !is_valid_nac_address(target) {
|
||
passed = false;
|
||
failed_clause = Some("A56".to_string());
|
||
reason = "目标地址格式无效(需要 64 位十六进制 NAC 原生地址,宪法条款 A56)"
|
||
.to_string();
|
||
} else {
|
||
clauses_verified.push("A56".to_string());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── 5. 生成合规检查收据 ──────────────────────────────────
|
||
let cr_input = format!(
|
||
"CR:COMPLIANCE:{}:{}:{}:{}",
|
||
req.check_type, req.wrapper_token_id, req.initiator, now
|
||
);
|
||
let cr_id = format!("CR-COMP-{}", &sha3_384_hex(&cr_input)[..28]);
|
||
|
||
let result = ComplianceCheckResult {
|
||
wrapper_token_id: req.wrapper_token_id.clone(),
|
||
check_type: req.check_type.clone(),
|
||
passed,
|
||
clauses_verified,
|
||
failed_clause,
|
||
reason,
|
||
checked_at: now,
|
||
constitutional_receipt_id: cr_id,
|
||
};
|
||
|
||
let status_code = if passed { 200u16 } else { 403u16 };
|
||
|
||
if status_code == 200 {
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"result": result
|
||
}))
|
||
} else {
|
||
HttpResponse::Forbidden().json(serde_json::json!({
|
||
"success": false,
|
||
"result": result
|
||
}))
|
||
}
|
||
}
|