feat(horizon): expose released-but-unwithdrawn delegation (slashable, derived view)#1340
feat(horizon): expose released-but-unwithdrawn delegation (slashable, derived view)#1340cargopete wants to merge 1 commit into
Conversation
feat(horizon): add delegation withdrawable bucket (tokensWithdrawable)
🚨 Report Summary
For more details view the full report in OpenZeppelin Code Inspector |
|
Hi, thanks for the contribution. Please see my comments in https://forum.thegraph.com/t/separate-withdrawable-bucket-for-fully-thawed-delegation-tokens/6882/4 about requiring a stronger justification for a change like this to go through, and other requirements for contributions to the repository. For completeness sake, I did a first quick pass with Claude review tool on the proposed changes and several important issues were identified (all validated by myself): |
d6920bc to
5d05ca9
Compare
|
Closing this in favour of the off-chain approach, per the discussion on the forum and @tmigone's feedback. The conclusion we reached: the actively-earning delegation base is already derivable on-chain ( This PR served its purpose as a proof that a hardened, slashable, append-only version was buildable — but "buildable" isn't "worth it" given the added storage, accounting and slashing surface for a value that's ultimately consumed off-chain. Thanks @tmigone for the thorough review. |
feat(horizon): expose released-but-unwithdrawn delegation (slashable, derived-view)
Summary
Reworks the original "withdrawable bucket" PR to expose the split between in-period and
completed-but-unwithdrawn delegation without changing the actively-earning base, the
slashing semantics, or the public struct ABI.
Context / correction
The original PR assumed
getDelegatedTokensAvailable = tokens - tokensThawingfailed toexclude completed-but-unwithdrawn delegation. It does not: completed thaw requests remain
in
tokensThawinguntilwithdrawDelegatedfulfils them, so the active base is alreadycorrect. This PR therefore does not change that formula. The only new capability is a
permissionless way to surface, and a view to read, how much of
tokensThawinghascompleted thawing.
Design
releaseThawedDelegationno longer moves tokens. It records, per delegator, how manythawing-pool shares have matured (
sharesWithdrawable), leaving the tokens in theslashable thawing pool.
withdrawDelegatedredeems those shares against the live(post-slash) thawing-pool ratio.
getDelegatedTokensAvailableunderflow / pool-brick.
sharesWithdrawableand bumps the existingthawingNonce, whichinvalidates each delegator's stale released shares (
withdrawableThawingNonce).getDelegatedTokensWithdrawable()is a derived view:sharesWithdrawable * tokensThawing / sharesThawing.Changes
IHorizonStakingTypes.sol — append-only:
DelegationPoolInternal.sharesWithdrawable(afterthawingNonce)DelegationInternal.sharesWithdrawable,DelegationInternal.withdrawableThawingNonceDelegationPoolstruct: unchanged (no ABI shift)IHorizonStakingMain.sol
DelegationThawReleasedevent;releaseThawedDelegation(...)(permissionless, neverreverts on nothing-to-do); updated
withdrawDelegatedNatSpec.IHorizonStakingBase.sol
getDelegatedTokensWithdrawablegetter;getDelegatedTokensAvailableNatSpec clarified(formula unchanged).
HorizonStaking.sol
releaseThawedDelegationexternal wrapper +_releaseThawedDelegation(usesLinkedList.traverse) +_releaseThawRequestcallback (emits per-requestThawRequestFulfilled)._withdrawDelegatedredeems released shares; lazy-releases first for backward compat;no-op (not revert) when nothing matured or fully slashed.
slash(): one line — resetsharesWithdrawableon full thaw-pool drain._undelegate: reverted to originaltokens - tokensThawingactive base.HorizonStakingBase.sol — derived withdrawable getter; reverted active-base formula.
Tests
New
test/unit/staking/delegation/release.t.sol(10 tests): permissionless release,idempotency, no-op-when-unmatured / no-requests, release-then-withdraw, active-base
invariance, released-tokens-remain-slashable, no-slash-evasion-advantage,
full-slash-invalidates-released-shares, withdrawable-view-zero-when-no-thawing.
All 350 horizon unit tests pass. solhint (
--max-warnings=0) and prettier clean.Backward compatibility
withdrawDelegatedworks unchanged for delegators (lazy release internally).DelegationPoolABI unchanged; new fields are appended internal storage only.ThawRequestFulfilledstill fires (now on the release step);the delegation-withdraw aggregate is
DelegationThawReleasedrather thanThawRequestsFulfilled. Network-subgraph handlers should indexDelegationThawReleased.Still required before merge
auditlabel (maintainer) to clear the Require-Audit-Label check.main.