FundForwarderV2: dual-source routing, batch deployment, TLV chain scanner#7
Open
Ramarti wants to merge 7 commits into
Open
FundForwarderV2: dual-source routing, batch deployment, TLV chain scanner#7Ramarti wants to merge 7 commits into
Ramarti wants to merge 7 commits into
Conversation
FundForwarderV2 accepts two authorized source vaults (the original TLV and the 2026-08-13 TLV, the latter optional via bytes32(0)) and forwards the full msg.value of every source transfer into one target vault (the user's 2028 TLV). The V1 tradingDesk/forwardThreshold split logic is removed; non-source transfers stay in the EOA and are never swept. FundForwarderFactoryV2 keys the CREATE3 salt on the beneficiary hash so each user gets exactly one forwarder shared by both source vaults, and verifies the target vault's beneficiary() matches the beneficiary hash. computeForwarderAddress(sourceVault) keeps the V1 signature and resolves the beneficiary hash from the vault. The deployed V1 sources remain untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DeployFundForwarderFactoryV2 deploys the V2 factory with a fresh salt. DeployFundForwardersBatch consumes the 2028 vault batch output JSON, derives the source hashes from the vault addresses (zero aug2026Vault = no secondary), verifies each target vault's beneficiary, deploys the forwarders via CREATE3, and writes the enriched batch with forwarder addresses. Idempotent: already-deployed forwarders are config-verified and skipped. Covered by DeployFundForwardersBatch.t.sol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds docs/TLV-2028-Extension.md: vault-generation economic parameters (2028 TLV: cliff 2028-02-13, 25% at cliff, linear monthly over 36 months, fully vested 2031-02-13), staking rewards claimable from deployment, allocation policy, the V1->V2 forwarder economics change (threshold/tradingDesk split removed, 100% forward to the 2028 TLV), contract changes, the JSON deployment pipeline with runbook, and the addresses table. Exempts the file from the docs/ gitignore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discovers all deployed TLVs on Story with no trusted input list, via three modalities: VaultCreated logs from the known TLV factories, ForwarderDeployed logs from the V1 forwarder factory plus a prediction cross-check, and Blockscout verified-contract search. Candidates are validated with multicall-batched TLV getters at a block pinned after log discovery (public RPCs prune historical state), bucketed by cliff date, grouped per beneficiary hash, and classified into the design-doc user states with data flags and a suggestedAllocation2028. Outputs a rich status report and the DeployVaults2028 input file; an optional "address,name" CSV attaches labels. Verified against Story mainnet: 316 users, 283 original and 49 aug-2026 vaults discovered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five verified FundForwarderFactory deployments exist on Story mainnet (three with ForwarderDeployed events, 533 total). The scanner config now takes v1ForwarderFactories as an array: events are collected across all of them, the newest record per source vault wins, and the prediction cross-check runs per factory to catch pre-event deployments. The effective aug-2026 vault is the one the newest forwarder targets (users have one target vault per forwarder round). Adds selfTargetForwarder flag: 215 newest records on mainnet target their own source vault, an on-chain re-lock pattern that needs ops review. getLogs chunks now retry with backoff and a 30s HTTP timeout (public RPC flakiness). Example config ships the five factory addresses; docs record the mainnet findings and the zero-secondary deviation from the Code Change Reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ramarti
force-pushed
the
feat/fund-forwarder-v2
branch
from
July 22, 2026 17:31
cc24603 to
c79fe1d
Compare
The mainnet scan showed 23 users with 64M IP of remaining entitlement in additional original vaults that the single-vault suggestion ignored. The suggestion now adds the remaining of every original vault not covered by the aug-2026 vault (covered = the vault the routing forwarder drains, else an exact remaining==allocation sizing match, else originals[0] as a conservative default against double counting); without an aug-2026 vault it sums all originals. Adds a moreThanTwoSources flag for users whose potential sources exceed FundForwarderV2's two source slots and need out-of-band handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forwarders deploy once per beneficiary and cannot be redeployed, so an
accidental zero secondary would be a permanent silent misconfiguration:
the user's 2026-08-13 TLV withdrawals would land liquid in their EOA.
FundForwarderV2 and FundForwarderFactoryV2 now revert with
InvalidSecondarySourceVaultHash on a zero secondary, matching the Code
Change Reference. Users without a 2026-08-13 TLV deploy with the
explicit per-beneficiary sentinel noSecondarySentinel(beneficiaryHash)
= keccak256("TLV2028_NO_SECONDARY" || beneficiaryHash), which is inert
at runtime; the batch script fills it in automatically for entries with
a zero aug2026Vault.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the fund-forwarder side of the TLV Extension to 2028 (design doc, code change reference). The deployed V1 contracts are untouched; new implementations are separate files.
Contracts
FundForwarderV2.sol— two authorized source hashes (original TLV + 2026-08-13 TLV) and one target (the user's 2028 TLV).receive()forwards the fullmsg.valueof every source transfer to the target; non-source transfers stay in the EOA and the balance is never swept. The V1 tradingDesk/forwardThreshold split logic is removed (equivalent to the reference'stradingDesk=0, threshold=0configuration, minus the now-unusedwithdrawn()read).FundForwarderFactoryV2.sol— CREATE3 salt keyed on the beneficiary hash: one forwarder per user, shared by both source vaults.deployForwarder(beneficiaryHash, sourceVaultHash, secondarySourceVaultHash, target)verifiestarget.beneficiary() == beneficiaryHash(TargetBeneficiaryMismatch).computeForwarderAddress(sourceVault)keeps the V1 signature and reads the beneficiary hash from the vault;computeForwarderAddressByBeneficiarypredicts before any vault exists;isForwarderDeployedreturnsfalse(V1 reverted) when nothing is deployed.interfaces/ISourceVaultV2.sol—beneficiary() → bytes32.Secondary source is always required (matches the code change reference): forwarders deploy once per beneficiary and cannot be redeployed, so an accidental zero secondary would be a permanent silent misconfiguration. Both contracts revert
InvalidSecondarySourceVaultHashon zero; users without a 2026-08-13 TLV deploy with the explicit inert sentinelnoSecondarySentinel(beneficiaryHash) = keccak256("TLV2028_NO_SECONDARY" ‖ beneficiaryHash), which can never match a real sender hash.Deployment scripts (JSON-driven)
script/DeployFundForwarderFactoryV2.s.sol— deploys the V2 factory (--sig "run(bytes32)" <fresh salt>).script/DeployFundForwardersBatch.s.sol— consumes theDeployVaults2028output from the timelock-vest-vault repo, derives source hashes (zeroaug2026Vaultin the JSON → the no-secondary sentinel on-chain), verifies each target vault's beneficiary, deploys via CREATE3, and writes the batch enriched withforwarderper entry. Idempotent: already-deployed forwarders are config-verified and skipped.Offchain scanner (
offchain/, TypeScript + viem)Discovers every deployed TLV on Story with no trusted input list:
VaultCreatedlogs from the TLV factories,ForwarderDeployedlogs from the V1 forwarder factories (several exist; newest record per source vault wins) plus a per-factory prediction cross-check, and Blockscout verified-contract search. Candidates are validated with multicall-batched getters at a pinned block, bucketed by cliff date, grouped per beneficiary hash, and classified into the design-doc user states with review flags (selfTargetForwarder,moreThanTwoSources,multipleOriginalVaults, …).suggestedAllocation2028sums the aug-2026 remaining plus every original vault not covered by it. Outputs a status report and the deploy-input JSON for the vault batch; optionaladdress,nameCSV attaches labels.Factory addresses and scan findings are intentionally not in this repo — see the internal runbook.
Docs
docs/TLV-2028-Extension.md— economic parameters (old vs new schedules, forwarder economics change, staking rewards claimable from deployment) and the deployment runbook. Addresses live in the internal runbook.Tests
74 Foundry tests (V1 suites untouched; new
FundForwarderV2.t.sol,FundForwarderFactoryV2.t.sol,DeployFundForwardersBatch.t.sol) + 26 vitest tests,tsc --noEmitand oxlint clean. Scanner verified with live mainnet runs.🤖 Generated with Claude Code