All 10 build-order stages complete and unit-tested (49 tests). Verified live on mainnet: registration/address derivation, deposit crediting, a real 10 PLM bet (broadcast + confirmed + change credited). A full round close->draw->payout cycle was triggered live and was in progress at commit time. Withdrawal and RBF bump are unit-tested but not yet exercised against a live broadcast. Known gaps (scheduler doesn't resume mid-flight rounds after restart, payout has no retry, no deployment setup, etc.) are documented in CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
3.8 KiB
Python
101 lines
3.8 KiB
Python
import pytest
|
|
from embit import script
|
|
from embit.bip32 import HDKey
|
|
from embit.transaction import Transaction
|
|
|
|
from app.wallet.plm_network import PLM_MAINNET
|
|
from app.wallet.psbt_builder import InsufficientFundsError, Utxo, build_payout_transaction, estimate_vsize
|
|
|
|
|
|
def _key(seed_byte: int) -> HDKey:
|
|
root = HDKey.from_seed(bytes([seed_byte]) * 32, version=PLM_MAINNET["xprv"])
|
|
return root.derive("m/84h/746h/0h/0/0")
|
|
|
|
|
|
def test_payout_deducts_fee_only_from_winner_share():
|
|
pool_key = _key(10)
|
|
pool_script = script.p2wpkh(pool_key.to_public())
|
|
pool_address = pool_script.address(network=PLM_MAINNET)
|
|
winner_address = script.p2wpkh(_key(11).to_public()).address(network=PLM_MAINNET)
|
|
fee_address = script.p2wpkh(_key(12).to_public()).address(network=PLM_MAINNET)
|
|
|
|
pool_amount = 10_000_000_000 # 100 PLM pot
|
|
winner_share = pool_amount * 70 // 100
|
|
commission = pool_amount - winner_share
|
|
|
|
utxos = [Utxo("aa" * 32, 0, pool_amount)]
|
|
built = build_payout_transaction(
|
|
signing_key=pool_key,
|
|
from_script=pool_script,
|
|
utxos=utxos,
|
|
winner_address=winner_address,
|
|
winner_share_sats=winner_share,
|
|
fee_address=fee_address,
|
|
commission_sats=commission,
|
|
change_address=pool_address,
|
|
fee_rate_sat_vb=1,
|
|
)
|
|
|
|
fee = estimate_vsize(1, 3)
|
|
assert built.fee_sats == fee
|
|
assert built.winner_sats == winner_share - fee
|
|
assert built.commission_sats == commission # untouched by the fee
|
|
assert built.change_sats == pool_amount - (winner_share + commission)
|
|
|
|
parsed = Transaction.parse(bytes.fromhex(built.raw_hex))
|
|
assert len(parsed.vout) == 2 # no change needed: winner_share + commission == pool_amount exactly
|
|
amounts = sorted(o.value for o in parsed.vout)
|
|
assert amounts == sorted([built.winner_sats, built.commission_sats])
|
|
|
|
|
|
def test_payout_adds_change_output_when_pool_utxos_exceed_target():
|
|
pool_key = _key(20)
|
|
pool_script = script.p2wpkh(pool_key.to_public())
|
|
pool_address = pool_script.address(network=PLM_MAINNET)
|
|
winner_address = script.p2wpkh(_key(21).to_public()).address(network=PLM_MAINNET)
|
|
fee_address = script.p2wpkh(_key(22).to_public()).address(network=PLM_MAINNET)
|
|
|
|
winner_share = 700_000_000
|
|
commission = 300_000_000
|
|
utxos = [Utxo("bb" * 32, 0, 2_000_000_000)] # more than winner_share+commission
|
|
built = build_payout_transaction(
|
|
signing_key=pool_key,
|
|
from_script=pool_script,
|
|
utxos=utxos,
|
|
winner_address=winner_address,
|
|
winner_share_sats=winner_share,
|
|
fee_address=fee_address,
|
|
commission_sats=commission,
|
|
change_address=pool_address,
|
|
fee_rate_sat_vb=1,
|
|
)
|
|
assert built.change_sats == 2_000_000_000 - (winner_share + commission)
|
|
|
|
parsed = Transaction.parse(bytes.fromhex(built.raw_hex))
|
|
assert len(parsed.vout) == 3
|
|
|
|
|
|
def test_payout_raises_when_winner_share_too_small():
|
|
pool_key = _key(30)
|
|
pool_script = script.p2wpkh(pool_key.to_public())
|
|
pool_address = pool_script.address(network=PLM_MAINNET)
|
|
winner_address = script.p2wpkh(_key(31).to_public()).address(network=PLM_MAINNET)
|
|
fee_address = script.p2wpkh(_key(32).to_public()).address(network=PLM_MAINNET)
|
|
|
|
winner_share = 100 # smaller than the ~172 sat fee at 1 sat/vB for 1-in-3-out
|
|
commission = 50
|
|
assert winner_share < estimate_vsize(1, 3)
|
|
utxos = [Utxo("cc" * 32, 0, winner_share + commission)]
|
|
with pytest.raises(InsufficientFundsError):
|
|
build_payout_transaction(
|
|
signing_key=pool_key,
|
|
from_script=pool_script,
|
|
utxos=utxos,
|
|
winner_address=winner_address,
|
|
winner_share_sats=winner_share,
|
|
fee_address=fee_address,
|
|
commission_sats=commission,
|
|
change_address=pool_address,
|
|
fee_rate_sat_vb=1,
|
|
)
|