myco-bonding-curve/tests/test_commitments.py

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