use std::error::Error;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use bitcoin::hashes::sha256;
use clarity_repl::clarity::types::{PrincipalData, QualifiedContractIdentifier};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
use crate::blockchain::common::{BlockHeight, TokenAmount};
use crate::dao::governance::{GovernanceError, ProposalId};
use crate::dao::types::{Distribution, EmissionSchedule, TokenDistribution, VestingSchedule};
use crate::error::Error;
use crate::metrics::MetricsManager;
use crate::storage::Storage;
pub const TOKEN_NAME: &str = "Anya Governance Token";
pub const TOKEN_SYMBOL: &str = "AGT";
pub const TOKEN_DECIMALS: u8 = 8;
pub const TOTAL_SUPPLY: u64 = 21_000_000_000_00000000;
pub const INITIAL_BLOCK_REWARD: u64 = 10_000_00000000; pub const MIN_HALVING_INTERVAL: u64 = 105_000; pub const DEFAULT_HALVING_INTERVAL: u64 = 105_000; pub const RESERVE_RATIO_THRESHOLD: f64 = 0.15;
pub const TREASURY_PERCENTAGE: u64 = 35; pub const LIQUIDITY_PERCENTAGE: u64 = 25; pub const TEAM_PERCENTAGE: u64 = 20; pub const COMMUNITY_PERCENTAGE: u64 = 15; pub const PARTNERS_PERCENTAGE: u64 = 5;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenConfig {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: TokenAmount,
pub initial_block_reward: TokenAmount,
pub halving_interval: BlockHeight,
pub min_halving_interval: BlockHeight,
pub reserve_ratio: f64,
pub distribution: TokenDistribution,
}
impl Default for TokenConfig {
fn default() -> Self {
Self {
name: TOKEN_NAME.to_string(),
symbol: TOKEN_SYMBOL.to_string(),
decimals: TOKEN_DECIMALS,
total_supply: TokenAmount(TOTAL_SUPPLY),
initial_block_reward: TokenAmount(INITIAL_BLOCK_REWARD),
halving_interval: DEFAULT_HALVING_INTERVAL,
min_halving_interval: MIN_HALVING_INTERVAL,
reserve_ratio: RESERVE_RATIO_THRESHOLD,
distribution: TokenDistribution {
treasury: Distribution {
percentage: TREASURY_PERCENTAGE,
vesting: VestingSchedule::new_with_cliff(0, 48, 20),
allocation: TokenAmount(TOTAL_SUPPLY * TREASURY_PERCENTAGE / 100),
},
liquidity: Distribution {
percentage: LIQUIDITY_PERCENTAGE,
vesting: VestingSchedule::new_linear(0, 18, 50),
allocation: TokenAmount(TOTAL_SUPPLY * LIQUIDITY_PERCENTAGE / 100),
},
team: Distribution {
percentage: TEAM_PERCENTAGE,
vesting: VestingSchedule::new_with_cliff(12, 36, 0),
allocation: TokenAmount(TOTAL_SUPPLY * TEAM_PERCENTAGE / 100),
},
community: Distribution {
percentage: COMMUNITY_PERCENTAGE,
vesting: VestingSchedule::new_linear(0, 48, 10),
allocation: TokenAmount(TOTAL_SUPPLY * COMMUNITY_PERCENTAGE / 100),
},
partners: Distribution {
percentage: PARTNERS_PERCENTAGE,
vesting: VestingSchedule::new_linear(0, 36, 10),
allocation: TokenAmount(TOTAL_SUPPLY * PARTNERS_PERCENTAGE / 100),
},
},
}
}
}
pub struct TokenContract {
config: Arc<RwLock<TokenConfig>>,
balances: Arc<RwLock<HashMap<PrincipalData, TokenAmount>>>,
allowances: Arc<RwLock<HashMap<(PrincipalData, PrincipalData), TokenAmount>>>,
total_minted: Arc<RwLock<TokenAmount>>,
total_burned: Arc<RwLock<TokenAmount>>,
last_halving_block: Arc<RwLock<BlockHeight>>,
current_block_reward: Arc<RwLock<TokenAmount>>,
contract_id: QualifiedContractIdentifier,
storage: Arc<dyn Storage>,
metrics: Arc<MetricsManager>,
treasury_controller: Arc<Mutex<TreasuryController>>,
emission_schedule: Arc<RwLock<EmissionSchedule>>,
}
impl TokenContract {
pub async fn new(
storage: Arc<dyn Storage>,
metrics: Arc<MetricsManager>,
contract_id: QualifiedContractIdentifier,
) -> Result<Self, Error> {
let config = Arc::new(RwLock::new(TokenConfig::default()));
let balances = Arc::new(RwLock::new(HashMap::new()));
let allowances = Arc::new(RwLock::new(HashMap::new()));
let total_minted = Arc::new(RwLock::new(TokenAmount(0)));
let total_burned = Arc::new(RwLock::new(TokenAmount(0)));
let last_halving_block = Arc::new(RwLock::new(0));
let current_block_reward = Arc::new(RwLock::new(TokenAmount(INITIAL_BLOCK_REWARD)));
let emission_schedule = EmissionSchedule::new(
INITIAL_BLOCK_REWARD,
DEFAULT_HALVING_INTERVAL,
MIN_HALVING_INTERVAL,
);
let treasury_controller = TreasuryController::new(config.clone());
Ok(Self {
config,
balances,
allowances,
total_minted,
total_burned,
last_halving_block,
current_block_reward,
contract_id,
storage,
metrics,
treasury_controller: Arc::new(Mutex::new(treasury_controller)),
emission_schedule: Arc::new(RwLock::new(emission_schedule)),
})
}
pub async fn initialize_distribution(&self) -> Result<(), Error> {
let config = self.config.read().await;
let mut balances = self.balances.write().await;
let mut total_minted = self.total_minted.write().await;
let treasury_principal = self.get_treasury_principal()?;
let treasury_initial = config.distribution.treasury.initial_release();
balances.insert(treasury_principal.clone(), treasury_initial);
let liquidity_principal = self.get_liquidity_principal()?;
let liquidity_initial = config.distribution.liquidity.initial_release();
balances.insert(liquidity_principal.clone(), liquidity_initial);
let team_principal = self.get_team_principal()?;
let team_initial = config.distribution.team.initial_release();
balances.insert(team_principal.clone(), team_initial);
let community_principal = self.get_community_principal()?;
let community_initial = config.distribution.community.initial_release();
balances.insert(community_principal.clone(), community_initial);
let partners_principal = self.get_partners_principal()?;
let partners_initial = config.distribution.partners.initial_release();
balances.insert(partners_principal.clone(), partners_initial);
*total_minted = TokenAmount(
treasury_initial.0 +
liquidity_initial.0 +
team_initial.0 +
community_initial.0 +
partners_initial.0
);
info!(
"Token distribution initialized: Treasury={}, Liquidity={}, Team={}, Community={}, Partners={}",
treasury_initial.0, liquidity_initial.0, team_initial.0, community_initial.0, partners_initial.0
);
Ok(())
}
pub async fn process_block_rewards(&self, block_height: BlockHeight) -> Result<(), Error> {
let mut emission_schedule = self.emission_schedule.write().await;
let mut current_reward = self.current_block_reward.write().await;
let mut last_halving = self.last_halving_block.write().await;
if emission_schedule.should_halve(block_height, *last_halving) {
*current_reward = TokenAmount(current_reward.0 / 2);
*last_halving = block_height;
info!(
"Halving occurred at block {}: New block reward = {}",
block_height, current_reward.0
);
self.metrics.record_halving(block_height, current_reward.0);
}
let mining_principal = self.get_mining_rewards_principal()?;
self.mint_internal(mining_principal, current_reward.0).await?;
Ok(())
}
pub async fn update_emission_parameters(
&self,
halving_interval: Option<BlockHeight>,
proposal_id: ProposalId,
) -> Result<(), GovernanceError> {
self.validate_governance_action(proposal_id)?;
let mut emission_schedule = self.emission_schedule.write().await;
let mut config = self.config.write().await;
if let Some(interval) = halving_interval {
if interval < config.min_halving_interval {
return Err(GovernanceError::InvalidParameter(
format!("Halving interval cannot be less than {}", config.min_halving_interval)
));
}
emission_schedule.set_halving_interval(interval);
config.halving_interval = interval;
info!(
"Emission parameters updated via proposal {}: halving_interval={}",
proposal_id, interval
);
}
Ok(())
}
pub async fn execute_buyback_and_burn(
&self,
amount: TokenAmount,
proposal_id: ProposalId,
) -> Result<(), GovernanceError> {
self.validate_governance_action(proposal_id)?;
let treasury_principal = self.get_treasury_principal()?;
self.burn_internal(treasury_principal, amount.0).await
.map_err(|e| GovernanceError::ExecutionFailed(format!("Burn failed: {}", e)))?;
info!(
"Executed buyback and burn via proposal {}: amount={}",
proposal_id, amount.0
);
self.metrics.record_token_burn(amount.0);
Ok(())
}
pub async fn manage_protocol_liquidity(
&self,
amount: TokenAmount,
action: LiquidityAction,
proposal_id: ProposalId,
) -> Result<(), GovernanceError> {
self.validate_governance_action(proposal_id)?;
let treasury_principal = self.get_treasury_principal()?;
let liquidity_principal = self.get_liquidity_principal()?;
match action {
LiquidityAction::Add => {
self.transfer_internal(treasury_principal, liquidity_principal, amount.0).await
.map_err(|e| GovernanceError::ExecutionFailed(format!("Transfer failed: {}", e)))?;
info!(
"Added {} tokens to protocol-owned liquidity via proposal {}",
amount.0, proposal_id
);
},
LiquidityAction::Remove => {
self.transfer_internal(liquidity_principal, treasury_principal, amount.0).await
.map_err(|e| GovernanceError::ExecutionFailed(format!("Transfer failed: {}", e)))?;
info!(
"Removed {} tokens from protocol-owned liquidity via proposal {}",
amount.0, proposal_id
);
}
}
let mut treasury_controller = self.treasury_controller.lock()
.map_err(|_| GovernanceError::LockAcquisitionFailed)?;
treasury_controller.update_liquidity_metrics(action, amount);
Ok(())
}
async fn mint_internal(&self, to: PrincipalData, amount: u64) -> Result<(), Error> {
let mut balances = self.balances.write().await;
let mut total_minted = self.total_minted.write().await;
let current_balance = balances.get(&to).cloned().unwrap_or(TokenAmount(0));
balances.insert(to.clone(), TokenAmount(current_balance.0 + amount));
*total_minted = TokenAmount(total_minted.0 + amount);
debug!("Minted {} tokens to {}", amount, to);
self.metrics.record_token_mint(amount);
Ok(())
}
async fn burn_internal(&self, from: PrincipalData, amount: u64) -> Result<(), Error> {
let mut balances = self.balances.write().await;
let mut total_burned = self.total_burned.write().await;
let current_balance = balances.get(&from).cloned().unwrap_or(TokenAmount(0));
if current_balance.0 < amount {
return Err(Error::InsufficientBalance);
}
balances.insert(from.clone(), TokenAmount(current_balance.0 - amount));
*total_burned = TokenAmount(total_burned.0 + amount);
debug!("Burned {} tokens from {}", amount, from);
self.metrics.record_token_burn(amount);
Ok(())
}
async fn transfer_internal(
&self,
from: PrincipalData,
to: PrincipalData,
amount: u64,
) -> Result<(), Error> {
let mut balances = self.balances.write().await;
let from_balance = balances.get(&from).cloned().unwrap_or(TokenAmount(0));
if from_balance.0 < amount {
return Err(Error::InsufficientBalance);
}
balances.insert(from.clone(), TokenAmount(from_balance.0 - amount));
let to_balance = balances.get(&to).cloned().unwrap_or(TokenAmount(0));
balances.insert(to.clone(), TokenAmount(to_balance.0 + amount));
debug!("Transferred {} tokens from {} to {}", amount, from, to);
self.metrics.record_token_transfer(amount);
Ok(())
}
fn get_treasury_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.treasury")?)
}
fn get_liquidity_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.liquidity")?)
}
fn get_team_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.team")?)
}
fn get_community_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.community")?)
}
fn get_partners_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.partners")?)
}
fn get_mining_rewards_principal(&self) -> Result<PrincipalData, Error> {
Ok(PrincipalData::parse("ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.mining")?)
}
fn validate_governance_action(&self, proposal_id: ProposalId) -> Result<(), GovernanceError> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LiquidityAction {
Add,
Remove,
}
struct TreasuryController {
config: Arc<RwLock<TokenConfig>>,
pol_amount: TokenAmount, reserve_amount: TokenAmount, }
impl TreasuryController {
fn new(config: Arc<RwLock<TokenConfig>>) -> Self {
Self {
config,
pol_amount: TokenAmount(0),
reserve_amount: TokenAmount(0),
}
}
fn update_liquidity_metrics(&mut self, action: LiquidityAction, amount: TokenAmount) {
match action {
LiquidityAction::Add => {
self.pol_amount = TokenAmount(self.pol_amount.0 + amount.0);
},
LiquidityAction::Remove => {
if amount.0 <= self.pol_amount.0 {
self.pol_amount = TokenAmount(self.pol_amount.0 - amount.0);
}
}
}
}
fn update_reserve_metrics(&mut self, action: LiquidityAction, amount: TokenAmount) {
match action {
LiquidityAction::Add => {
self.reserve_amount = TokenAmount(self.reserve_amount.0 + amount.0);
},
LiquidityAction::Remove => {
if amount.0 <= self.reserve_amount.0 {
self.reserve_amount = TokenAmount(self.reserve_amount.0 - amount.0);
}
}
}
}
async fn check_reserve_ratio(&self, circulating_supply: TokenAmount) -> bool {
let config = match self.config.read().await {
Ok(config) => config,
Err(_) => return false,
};
let required_reserves = TokenAmount((circulating_supply.0 as f64 * config.reserve_ratio) as u64);
self.reserve_amount.0 >= required_reserves.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_token_distribution() {
assert_eq!(TREASURY_PERCENTAGE, 35);
assert_eq!(LIQUIDITY_PERCENTAGE, 25);
assert_eq!(TEAM_PERCENTAGE, 20);
assert_eq!(COMMUNITY_PERCENTAGE, 15);
assert_eq!(PARTNERS_PERCENTAGE, 5);
assert_eq!(
TREASURY_PERCENTAGE + LIQUIDITY_PERCENTAGE + TEAM_PERCENTAGE +
COMMUNITY_PERCENTAGE + PARTNERS_PERCENTAGE,
100
);
assert_eq!(INITIAL_BLOCK_REWARD, 10_000_00000000);
assert_eq!(MIN_HALVING_INTERVAL, 105_000);
}
}