- Remove duplicate `/marketplace/gpu/{gpu_id}` endpoint from marketplace_gpu.py
- Remove marketplace_gpu router inclusion from main.py (already included elsewhere)
- Fix staking service staker_count logic to check existing stakes before increment/decrement
- Add minimum stake amount validation (100 AITBC)
- Add proper error handling for stake not found cases
- Fix staking pool update to commit and refresh after modifications
- Update CLI send_transaction to use chain
333 lines
9.0 KiB
Python
333 lines
9.0 KiB
Python
"""
|
|
Shared fixtures for staking tests
|
|
Reusable fixtures for service and integration tests to avoid duplication
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker, Session
|
|
from sqlmodel import SQLModel
|
|
|
|
# Add paths for imports
|
|
sys.path.insert(0, "/opt/aitbc/apps/coordinator-api/src")
|
|
|
|
from app.domain.bounty import (
|
|
AgentStake, AgentMetrics, StakingPool,
|
|
StakeStatus, PerformanceTier
|
|
)
|
|
from app.services.staking_service import StakingService
|
|
|
|
|
|
@pytest.fixture
|
|
def db_session():
|
|
"""Create in-memory SQLite database for testing"""
|
|
engine = create_engine("sqlite:///:memory:")
|
|
SQLModel.metadata.create_all(engine)
|
|
SessionLocal = sessionmaker(bind=engine)
|
|
session = SessionLocal()
|
|
try:
|
|
yield session
|
|
finally:
|
|
session.close()
|
|
engine.dispose()
|
|
|
|
|
|
@pytest.fixture
|
|
def staking_service(db_session):
|
|
"""Create StakingService instance with test session"""
|
|
return StakingService(db_session)
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_wallet():
|
|
"""Default test agent wallet address"""
|
|
return "0x1234567890123456789012345678901234567890"
|
|
|
|
|
|
@pytest.fixture
|
|
def staker_address():
|
|
"""Default test staker address"""
|
|
return "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_metrics(agent_wallet):
|
|
"""Create test agent metrics with GOLD tier"""
|
|
return AgentMetrics(
|
|
agent_wallet=agent_wallet,
|
|
total_submissions=10,
|
|
successful_submissions=9,
|
|
average_accuracy=95.0,
|
|
current_tier=PerformanceTier.GOLD,
|
|
tier_score=85.0,
|
|
total_staked=0.0,
|
|
staker_count=0,
|
|
total_rewards_distributed=0.0,
|
|
last_update_time=datetime.utcnow()
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_metrics_bronze(agent_wallet):
|
|
"""Create test agent metrics with BRONZE tier"""
|
|
return AgentMetrics(
|
|
agent_wallet=agent_wallet,
|
|
total_submissions=5,
|
|
successful_submissions=4,
|
|
average_accuracy=80.0,
|
|
current_tier=PerformanceTier.BRONZE,
|
|
tier_score=60.0,
|
|
total_staked=0.0,
|
|
staker_count=0,
|
|
total_rewards_distributed=0.0,
|
|
last_update_time=datetime.utcnow()
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def agent_metrics_diamond(agent_wallet):
|
|
"""Create test agent metrics with DIAMOND tier"""
|
|
return AgentMetrics(
|
|
agent_wallet=agent_wallet,
|
|
total_submissions=50,
|
|
successful_submissions=48,
|
|
average_accuracy=98.0,
|
|
current_tier=PerformanceTier.DIAMOND,
|
|
tier_score=95.0,
|
|
total_staked=0.0,
|
|
staker_count=0,
|
|
total_rewards_distributed=0.0,
|
|
last_update_time=datetime.utcnow()
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def staking_pool(db_session, agent_metrics):
|
|
"""Create test staking pool"""
|
|
pool = StakingPool(
|
|
agent_wallet=agent_metrics.agent_wallet,
|
|
total_staked=0.0,
|
|
total_rewards=0.0,
|
|
pool_apy=5.0,
|
|
staker_count=0,
|
|
active_stakers=[],
|
|
last_distribution_time=datetime.utcnow(),
|
|
distribution_frequency=1
|
|
)
|
|
db_session.add(pool)
|
|
db_session.commit()
|
|
db_session.refresh(pool)
|
|
return pool
|
|
|
|
|
|
@pytest.fixture
|
|
def stake_data():
|
|
"""Default stake creation data"""
|
|
return {
|
|
"amount": 1000.0,
|
|
"lock_period": 30,
|
|
"auto_compound": False
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def large_stake_data():
|
|
"""Large stake creation data"""
|
|
return {
|
|
"amount": 50000.0,
|
|
"lock_period": 90,
|
|
"auto_compound": True
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def small_stake_data():
|
|
"""Small stake creation data"""
|
|
return {
|
|
"amount": 100.0,
|
|
"lock_period": 7,
|
|
"auto_compound": False
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def invalid_stake_data():
|
|
"""Invalid stake creation data (below minimum)"""
|
|
return {
|
|
"amount": 50.0,
|
|
"lock_period": 30,
|
|
"auto_compound": False
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def created_stake(staking_service, agent_metrics, staker_address, stake_data):
|
|
"""Create a stake for testing"""
|
|
return staking_service.create_stake(
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_metrics.agent_wallet,
|
|
amount=stake_data["amount"],
|
|
lock_period=stake_data["lock_period"],
|
|
auto_compound=stake_data["auto_compound"]
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def active_stake(db_session, agent_wallet, staker_address):
|
|
"""Create an active stake directly in database"""
|
|
stake = AgentStake(
|
|
stake_id="stake_test_001",
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_wallet,
|
|
amount=1000.0,
|
|
lock_period=30,
|
|
start_time=datetime.utcnow(),
|
|
end_time=datetime.utcnow() + timedelta(days=30),
|
|
status=StakeStatus.ACTIVE,
|
|
accumulated_rewards=0.0,
|
|
last_reward_time=datetime.utcnow(),
|
|
current_apy=8.25,
|
|
agent_tier=PerformanceTier.GOLD,
|
|
performance_multiplier=1.5,
|
|
auto_compound=False
|
|
)
|
|
db_session.add(stake)
|
|
db_session.commit()
|
|
db_session.refresh(stake)
|
|
return stake
|
|
|
|
|
|
@pytest.fixture
|
|
def unbonding_stake(db_session, agent_wallet, staker_address):
|
|
"""Create an unbonding stake directly in database"""
|
|
stake = AgentStake(
|
|
stake_id="stake_test_002",
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_wallet,
|
|
amount=1000.0,
|
|
lock_period=30,
|
|
start_time=datetime.utcnow() - timedelta(days=35),
|
|
end_time=datetime.utcnow() - timedelta(days=5),
|
|
status=StakeStatus.UNBONDING,
|
|
accumulated_rewards=50.0,
|
|
last_reward_time=datetime.utcnow() - timedelta(days=5),
|
|
current_apy=8.25,
|
|
agent_tier=PerformanceTier.GOLD,
|
|
performance_multiplier=1.5,
|
|
auto_compound=False,
|
|
unbonding_time=datetime.utcnow() - timedelta(days=5)
|
|
)
|
|
db_session.add(stake)
|
|
db_session.commit()
|
|
db_session.refresh(stake)
|
|
return stake
|
|
|
|
|
|
@pytest.fixture
|
|
def completed_stake(db_session, agent_wallet, staker_address):
|
|
"""Create a completed stake directly in database"""
|
|
stake = AgentStake(
|
|
stake_id="stake_test_003",
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_wallet,
|
|
amount=1000.0,
|
|
lock_period=30,
|
|
start_time=datetime.utcnow() - timedelta(days=70),
|
|
end_time=datetime.utcnow() - timedelta(days=40),
|
|
status=StakeStatus.COMPLETED,
|
|
accumulated_rewards=100.0,
|
|
last_reward_time=datetime.utcnow() - timedelta(days=40),
|
|
current_apy=8.25,
|
|
agent_tier=PerformanceTier.GOLD,
|
|
performance_multiplier=1.5,
|
|
auto_compound=False,
|
|
unbonding_time=datetime.utcnow() - timedelta(days=40)
|
|
)
|
|
db_session.add(stake)
|
|
db_session.commit()
|
|
db_session.refresh(stake)
|
|
return stake
|
|
|
|
|
|
@pytest.fixture
|
|
def multiple_stakes(db_session, agent_wallet, staker_address):
|
|
"""Create multiple stakes for testing"""
|
|
stakes = []
|
|
|
|
# Stake 1: Active, 30-day lock
|
|
stake1 = AgentStake(
|
|
stake_id="stake_test_001",
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_wallet,
|
|
amount=1000.0,
|
|
lock_period=30,
|
|
start_time=datetime.utcnow(),
|
|
end_time=datetime.utcnow() + timedelta(days=30),
|
|
status=StakeStatus.ACTIVE,
|
|
accumulated_rewards=0.0,
|
|
last_reward_time=datetime.utcnow(),
|
|
current_apy=8.25,
|
|
agent_tier=PerformanceTier.GOLD,
|
|
performance_multiplier=1.5,
|
|
auto_compound=False
|
|
)
|
|
|
|
# Stake 2: Active, 90-day lock with auto-compound
|
|
stake2 = AgentStake(
|
|
stake_id="stake_test_002",
|
|
staker_address=staker_address,
|
|
agent_wallet=agent_wallet,
|
|
amount=2000.0,
|
|
lock_period=90,
|
|
start_time=datetime.utcnow(),
|
|
end_time=datetime.utcnow() + timedelta(days=90),
|
|
status=StakeStatus.ACTIVE,
|
|
accumulated_rewards=0.0,
|
|
last_reward_time=datetime.utcnow(),
|
|
current_apy=10.0,
|
|
agent_tier=PerformanceTier.GOLD,
|
|
performance_multiplier=1.5,
|
|
auto_compound=True
|
|
)
|
|
|
|
db_session.add_all([stake1, stake2])
|
|
db_session.commit()
|
|
|
|
for stake in [stake1, stake2]:
|
|
db_session.refresh(stake)
|
|
stakes.append(stake)
|
|
|
|
return stakes
|
|
|
|
|
|
def calculate_expected_apy(base_apy=5.0, tier_multiplier=1.0, lock_multiplier=1.0):
|
|
"""Calculate expected APY based on parameters"""
|
|
apy = base_apy * tier_multiplier * lock_multiplier
|
|
return min(apy, 20.0) # Cap at 20%
|
|
|
|
|
|
def get_tier_multiplier(tier):
|
|
"""Get tier multiplier for APY calculation"""
|
|
multipliers = {
|
|
PerformanceTier.BRONZE: 1.0,
|
|
PerformanceTier.SILVER: 1.25,
|
|
PerformanceTier.GOLD: 1.5,
|
|
PerformanceTier.PLATINUM: 2.0,
|
|
PerformanceTier.DIAMOND: 3.0
|
|
}
|
|
return multipliers.get(tier, 1.0)
|
|
|
|
|
|
def get_lock_multiplier(lock_period_days):
|
|
"""Get lock period multiplier for APY calculation"""
|
|
if lock_period_days >= 365:
|
|
return 2.0
|
|
elif lock_period_days >= 90:
|
|
return 1.5
|
|
elif lock_period_days >= 30:
|
|
return 1.1
|
|
else:
|
|
return 1.0
|