165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
"""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
|