"""Tests for commitment-based issuance channels.""" import numpy as np import pytest from src.commitments.labor import ( LaborIssuanceSystem, ContributorState, ContributionType, attest_contribution, claim_tokens, create_default_system, ) from src.commitments.subscription import ( SubscriptionSystem, SubscriptionTier, create_subscription, process_payment, cancel_subscription, create_default_tiers, ) from src.commitments.staking import ( StakingParams, StakingSystem, lockup_multiplier, create_stake, compute_pending_bonus, claim_bonus, early_withdraw, ) class TestLabor: def test_attest_and_claim(self): system = create_default_system() contrib = ContributorState(address="alice") contrib = attest_contribution(system, contrib, "code", 5.0, current_time=0) assert contrib.unclaimed_units["code"] == 5.0 system, contrib, tokens = claim_tokens(system, contrib, "code", current_time=0) assert tokens == 5.0 * 10.0 # 5 units * 10 tokens/unit assert contrib.unclaimed_units["code"] == 0.0 def test_rate_limit(self): system = create_default_system() contrib = ContributorState(address="bob") # Attest more than max_units_per_period contrib = attest_contribution(system, contrib, "code", 100.0, current_time=0) system, contrib, tokens = claim_tokens(system, contrib, "code", current_time=0) # Should be capped at 50 units (max_units_per_period for code) assert tokens == 50.0 * 10.0 def test_cooldown(self): system = create_default_system() contrib = ContributorState(address="carol") contrib = attest_contribution(system, contrib, "code", 10.0, current_time=0) system, contrib, t1 = claim_tokens(system, contrib, "code", current_time=0) assert t1 > 0 # Try to claim again immediately — should fail (cooldown = 1 day) contrib = attest_contribution(system, contrib, "code", 5.0, current_time=0.5) system, contrib, t2 = claim_tokens(system, contrib, "code", current_time=0.5) assert t2 == 0.0 def test_global_cap(self): system = create_default_system() system.max_total_mint = 100.0 contrib = ContributorState(address="dave") contrib = attest_contribution(system, contrib, "code", 20.0, current_time=0) system, contrib, tokens = claim_tokens(system, contrib, "code", current_time=0) assert tokens <= 100.0 class TestSubscription: def test_create_and_process(self): tiers = create_default_tiers() system = SubscriptionSystem(tiers=tiers) system, sub = create_subscription(system, "alice", "supporter", 0.0) assert sub.is_active # Process after one period system, tokens = process_payment(system, "alice", 30.0) assert tokens > 0 assert system.total_revenue == 10.0 def test_loyalty_multiplier_grows(self): tiers = create_default_tiers() system = SubscriptionSystem(tiers=tiers) system, _ = create_subscription(system, "alice", "supporter", 0.0) # First payment system, tokens_early = process_payment(system, "alice", 30.0) # Payment after 1 year (more loyalty) system, tokens_later = process_payment(system, "alice", 395.0) # Later payment should earn more due to loyalty assert tokens_later > tokens_early def test_cancellation(self): tiers = create_default_tiers() system = SubscriptionSystem(tiers=tiers) system, _ = create_subscription(system, "alice", "supporter", 0.0) system = cancel_subscription(system, "alice") system, tokens = process_payment(system, "alice", 30.0) assert tokens == 0.0 def test_no_payment_before_period(self): tiers = create_default_tiers() system = SubscriptionSystem(tiers=tiers) system, _ = create_subscription(system, "alice", "supporter", 0.0) system, tokens = process_payment(system, "alice", 15.0) # Half period assert tokens == 0.0 class TestStaking: def test_lockup_multiplier_sqrt(self): params = StakingParams(multiplier_curve="sqrt") m_short = lockup_multiplier(30, params) m_long = lockup_multiplier(365, params) assert m_long > m_short assert m_long == params.max_multiplier # Max at max duration def test_lockup_multiplier_concave(self): """Sqrt curve should be concave (diminishing returns).""" params = StakingParams(multiplier_curve="sqrt") m_30 = lockup_multiplier(30, params) m_60 = lockup_multiplier(60, params) m_90 = lockup_multiplier(90, params) # Gain from 30→60 should be > gain from 60→90 gain_1 = m_60 - m_30 gain_2 = m_90 - m_60 assert gain_1 > gain_2 def test_create_and_claim(self): system = StakingSystem(params=StakingParams()) system, pos = create_stake(system, "alice", 1000.0, "MYCO", 90.0, 0.0) assert system.total_staked == 1000.0 # Can't claim before lockup ends system, tokens = claim_bonus(system, "alice", 50.0) assert tokens == 0.0 # Can claim after lockup system, tokens = claim_bonus(system, "alice", 91.0) assert tokens > 0 def test_early_withdrawal_penalty(self): system = StakingSystem(params=StakingParams()) system, _ = create_stake(system, "bob", 1000.0, "MYCO", 90.0, 0.0) system, returned, forfeited = early_withdraw(system, "bob", 45.0) assert returned == 1000.0 # Get staked amount back assert forfeited > 0 # But forfeit some bonus def test_longer_lockup_more_bonus(self): params = StakingParams() system1 = StakingSystem(params=params) system2 = StakingSystem(params=params) system1, pos1 = create_stake(system1, "a", 1000.0, "MYCO", 30.0, 0.0) system2, pos2 = create_stake(system2, "b", 1000.0, "MYCO", 365.0, 0.0) bonus1 = compute_pending_bonus(pos1, params, 30.0) bonus2 = compute_pending_bonus(pos2, params, 365.0) assert bonus2 > bonus1