Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion switchboard/adapters/lucidly.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def __init__(
self._wallet_total_usd: dict[str, float] = {}
self._total_parked: float = 0.0
self._total_yield: float = 0.0
# Monotonic suffix so two parks on the same chain at the same clock
# value get distinct keys instead of silently overwriting each other.
self._position_seq: int = 0

def _target_bps(self, chain: str) -> int:
return self.config.per_chain_targets.get(chain, self.config.idle_target_bps)
Expand Down Expand Up @@ -157,7 +160,8 @@ def rebalance(self, chain: str, liquid_balance_usd: float) -> dict[str, Any]:
syUSD_shares=shares,
parked_at=self.clock(),
)
self._positions[f"{chain}:{self.clock()}"] = position
self._position_seq += 1
self._positions[f"{chain}:{self.clock()}:{self._position_seq}"] = position
self._liquid_buffer[chain] = current_liquid - excess
self._total_parked += excess

Expand Down
21 changes: 21 additions & 0 deletions tests/test_lucidly.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,24 @@ def test_ensure_liquid_unparks_to_threshold_buffer():

assert returned == 500.0
assert park.status("base")["liquid_buffer_usd"] == 1_500.0


def test_two_parks_same_chain_same_clock_are_distinct_positions():
# The position key was `chain:clock()`. The adapter takes an injectable
# deterministic clock ("Pluggable clock for deterministic tests"), so two
# parks on the same chain at the same clock value mapped to the SAME key —
# the second ParkedPosition silently overwrote the first, dropping its
# per-position yield accrual even though `_total_parked` still counted both.
fixed_t = 1_000_000.0
park = LucidlyAutoPark(clock=lambda: fixed_t)

r1 = park.rebalance("base", liquid_balance_usd=10_000.0)
r2 = park.rebalance("base", liquid_balance_usd=10_000.0)
assert r1["action"] == "parked"
assert r2["action"] == "parked"

report = park.yield_report()
# Both park events must be retained as distinct positions, and the position
# count must stay consistent with the parked total (which already sums both).
assert report["positions"] == 2
assert report["total_parked_usd"] == r1["amount_usd"] + r2["amount_usd"]
Loading