Skip to content

feat(deploy): make tnt-core deployable on Tempo — split StakingDelegationsFacet + ServiceFeeDistributor library (30M per-tx cap)#207

Merged
drewstone merged 3 commits into
mainfrom
feat/tempo-deploy-size
Jul 8, 2026
Merged

feat(deploy): make tnt-core deployable on Tempo — split StakingDelegationsFacet + ServiceFeeDistributor library (30M per-tx cap)#207
drewstone merged 3 commits into
mainfrom
feat/tempo-deploy-size

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

Tempo (chain 42431) caps a transaction at 30M gas and meters contract code deposit ~6.7× mainnet. Contracts near the EIP-170 24KB runtime limit therefore blow the per-tx deploy cap: a FullDeploy to Tempo failed on StakingDelegationsFacet (24,358 B → 33.1M CREATE > 30M). A full size audit found two over-cap contracts in the Tempo path; every other CREATE fits.

Changes

1. Split StakingDelegationsFacet (24,358 → 21,237 B). The single-step redeem path — executeDelegatorUnstakeAndWithdraw + its 9 private helpers + the FixedModeBondlessContext struct (~300 lines) — moves into a new StakingUnstakeWithdrawFacet. Both extend the same StakingFacetBase, so storage and behavior are identical; only that one selector routes to the new facet. Registered at all 22 deploy/test sites + a FacetSize assertion.

2. Shrink ServiceFeeDistributor (24,302 → 19,288 B). Heavy math/bookkeeping (USD conversion, slash factors, the two USD-total + two distribution loops, immediate-distribute, fixed-score delta, expired-lock decay, harvest/debt settlement, transfers) moves into a new deployed library ServiceFeeDistributorLib (public functions → delegatecall-linked, code lives in the library). All 32 state vars move into a single Layout struct at slot 0 in the exact original field order — storage layout verified byte-for-byte identical (hard requirement for this live UUPS contract). Foundry auto-links the library at broadcast; no deploy-script change. (Explorer verification needs --libraries — metadata only.)

Result: every Tempo CREATE now < 30M (heaviest: StakingDelegationsFacet 28.96M, ServiceFeeDistributor 26.7M, InflationPool 27.8M).

Verified

  • Deployed to Tempo Moderato (chain 42431) — ONCHAIN EXECUTION COMPLETE & SUCCESSFUL, 143 txs, ~707M gas / $2.38. Live checks: proxies have code; TNT.symbol() reads; executeDelegatorUnstakeAndWithdraw routes to the new facet and executes its guard (reverts ZeroAmount, not UnknownSelector) — the split works on-chain.
    • tangle 0xff137b9c879c47c28ce389e84501925438ab4cda · staking 0x9484d07899b98384f1d66bd5b2659f3ed346f89e
  • Staking/delegation suite: 223 tests (the 6 selector-routing tests fixed by registering the new facet everywhere). Rewards/distributor suite: 174/174, distributor storage layout byte-identical.
  • No 0.19 ABI break: both facets share StakingFacetBase; ServiceFeeDistributor signatures/events/errors unchanged (contract isn't in the Rust bindings).

drewstone added 2 commits July 7, 2026 17:21
…tor for Tempo 30M per-tx deploy cap

Tempo (chain 42431) caps a tx at 30M gas and meters code deposit ~6.7x, so contracts near
the EIP-170 24KB limit exceed the deploy cap:
- StakingDelegationsFacet (24358B, 33.1M CREATE): split the single-step redeem path
  (executeDelegatorUnstakeAndWithdraw + helpers) into new StakingUnstakeWithdrawFacet; both
  share StakingFacetBase so storage/behavior identical, one selector relocates. Now 21237B/~29M.
- ServiceFeeDistributor (24302B, 33.4M CREATE): extract heavy math into deployed library
  ServiceFeeDistributorLib + storage-struct refactor (layout byte-identical). Now 19288B/~26.5M.
Registered the new facet at all 22 deploy/test sites + FacetSize assertion.

Tests: staking/delegation 223 (6 formerly-UnknownSelector now pass); rewards 174/174 (agent).
WIP: pending fresh Tempo broadcast to confirm every CREATE < 30M.
Full-stack FullDeploy broadcast to Tempo Moderato testnet succeeded (143 txs, ~707M gas /
$2.38) after the split+shrink brought every CREATE under Tempo's 30M per-tx cap.
Verified on-chain: proxies have code, TNT token symbol() reads, and
executeDelegatorUnstakeAndWithdraw routes to the new StakingUnstakeWithdrawFacet.
tangle=0xff137b9c879c47c28ce389e84501925438ab4cda staking=0x9484d07899b98384f1d66bd5b2659f3ed346f89e
tangletools
tangletools previously approved these changes Jul 7, 2026

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 4f029422

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-07T23:38:48Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Value Audit — audit-incomplete

Verdict audit-incomplete
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 7.4s (2 bridge agents)
Total 7.4s

💰 Value — error

value agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: bridge stream ended without value-audit content

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: bridge stream ended without value-audit content

No PR concerns were produced because the value/usefulness agent pass did not complete. Treat this audit as incomplete, not as approval.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260707T234131Z

…join-seed + SFD deploy-once

Boil-the-ocean pre-testnet hardening pass, all folded onto the Tempo-deployability branch:

- SIZE GATE (durable): new test_DeployablesUnderTempoDeployCap asserts every deployable
  contract (26 facets + near-ceiling impls) stays < 21,500 B — Tempo's 30M per-tx cap meters
  code-deposit ~1,372 gas/byte, so the deploy ceiling is ~21,870 B, STRICTER than EIP-170.
  CI now fails before any contract can grow past the point where a Tempo broadcast reverts.
- BEACON SHRINK: ValidatorPodManager 21,760 -> 19,929 B via a delegatecall-linked
  ValidatorPodManagerLib (delegateTo/queueUndelegation/completeUndelegation/_slash extracted),
  storage + behavior byte-identical. Gives beacon real Tempo headroom (was 40 B).
- FAIL-CLOSED REFINEMENT (audit LOW): an operator joining an already-USD-pinned subscription
  while the oracle is off now seeds an identity snapshot (1e18) so its leg bills at raw scale
  instead of being zeroed. Gated strictly on the service being ALREADY USD-pinned so the
  "snapshot present <=> USD-pinned" witness is never corrupted. _subscriptionPinnedInUsd moved
  to shared Base (ServicesLifecycle + Payments are separate facets; one definition avoids drift).
- SFD DEPLOY-ONCE: FullDeploy deployed ServiceFeeDistributor impl twice (inherited
  DeployV2._configureServiceFeeDistributor + FullDeploy's own path, the first orphaned). New
  virtual seam _deploysServiceFeeDistributorExternally() lets FullDeploy skip the inherited
  deploy; base DeployV2 behavior unchanged. Saves one ~26.7M-gas orphaned CREATE.
- Tests: fail-closed branch now covered (join-seed + fail-closed + non-USD invariant, 3 tests);
  the fail-closed test locates its storage slot dynamically (survives reorders) rather than
  hardcoding it.

Verified: src builds; every gated contract < 21,500 B (ValidatorPodManager 19,929, SFD 19,288,
StakingDelegationsFacet 21,237); FacetSize gate 3/3; PaymentsBilling 6/6; behavior sweep green.

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — b07970f8

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-08T01:29:12Z

@tangletools tangletools left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Value Audit — audit-incomplete

Verdict audit-incomplete
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 176.6s (2 bridge agents)
Total 176.6s

💰 Value — error

value agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: bridge stream ended without value-audit content

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: bridge stream ended without value-audit content

No PR concerns were produced because the value/usefulness agent pass did not complete. Treat this audit as incomplete, not as approval.


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260708T013355Z

@drewstone drewstone merged commit 23d49bc into main Jul 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants