Fix missing UnstakingManager subscription; track vault exchange rate and staking positions - #22
Fix missing UnstakingManager subscription; track vault exchange rate and staking positions#22lcamargof wants to merge 4 commits into
Conversation
…vault exchange rate and staking positions - Move the UnstakingManager template subscription into getOrCreateStakingToken so vaults discovered through Governor.token() or timelock role grants index their locks (BSC/mainnet/base vlRSR had unstakingManager = null and no Lock entities) - Add the ReserveOptimisticGovernorDeployer data source per chain (ReserveOptimisticGovernorSystemDeployed, both new-vault and existing-vault flavours) - Add a hardcoded TrackedVault data source per chain (the vlRSR vault) with a polling block handler sampling convertToAssets/totalAssets every ~10 minutes; StakingToken.exchangeRate/totalAssets, daily + change-only snapshots - Add StakingPosition / StakingPositionRecord / StakingPositionDailySnapshot: average-cost principal, pro-rata release on withdraw/transfer, realized rewards, LOCK_CANCEL re-deposits, accountingComplete for late-discovered positions - Handle UnstakingDelaySet / RewardRatioSet; save Account before referencing it from Lock / RewardClaim - Tests replay the real BSC events (deployer event, both LockCreated, the locker's full 14-event history against an independent oracle)
Shares received from a holder whose own cost basis was unknown were folded into the receiver's tracked pool with zero principal, so a later withdrawal booked the full payout as realized rewards. Positions now hold two pools (shares with a known basis, untrackedShares without); exits take from both pro rata and only the tracked part releases principal or earns rewards, on any number of hops.
Vaults are discovered at creation (deployer events + TrackedVault), so every share is seen entering; the quarantine pools and accountingComplete flag were a safety net for a case that cannot happen. Back to plain average-cost principal.
For deployWithExistingStakingVault the deployer handler created the Governance before its GovernanceTimelock existed, so getOrCreateGovernance froze optimisticProposers to [] and nothing backfilled it (prod currently has them via the role-grant path). The handler now only discovers the vault through Governor.token(); the DTF role grant creates timelock + governance as on main.
|
Code review — a few correctness issues worth fixing before merge, plus some smaller notes. 1. 2. Position tracking runs on non-vault StakingTokens ( 3. Exchange rate freezes for any future vault outside the hardcoded 4. Smaller notes:
Solid PR overall — the test coverage (real BSC replay against an independent oracle) is great. Items 1–4 are the ones I'd address before merge. |
Summary
Version 1.10.0 — clean reindex of all three networks (no graft). Deployed as
dtf-index-{mainnet,base,bsc}/1.10.0and syncing;prodtags still on 1.9.8.Bug: locks never indexed for vaults discovered outside a deployer event
The vlRSR vaults (BSC
0xE744…, mainnet0xABbD…, base0x2F0D…) were deployed by the per-chainReserveOptimisticGovernorDeployer(reserve-governor repo), which the subgraph didn't track. They got discovered throughGovernor.token(), and that path only subscribed theStakingTokentemplate — theUnstakingManagersubscription lived solely in theGovernanceDeployerhandler. Result on Goldsky:unstakingManager: null,locks: []whileLockCreatedevents sat on chain.getOrCreateStakingTokennow subscribes both templates (subscribeUnstakingManager), so every discovery path is covered.OptimisticGovernorDeployerdata source per chain (mainnet0x2acc45e1…, base0x604d70d1…, bsc0x1c10e68b…).deployWithNewStakingVaultindexes the vault, manager, governance and timelock (the AccessControl vault emits noOwnershipTransferred).deployWithExistingStakingVault(stakingVault = 0x0) only discovers the vault; the DTF governance keeps being created by the DTF role grant, where its timelock andoptimisticProposersare known.Accountis now saved beforeLock/RewardClaimreference it.Vault exchange rate
The reserve-governor
StakingVaultstreams native RSR rewards intototalAssets(), so vlRSR/RSR is no longer 1:1 and drifts every block. A hardcodedTrackedVaultdata source per chain (networks.json, the vlRSR vault) runs a polling block handler every ~10 minutes (everyderived fromsecondsPerBlock: mainnet 50, base 300, bsc 1333 blocks) that recordsStakingToken.exchangeRate/totalAssets, a daily upsert snapshot and a change-only snapshot. Also handlesUnstakingDelaySet/RewardRatioSet.Per-account staking positions
StakingPosition(average-costprincipal,realizedRewards, lifetime totals),StakingPositionRecordperDeposit/LOCK_CANCEL/Withdraw/TRANSFER_IN|OUT, andStakingPositionDailySnapshot. Withdrawals release principal pro rata and realize the excess; wallet transfers move principal pro rata; a cancelled lock (Deposit.sender == unstakingManager) re-enters as principal without inflating lifetime totals. Query recipe for the UI chart indocs/staking-vaults.md.Validation
LockCreatedevents (blocks 116930184 / 116931302), and the locker's full 14-event history checked against an independent BigInt oracle (final shares matchbalanceOfon chain).graph buildgreen for mainnet, base and bsc; all 19deployWithExistingStakingVaultgovernances verified on chain to point at their chain's vlRSR; deploy script confirmed not to graft.