use std::sync::Arc;
use parking_lot::RwLock;
use tenzro_storage::{CF_TOKENS, KvStore};
use tenzro_types::economics::EconomicPolicy;
use tracing::{info, warn};
use crate::error::{Result, TokenError};
pub const ECONOMIC_POLICY_KEY: &str = "economic_policy:current";
pub struct EconomicPolicyManager {
policy: RwLock<EconomicPolicy>,
storage: Option<Arc<dyn KvStore>>,
}
impl EconomicPolicyManager {
pub fn new() -> Self {
Self {
policy: RwLock::new(EconomicPolicy::default()),
storage: None,
}
}
pub fn with_storage(storage: Arc<dyn KvStore>) -> Self {
let policy = match storage.get(CF_TOKENS, ECONOMIC_POLICY_KEY.as_bytes()) {
Ok(Some(bytes)) => match serde_json::from_slice::<EconomicPolicy>(&bytes) {
Ok(stored) => match stored.validate() {
Ok(()) => {
info!("Hydrated economic policy from storage");
stored
}
Err(e) => {
warn!(
"Stored economic policy is invalid ({e}); falling back to the default. \
The network will charge default rates until governance sets a valid \
policy."
);
EconomicPolicy::default()
}
},
Err(e) => {
warn!("Stored economic policy could not be decoded ({e}); using the default");
EconomicPolicy::default()
}
},
_ => EconomicPolicy::default(),
};
Self {
policy: RwLock::new(policy),
storage: Some(storage),
}
}
pub fn current(&self) -> EconomicPolicy {
*self.policy.read()
}
pub fn apply(&self, policy: EconomicPolicy) -> Result<()> {
policy
.validate()
.map_err(|e| TokenError::InvalidAmount(format!("economic policy rejected: {e}")))?;
*self.policy.write() = policy;
if let Some(ref storage) = self.storage {
let bytes = serde_json::to_vec(&policy).map_err(|e| {
TokenError::InvalidAmount(format!("economic policy could not be encoded: {e}"))
})?;
storage
.put(CF_TOKENS, ECONOMIC_POLICY_KEY.as_bytes(), &bytes)
.map_err(|e| {
TokenError::InvalidAmount(format!("economic policy could not be stored: {e}"))
})?;
}
info!(
operator_validating_bps = policy.validating.operator_bps,
operator_delegated_bps = policy.delegated.operator_bps,
rpc_provider_bps = policy.delegated.rpc_provider_bps,
"Applied economic policy"
);
Ok(())
}
}
impl Default for EconomicPolicyManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tenzro_types::economics::{DelegatedSchedule, ValidatingSchedule};
#[test]
fn a_fresh_manager_holds_the_default_policy() {
let mgr = EconomicPolicyManager::new();
assert_eq!(mgr.current(), EconomicPolicy::default());
}
#[test]
fn a_valid_policy_becomes_live() {
let mgr = EconomicPolicyManager::new();
let policy = EconomicPolicy {
delegated: DelegatedSchedule {
operator_bps: 7_000,
rpc_provider_bps: 2_000,
treasury_bps: 1_000,
},
..EconomicPolicy::default()
};
mgr.apply(policy).unwrap();
assert_eq!(mgr.current().delegated.rpc_provider_bps, 2_000);
}
#[test]
fn an_invalid_policy_is_refused_and_the_previous_one_stays_live() {
let mgr = EconomicPolicyManager::new();
let before = mgr.current();
let broken = EconomicPolicy {
validating: ValidatingSchedule {
operator_bps: 4_000,
treasury_bps: 6_000,
},
..EconomicPolicy::default()
};
assert!(mgr.apply(broken).is_err());
assert_eq!(mgr.current(), before, "the live policy must be untouched");
}
}