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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ script/tanglepod-cli/tanglepod-cli

# Local developer scratch
.local/
.evolve/

# Claude Code
.claude/
Expand Down
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,11 @@ scripts/local-env/start-local-env.sh
- O(1) share-based accounting for delegations
- Events over storage for gas optimization
- Fuzz testing required for financial logic

## Commit Messages

- No `Co-Authored-By` lines ever — not in any commit, in any repo

## Pull Requests

- Before opening any PR, always resolve merge conflicts locally first: create a merge branch, run `git merge <target>`, fix all conflicts, verify lint/tests/build pass, push the resolved branch, THEN open the PR. Never open a PR that has conflicts.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions bindings/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.12.0] - 2026-05-08

### Changed (BREAKING)

- `QuoteDetails` EIP-712 typehash now includes `address requester` as the first
field. Previously `requester` lived on the struct but was excluded from the
typehash, so an attacker who observed a signed quote in the mempool could
flip `details.requester` to themselves and the operator's signature still
recovered correctly — the binding check at the protocol layer was therefore
cosmetic. Off-chain signers MUST now hash `requester` as the first member of
`QuoteDetails`. Existing pre-fix signatures are invalid against the new
typehash. The full updated string is:
`"QuoteDetails(address requester,uint64 blueprintId,uint64 ttlBlocks,uint256 totalCost,uint64 timestamp,uint64 expiry,uint8 confidentiality,AssetSecurityCommitment[] securityCommitments,ResourceCommitment[] resourceCommitments)"`
- `ITangleSlashing` event declarations realigned with what the protocol
actually emits from `SlashingLib`: `SlashProposed` is now 8 fields (was 4),
`SlashExecuted` is now 4 fields (was 3), and the previously-missing
`SlashDisputed`, `SlashCancelled`, `SlashConfigUpdated` events are now
declared. Rust consumers wired to `ITangleSlashing` could not decode any
emitted slash event before this fix; they can now.

### Added

- Permissionless `expireServiceRequest(uint64)` is wired to the proxy. The
declaration shipped in 0.11.2 but the corresponding selector was never
registered on `TangleServicesFacet.selectors()`, so calls routed through the
unknown-selector fallback. Off-chain callers can now reach the function via
the canonical `ITangleServices` ABI.

### Fixed (security)

- `proposeSlash` and `disputeSlash` now carry `nonReentrant`; only `executeSlash`,
`executeSlashBatch`, and `cancelSlash` were guarded before. `proposeSlash`
also rejects `bytes32(0)` evidence so off-chain monitors keying off non-zero
evidence don't see silently-zero entries.
- Disputed slashes now apply the same 15-second `TIMESTAMP_BUFFER` as Pending
slashes. Previously a sequencer / proposer with timestamp influence could
sandwich the dispute deadline tick; the operator had no symmetric protection.
- `approveService` now rejects requests past the expiry grace window — operators
could otherwise race `expireServiceRequest` and quietly activate a stale
request the requester thought they could clean up.
- `requestService*` now rejects duplicate operator entries. With duplicates,
`req.operatorCount` exceeds the unique approver count, so
`approvalCount == operatorCount` was unreachable and the request could only
be cleaned up via `expireServiceRequest`.
- `terminateService` and `terminateServiceForNonPayment` now carry
`nonReentrant`. State writes already preceded external calls (CEI), but
defense-in-depth aligns these entrypoints with the rest of the lifecycle.
- All operator-exit entrypoints (`scheduleExit`, `executeExit`, `forceExit`,
`leaveService`, `forceRemoveOperator`) now reject when the service is no
longer Active. Previously a stale operator could continue to fire exit paths
on a Terminated service, double-decrementing counts and emitting
`OperatorLeftService` for a dead service.
- `_distributePaymentWithEffectiveExposure` now reverts (instead of silently
retaining funds) when there are zero active operators at billing time. The
developer/treasury split would still pay out while the operator+staker pool
(default 60%) remained stuck in the contract with no path back. Service
owners who lose all operators can recover escrow via `terminateService` →
`withdrawRemainingEscrow`.
- `fundService`, `billSubscription`, and `billSubscriptionBatch` now respect
the global pause. Reward / refund claim paths remain unguarded so users can
always exit.
- `OperatorStatusRegistry.registerOperator` now resets all per-(serviceId,
operator) heartbeat / metrics state on (re-)register. Without this, an
operator who deregistered carried stale heartbeat data forward — and
`isHeartbeatCurrent` could return true before any new heartbeat landed.
- `LiquidDelegationVault.requestRedeem` rejects `controller == address(0)` so
filing a request under that controller no longer permanently locks the
redeemer's burned shares.

### Removed

- Deleted unused `src/exposure/` module (`ExposureManager`, `ExposureCalculator`,
`ExposureTypes`, `IExposureManager`) and its self-contained `test/exposure/`
suite. The actual exposure logic lives in `src/core/PaymentsEffectiveExposure.sol`
and is exercised by `test/payments/`. The orphan module was an audit-burden
divergence risk for the same bps math.
- Deleted `src/interfaces/IStreamingPaymentAdapter.sol` (also defining
`ISuperfluidAdapter`, `ISablierAdapter`, `IPaymentAdapterRegistry`). None of
these interfaces are implemented or referenced anywhere; only
`IStreamingPaymentManager` is wired in.

## [0.11.3] - 2026-05-06

### Changed
Expand Down
2 changes: 1 addition & 1 deletion bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tnt-core-bindings"
version = "0.11.3"
version = "0.12.0"
edition = "2021"
rust-version = "1.81"
description = "Rust bindings for TNT Core Solidity contracts (Tangle staking protocol)"
Expand Down
2 changes: 1 addition & 1 deletion bindings/TNT_CORE_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
aa511c20dd1d5fab98c72aa617096ab12143f696
93f8398b43dea3b7a880ccb912cc785811cb254b
2 changes: 1 addition & 1 deletion bindings/abi/ITangle.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/ITangleFull.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/ITangleSlashing.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion bindings/abi/OperatorStatusRegistry.json

Large diffs are not rendered by default.

Loading
Loading