Skip to content

fix(contract): drop the deposit for submit_participant_info - #3940

Merged
barakeinav1 merged 30 commits into
mainfrom
3857-attestation-storage-delta
Jul 28, 2026
Merged

fix(contract): drop the deposit for submit_participant_info#3940
barakeinav1 merged 30 commits into
mainfrom
3857-attestation-storage-delta

Conversation

@pbeza

@pbeza pbeza commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes #3925.
Closes #3857.
Closes #903.

submit_participant_info is no longer #[payable] — it takes no deposit and rejects one if attached, and the contract funds attestation storage from its own balance. A node self-submits with its function-call access key for its first attestation and re-attestations alike.

This fixes onboarding: #3714 introduced a deposit, which a function-call key cannot attach (DepositWithFunctionCall), so nodes could no longer attest.

Why removing the deposit is safe

Pre-#3714 the contract had "charge the caller for a new entry" logic that never collected: it read env::storage_usage() immediately after inserting into the IterableMap, before the deferred write was flushed, so the delta read as 0 and nothing was charged. New entries were already contract-funded, by accident. This makes that intentional.

The cost is bounded by construction — every field of the stored entry is fixed-width except account_id, which NEAR caps at 64 bytes. Its exact size is pinned at 604 bytes (~0.006 NEAR) by submit_participant_info__should_store_exactly_the_pinned_entry_size, under a 0.01 NEAR ceiling, so a schema change that bloats it fails CI.

Dstack entries expire and are reclaimed by clean_invalid_attestations.
Mock::Valid never expires and is never reclaimed — pre-existing (but will be fix in #3785 -) , and the same exposure release/v3.13 shipped with; see #3972.

Behavior after this change

  • any submit (first attestation or re-attestation) -> no deposit; contract funds the storage
  • async (dstack) failure -> submission fails, nothing stored

Follow-ups

Referenced, not closed by this PR:

submit_participant_info kept the whole 0.1 NEAR deposit on success. Re-submissions
that change no stored bytes overpaid, and the flat fee mis-charged when the stored
attestation entry changed size.

Charge only the measured storage delta (storage_byte_cost * bytes_grown) and refund
the excess, keeping the up-front minimum-deposit floor. The floor covers the
worst-case entry, so the charge can never exceed the deposit and the refund cannot
underflow, which is why no insert-then-revert machinery is needed. store_verified_attestation
now flushes so env::storage_usage() reflects the insert before the delta is measured.

Closes #3857
Copilot AI review requested due to automatic review settings July 23, 2026 12:21

Copilot AI 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.

Pull request overview

Adjusts submit_participant_info deposit handling in mpc-contract so successful submissions only retain the storage staking cost for the actual on-chain storage delta and refund any excess, addressing overcharging on identical re-submissions and better tracking entry-size changes.

Changes:

  • Charge submit_participant_info by measured env::storage_usage() delta and refund the remainder (Mock path immediately; Dstack path in resolve_verification).
  • Ensure attestation inserts are flushed (IterableMap::flush) so storage deltas are measurable.
  • Update unit + sandbox tests and interface/docs commentary to reflect “keep delta, refund rest”.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docs/design/attestation-verifier-contract.md Updates design doc wording around deposit behavior (but needs alignment with how the callback receives deposit).
crates/near-mpc-contract-interface/src/deposits.rs Updates deposit constant documentation to reflect refunding excess.
crates/contract/tests/sandbox/utils/mpc_contract.rs Adds a helper to measure “kept deposit” in sandbox tests.
crates/contract/tests/sandbox/tee.rs Updates/extends sandbox tests to assert non-overcharge on first submit and identical resubmit.
crates/contract/src/tee/tee_state.rs Flushes attestation inserts so storage delta measurement works; adds a unit test guarding this.
crates/contract/src/lib.rs Implements “keep storage delta + refund rest” logic and updates docs/tests around minimum deposit sizing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/contract/tests/sandbox/utils/mpc_contract.rs Outdated
Comment thread crates/contract/src/lib.rs Outdated
Comment thread crates/contract/src/lib.rs Outdated
Comment thread docs/design/attestation-verifier-contract.md Outdated
pbeza added 5 commits July 23, 2026 14:33
Make MINIMUM_ATTESTATION_STORAGE_DEPOSIT pub so the public submit_participant_info
doc can link to it (broken_intra_doc_links is denied), matching the sibling
MINIMUM_NODE_MANAGEMENT_DEPOSIT.

Sandbox measure-kept-deposit helper now asserts the balance decreased and covers
gas, using plain subtraction so it fails loudly instead of masking an inverted
balance. Log the unreachable cost-exceeds-deposit branch for observability. Correct
the design-doc bullet: the deposit is forwarded onto the callback receipt, not
stashed.
… test helper

The submit_participant_info doc edits changed the ABI doc strings; regenerate the
snapshot. The sandbox measure-kept-deposit helper uses checked_sub with a panicking
fallback so an inverted balance or gas exceeding spend fails loudly instead of
saturating to zero.
…ion-charge comments

Convert minimum_attestation_storage_deposit__should_cover_worst_case_entry to
an rstest parametrized over the Dstack and Mock attestation variants, so each
reports as its own case. The per-variant assertion (deposit covers each
variant's cost) is equivalent to the previous max-over-variants assertion.

Also tighten the surrounding comments: drop the stale 'submission' framing on
keep_storage_delta_and_refund_rest, name the test that pins the deposit floor
on the unreachable None branch, and shorten the flush comment in
store_verified_attestation.

This comment was marked as outdated.

pbeza added 2 commits July 23, 2026 19:42
…-participants

submit_participant_info required a deposit unconditionally, which broke nodes
signing with a function-call access key: such keys cannot attach any deposit, so
every re-attestation failed at tx validation with DepositWithFunctionCall.

Restore the pre-#3714 conditional: a participant re-attesting an existing entry
attaches no deposit and is charged nothing (so the node's function-call key can
re-attest), while a new entry or a non-participant caller still pays the storage
delta. The client and node stop attaching the deposit; an operator's full-access
key funds a first-time (join) submission.

Adds a sandbox regression test that drives submit_participant_info through a real
function-call access key (zero deposit succeeds; a deposit is rejected).

Closes #3925
…age-delta

# Conflicts:
#	crates/near-mpc-contract-interface/src/client.rs

This comment was marked as outdated.

pbeza added 5 commits July 24, 2026 12:36
… onboarding

The fixed MINIMUM_ATTESTATION_STORAGE_DEPOSIT floor rejected zero-deposit
submissions outright. Restore the pre-#3714 behavior: charge only the measured
storage delta and reject only when the attached deposit is below it. A
participant re-attesting an unchanged entry grows nothing, so it pays nothing
and its function-call key works; a new entry costs the measured delta, funded
by a deposit-capable caller.

In e2e, fund each new participant's first attestation before resharing, signed
with the node's near_signer_key so the stored account_public_key matches the key
the node later uses for key-event votes (an operator key would store the wrong
key and stall resharing). Add MpcNodeState::near_signer_key for this.

Repurpose the worst-case test to assert the entry cost stays under a bounded
ceiling, and regenerate the ABI snapshot.
submit_participant_info takes no deposit; the contract's own balance stakes the
bounded attestation entry. A node self-submits with its function-call access key
for its first attestation and re-attestations alike, fixing onboarding: #3714
required a deposit, which a function-call key cannot attach.

This makes intentional what pre-#3714 did by accident. Pre-#3714 measured the
storage delta right after inserting into the IterableMap, before the deferred
write was flushed, so the delta read as 0 and the caller was never charged.
#3714 added the flush (kept here), which exposed the real cost and started
rejecting the node's zero-deposit submit. Storage is bounded and reclaimed by
clean_invalid_attestations, so the contract-funded cost is bounded and
self-healing.

Removes the charge path, the payable modifiers, deposit forwarding, and the
e2e funding workaround; updates tests and the ABI snapshot.
Once storage is contract-funded, nothing reads env::storage_usage() after the
store, so the explicit flush in store_verified_attestation is dead: IterableMap
flushes on Drop at end-of-call, so persistence is unchanged. Remove the flush,
its comments, and the test that only guarded immediate delta visibility. Also
reword one stale comment referring to the removed caller-funded model.
@near near deleted a comment from claude Bot Jul 24, 2026
pbeza added 3 commits July 24, 2026 13:53
submit_participant_info and resolve_verification no longer take a deposit, so
the attached_deposit(1 NEAR) set in their unit-test contexts is dead context;
remove it. Rename the tee_verifier sandbox refund helper/test to reflect that a
failed submission now costs only gas (no deposit to refund).
…hen/then markers

Revert the sandbox submit_participant_info helper to the MpcContractHandle-based
form (it attaches zero deposit, which the contract-funded method accepts), and
add the Given/When/Then markers to the function-call-key test for consistency
with the rest of the file.
…orage

The 'attached deposit' bullet described the removed delta-charge/refund/
deposit-forwarding design. Attestation storage is now contract-funded, so
submit_participant_info attaches no deposit and none is forwarded or refunded.
Comment on lines +126 to +127
// Zero: the node's function-call key cannot attach a deposit; the contract only charges new entries.
deposit: NearToken::from_yoctonear(0),

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.

Stale as of this PR — the contract no longer "charges new entries"; it funds them from its own balance. Copilot flagged this one too.

Suggested change
// Zero: the node's function-call key cannot attach a deposit; the contract only charges new entries.
deposit: NearToken::from_yoctonear(0),
// The node's function-call key cannot attach a deposit; attestation storage is
// funded by the contract's own balance.
deposit: NearToken::from_yoctonear(0),

- **The submitter's `Attestation::Dstack` payload** — the RTMR3 event log, app-compose, and report-data the post-DCAP checks consume.
- **The submitter's TLS public key** — the callback hashes it with the submitter's account public key and compares to the quote's `report_data` field, proving the enclave produced the quote for this specific submitter.
- **The attached deposit** — covers storage staking on success, refunded to the signer of the original `submit_participant_info` transaction on failure. `env::attached_deposit()` is not visible from the callback receipt, so the value is stashed at submit time and the recipient `AccountId` is the same one used to key the entry (set by `Self::assert_caller_is_signer()`).
- No deposit is stashed: attestation storage is funded by the contract's own balance, so `submit_participant_info` attaches nothing and there is no deposit to forward to the callback or refund.

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.

Three things in this file, in increasing order of scope.

1. This bullet (line 142). It's a negation dropped into a list of things PendingAttestation holds, and the only unbolded entry in it. Reads better as prose right after the list than as a list item.

2. Line 108 is still stale. It reads "...and the deposit stays locked because the refund is part of the cleanup the contract never got around to." Outside this PR's diff so I can't suggest inline, but it needs the same treatment you gave :142.

3. Pre-existing, so your call — this whole section describes a design that never shipped. PendingAttestation doesn't exist in the code:

$ grep -rn "PendingAttestation\|on_attestation_verified\|pending_attestation" crates/ --include=*.rs
(no matches)

submit_participant_info passes VerificationContext directly into a plain .then(resolve_verification) chain — no pending map, no data_id, no promise_yield_create (the one at lib.rs:341 is the generic enqueue_yield_request helper used by sign/CKD/foreign-tx, not this flow). So lines ~136-146 and the "Handling failures" discussion above describe a superseded mechanism. Copilot noticed this too.

Not introduced by this PR, and I wouldn't ask you to rewrite the section here. But AGENTS.md says a design doc describing a superseded design has to be updated, removed, or prominently marked rather than left silently stale — so the cheap out is a Status: superseded by #NNNN banner plus a follow-up issue, rather than editing individual bullets inside a section that no longer matches the code.

@barakeinav1 barakeinav1 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.

Direction is right — contract-funded storage is the only model that works with fc keys, and deferring the rework to #3972 makes sense. Requesting changes on one thing.

Blocker: underflow in the new entry-size cap — #3940 (comment). storage_usage() - before is a signed delta read as unsigned, so any submission that shrinks the entry wraps and permanently pins that account to its larger entry. Two ordinary txs, no TEE. release-contract sets no overflow-checks, so it wraps silently rather than panicking.

More generally, I think a static check is enough here — and better, given the bug. NodeAttestation is bounded by construction (only account_id varies, capped at 64B), so nothing can vary at runtime. The runtime check costs gas on every submit, adds an untested rollback path (r3656731057), and at 0.1 NEAR against a measured 604-byte worst case wouldn't catch bloat anyway (r3656725797).

Suggestion: revert e845e90, keep the unit test, tighten it to ~0.02 NEAR. Closes all three in one move.

@kevindeforth your call, since it counters your runtime-check preference. If it stays, it needs all three: underflow fix, tighter constant, rejection-path coverage.

Also, doc-alignment rule: stale comment at client.rs:126, stale deposit sentence at attestation-verifier-contract.md:108.

Reverts the three commits that introduced it, returning store_verified_attestation
to the state approved in afacfef:

  e845e90 refactor(contract): measure real storage delta for the attestation entry cap
  5a164a8 test(contract): drop tautological entry-cost test, inline single-use helper
  296539d refactor(contract): reuse storage-cost idiom for attestation entry cap

NodeAttestation is bounded by construction - every field is fixed-width except
account_id, which NEAR caps at 64 bytes - so no runtime-variable input exists for
the check to catch. It only guarded against a future schema change, which
submit_participant_info__should_bound_worst_case_entry_cost already covers, and it
would have caught that in production by rejecting live submissions rather than in
CI.

It also carried an underflow: env::storage_usage() - before reads a signed delta as
unsigned, so any submission that shrank the stored entry wrapped and permanently
pinned that account to its larger entry.
With the runtime cap reverted, these tests are the only guard on how much storage
the contract funds per attestation entry.

Pin the exact measured size of each VerifiedAttestation variant (599 bytes Dstack,
604 bytes Mock) so any layout change fails CI and forces a deliberate decision,
rather than silently changing what the contract pays per node. Add
WORST_CASE_ENTRY_BYTES as the pinned worst case, and a doc comment stating what
must be revisited before the numbers are updated.

Tighten WORST_CASE_ENTRY_COST_CEILING from 0.1 to 0.01 NEAR. The old value was
inherited from the flat deposit MINIMUM_ATTESTATION_STORAGE_DEPOSIT, where wide
margin was wanted; as a regression ceiling it left 16x slack and would not have
caught a schema change short of a 16x bloat.

Extract the shared measurement into measure_stored_entry_bytes so both tests
share one definition of the worst case.
barakeinav1
barakeinav1 previously approved these changes Jul 28, 2026

@barakeinav1 barakeinav1 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.

Approving — the blocker is resolved.

  • Runtime entry-size cap removed in 158e8e4. NodeAttestation is bounded by construction, so there was no runtime-variable input for it to catch; it only guarded against a future schema change, and it carried an underflow (storage_usage() - before reading a signed delta as unsigned). The revert lands byte-identical to afacfef, the version @gilcu3 approved.
  • Replaced with stricter build-time checks in 74ea6a0. The exact stored size of each attestation variant is now pinned — 599 bytes Dstack, 604 bytes Mock — so a layout change fails CI instead of surfacing in production by rejecting live submissions. WORST_CASE_ENTRY_COST_CEILING tightened from 0.1 to 0.01 NEAR; the old value was inherited from the flat deposit, where wide margin was wanted, and left 16x slack as a regression ceiling.

Net effect is a strictly tighter guard than the runtime check it replaces, caught earlier and at no gas cost.

Two doc items are still open and I'm happy to push them myself: the stale comment at client.rs:126 and the stale deposit sentence at docs/design/attestation-verifier-contract.md:108. Not blocking.

…age-delta

# Conflicts:
#	crates/near-mpc-contract-interface/src/client.rs
```

_Note_: submit_participant_info - can be called either by the node or by the operator.
_Note_: submit_participant_info - can be called either by the node or by the operator. Attestation storage is funded by the contract's own balance, so submissions attach no deposit and the node's function-call access key works for both a first-time (join) submission and re-attestations.

@barakeinav1 barakeinav1 Jul 28, 2026

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.

our current flow is for node submission only.
IIRC we check the reportdata that contains the hash of public key hash, against who submitted 'submit_participant_info'

- **The submitter's `Attestation::Dstack` payload** — the RTMR3 event log, app-compose, and report-data the post-DCAP checks consume.
- **The submitter's TLS public key** — the callback hashes it with the submitter's account public key and compares to the quote's `report_data` field, proving the enclave produced the quote for this specific submitter.
- **The attached deposit** — covers storage staking on success, refunded to the signer of the original `submit_participant_info` transaction on failure. `env::attached_deposit()` is not visible from the callback receipt, so the value is stashed at submit time and the recipient `AccountId` is the same one used to key the entry (set by `Self::assert_caller_is_signer()`).
- No deposit is stashed: attestation storage is funded by the contract's own balance, so `submit_participant_info` attaches nothing and there is no deposit to forward to the callback or refund.

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.

add follow-up issue here

The 'Handling failures' and 'Contract state changes' sections describe a
yield-resume design that was replaced by the no-yield promise chain:
PendingAttestation, pending_attestations, data_id, the 'already pending' guard
and on_attestation_verified do not exist in the code. #3825 tracks rewriting
them; mark them until it lands so readers are not misled.
The note claimed the call could be made by the node or the operator. The quote's
report_data binds hash(tls_public_key, account_public_key) to
env::signer_account_pk(), and verify_report_data enforces it, so a submission
signed by an operator key fails verification. assert_caller_is_signer rules out
a proxy contract as well.
@gilcu3

gilcu3 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@claude review

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Pull request overview

submit_participant_info drops its 0.1 NEAR flat fee and its #[payable] marker: attestation storage is now funded from the contract's own balance, so a node can self-submit with its function-call access key for both first-time attestation and re-attestation. This unblocks onboarding broken by #3714 (DepositWithFunctionCall). The runtime entry-size cap from earlier revisions was reverted in favour of build-time size pinning, and the deposit constant is removed from the node/interface/test layers.

Changes:

  • Contract: remove MINIMUM_ATTESTATION_STORAGE_DEPOSIT and the deposit gate; drop #[payable] from submit_participant_info and resolve_verification; stop forwarding the deposit onto the callback receipt and stop refunding on failure.
  • Tests: replace deposit-gate tests with zero-deposit store/re-attest tests (in-process + sandbox), add an fc-key sandbox regression test, and pin the exact stored entry size per attestation variant (599 B Dstack, 604 B Mock) under a 0.01 NEAR ceiling.
  • Node/interface: delete SUBMIT_PARTICIPANT_INFO_DEPOSIT_MILLINEAR and ChainSendTransactionRequest::deposit_required; hardcode a zero deposit in create_and_sign_function_call_tx.
  • Docs/snapshots: refresh the ABI and wire-format snapshots; correct two prose spots and add a TODO(#3825) banner over two superseded subsections.

Reviewed changes

Per-file summary
File Description
crates/contract/src/lib.rs Removes the deposit constant, gate, #[payable] markers and deposit forwarding/refund; replaces the flat-fee unit test with pinned entry-size + cost-ceiling rstest cases
crates/contract/tests/inprocess/attestation_submission.rs Drops the deposit-context helper and the insufficient-deposit test; adds zero-deposit new-entry and re-attest tests
crates/contract/tests/sandbox/tee.rs Adds a function-call-key zero-deposit regression test; removes the below-fee rejection test and rewrites the store test for zero deposit
crates/contract/tests/sandbox/tee_verifier.rs Renames refund assertions to gas-only assertions
crates/contract/tests/sandbox/utils/consts.rs Deletes SUBMIT_PARTICIPANT_INFO_DEPOSIT
crates/contract/tests/snapshots/abi__abi_has_not_changed.snap Drops the payable modifiers and updates doc strings
crates/near-mpc-contract-interface/src/client.rs Attaches a zero deposit with an updated rationale comment
crates/near-mpc-contract-interface/src/deposits.rs Removes the deposit constant
crates/near-mpc-contract-interface/src/snapshots/…catalog.snap Wire-format catalog now records 0 NEAR
crates/node/src/indexer/tx_sender.rs Drops the deposit parameter from the submit path
crates/node/src/indexer/tx_signer.rs Hardcodes a zero deposit; removes the too_many_arguments allow
crates/node/src/indexer/types.rs Removes deposit_required
docs/design/attestation-verifier-contract.md Adds a TODO(#3825) banner and rewrites the deposit bullet
docs/securing-mpc-with-tee-design-doc.md Notes that submission must be node-signed and is contract-funded

Findings

Blocking (must fix before merge):

  • docs/running-an-mpc-node-in-tdx-external-guide.md:2074 — the operator runbook still tells first-time joiners to "call submit_participant_info manually with --deposit once". With #[payable] gone, near-sdk emits an unconditional attached_deposit() != 0 guard, so following this instruction now panics with Method submit_participant_info doesn't accept deposit — the runbook actively breaks the flow it is meant to unblock, and the Attached deposit is lower than required error it documents can no longer be produced by this method. Line 2070 also lists "the contract's deposit logic" as a source of on-chain-only errors. Both bullets should go (and the stale [Docs-Missing] Document cost in NEAR tokens for calling submit_participant_info #903 pointer with them, since this PR is the resolution). This is the doc-alignment rule in CLAUDE.md ("Doc drift is a review-blocking issue, not a follow-up") applied to the entry point this PR reshapes.

  • docs/design/attestation-verifier-contract.md:62, :86, :91, :97 — the TODO(#3825) banner is scoped to "This subsection and §Contract state changes below", but it sits inside #### Handling failures, which is a child of ### Submission flow. Lines 62–100 (prose + mermaid) are above the banner and still describe the removed refund path: "Rejected refunds and resumes immediately", "schedule a refund", store on pass / refund on fail, resolve_verification: refund, remove PendingAttestation, remove PendingAttestation, refund. The same is true of ### mpc-contract::submit_participant_info (≈:292, :418, :436, :473, :510, :544, :576, :606 post-diff), which stashes attached_deposit in PendingAttestation and calls refund_deposit. Two of ~15 stale deposit/refund references in this file were fixed; the rest read as current design. Cheapest correct fix, and what @barakeinav1 suggested in r3656738…: a file-level > **Status:** superseded — see #3825 banner at the top instead of per-subsection TODOs.

Non-blocking (nits, follow-ups, suggestions):

  • crates/contract/src/lib.rs:780 — dropping #[payable] does more than make the deposit optional: it turns any attached deposit into a hard panic. That is a genuine API break for any full-access-key caller or operator script that still passes one (see the runbook above). If compatibility matters more than the safety of rejecting stray funds, keeping #[payable] plus refund_to(&account_id, env::attached_deposit()) gives the same fc-key fix without breaking those callers. Whichever way it lands, nothing currently pins it — the old below-fee sandbox test was deleted and no test asserts that a deposit-attaching call is rejected, so the new contract is untested in either direction.

  • crates/contract/src/lib.rs:2326resolve_verification also loses #[payable]. If the deposit-requiring contract has been deployed anywhere, an upgrade landing between verify_quote and its callback will hit the new non-payable resolve_verification with the old receipt's with_attached_deposit(0.1 NEAR) and panic; the submission fails and the 0.1 NEAR settles back on the contract account rather than the submitter. Narrow window and self-healing via node retry, but worth a line in the release notes if feat(contract): async TEE attestation verification, drop dcap-qvl #3714 ever shipped.

  • crates/contract/src/lib.rs:8192,8215submit_participant_info__should_store_exactly_the_pinned_entry_size and submit_participant_info__should_bound_worst_case_entry_cost never call submit_participant_info; they insert straight into tee_state.stored_attestations. Per docs/engineering-standards.md the prefix should name the actual system under test (the stored entry / TeeState), otherwise the names promise coverage of the entry point that isn't there. The doc comment on the first one also says it pins "every attestation variant" while only the two worst-case variants are cased.

✅ Approved on the code; ⚠️ Issues found in the accompanying documentation — the two doc-alignment items above should land in this PR.

…nner

The operator runbook told first-time joiners to call submit_participant_info
with --deposit. submit_participant_info is no longer payable, so near-sdk's
generated guard panics with 'doesn't accept deposit' on any attached amount, and
the 'Attached deposit is lower than required' error it documented can no longer
come from this method. Remove that bullet and the 'contract's deposit logic'
clause above it.

Replace the per-subsection TODO(#3825) in the attestation-verifier design doc
with a file-level Status banner. The subsection marker covered two of the roughly
fifteen stale yield-resume and deposit references; the rest -- the submission-flow
prose, the mermaid diagrams, and the mpc-contract API proposal -- sit outside it
and read as current design.
Both tests insert straight into tee_state.stored_attestations and never call
submit_participant_info, so the old prefix promised coverage of an entry point
they do not exercise. The doc comment also claimed to pin every attestation
variant while only the largest of each kind is cased.
…deposit

Dropping #[payable] makes near-sdk's generated guard panic on any nonzero
deposit rather than merely making it optional. Nothing pinned that: the old
below-fee sandbox test was deleted with the deposit gate and no test asserted
either direction. Assert the rejection and that nothing is stored.
@barakeinav1

Copy link
Copy Markdown
Contributor

Went through all five. Three fixed, one rejected with reasoning, one moot.

docs/running-an-mpc-node-in-tdx-external-guide.md:2074 — the operator runbook still tells first-time joiners to "call submit_participant_info manually with --deposit once"

Fixed in aae164f — removed that bullet, which took the stale #903 pointer with it, plus the "the contract's deposit logic" clause at :2070. #903 is now Closes-linked here, since the answer to "what does this cost the caller" is "nothing".

the TODO(#3825) banner is scoped to "This subsection and §Contract state changes below", but it sits inside #### Handling failures ... Two of ~15 stale deposit/refund references in this file were fixed; the rest read as current design.

Correct, and my mistake — I grepped with too narrow a pattern and only inspected the section I was editing. A plain deposit|refund grep finds references at :62, :86, :91, :97, :121, :147, :289, :292 and throughout :418–:613, most of them above or outside the marker.

Fixed in aae164f by replacing the subsection TODO with a file-level **Status:** line, matching the convention in node-operator-metrics.md. It names what is superseded (the mpc-contract-side flow, state, and API proposal) and what is not (the verifier split, governance, crate layout, the verifier's own API), and keeps TODO(#3825) inside so the CI TODO check still links it.

dropping #[payable] does more than make the deposit optional ... keeping #[payable] plus refund_to(&account_id, env::attached_deposit()) gives the same fc-key fix without breaking those callers

Keeping non-payable, deliberately. Restoring #[payable] means restoring a refund path on every error branch — exactly the machinery this PR deletes — to serve a caller that should not exist. A loud doesn't accept deposit is better operator feedback than a silent refund that makes --deposit look meaningful.

Checked what actually breaks first. Pre-#3714 nodes attached zero:

  • crates/node/src/indexer/tx_signer.rs at ebc7f0d5^: deposit: Balance::from_near(0), hardcoded
  • ChainSendTransactionRequest::deposit_required did not exist
  • the node never used MpcContractHandle (0 matches in crates/node/src/)

So this restores the pre-#3714 node path line-for-line. The 0.1 NEAR in client.rs at ebc7f0d5^ came from #3873 and only reaches sandbox tests, e2e, and tee-context — and no crate depends on tee-context. After this PR every in-repo caller attaches 0.

nothing currently pins it — the old below-fee sandbox test was deleted and no test asserts that a deposit-attaching call is rejected

Fair. Added submit_participant_info__should_reject_an_attached_deposit in 26c343b: attaches 1 yoctoNEAR, asserts the transaction fails with doesn't accept deposit, and asserts nothing is stored.

resolve_verification also loses #[payable]. If the deposit-requiring contract has been deployed anywhere, an upgrade landing between verify_quote and its callback will hit the new non-payable resolve_verification

Moot on both conditions. No contract carrying #3714 is deployed — mainnet and the testnet cluster in #3925 are both on 3.13.2. And the verifier is not a party to the deposit in any case: submit_dstack_attestation attaches NearToken::from_near(0) to verify_quote, and crates/tee-verifier/src/ contains no payable or attached_deposit at all. The deposit only ever travelled from mpc-contract to its own callback.

the tests never call submit_participant_info; they insert straight into tee_state.stored_attestations ... The doc comment on the first one also says it pins "every attestation variant" while only the two worst-case variants are cased.

Both fixed in d8c3579 — renamed to stored_attestation_entry__should_have_the_pinned_size and stored_attestation_entry__should_stay_under_the_cost_ceiling, and the doc comment now reads "the largest variant of each attestation kind".

scripts/check-todo-format.sh requires a colon immediately after the issue
reference; the banner used a period, failing the fast CI checks.

@gilcu3 gilcu3 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.

LGTM

One thing to flag, apparently we no longer need the fail_attestation_submission method, or its usage, because we are no longer accepting deposits that would need roll-back. If you agree, feel free to clean that up here, or open an issue for a follow-up (if none exists)

Comment on lines +8181 to +8188
/// Pins the exact stored size of the largest variant of each attestation kind.
///
/// Do not update these numbers just to make a failing case pass. The contract funds every
/// entry from its own balance, so a size change alters what the contract pays per node, and
/// everything derived from it must be revisited in the same change — today
/// [`WORST_CASE_ENTRY_BYTES`] and [`WORST_CASE_ENTRY_COST_CEILING`].
///
/// TODO(#3972): the flat onboarding deposit will be derived from these sizes too.

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.

nit: this seems a bit LLM verbose, but I don't mind

Comment on lines +3 to +4
**Status:** Partially superseded — TODO(#3825). The `mpc-contract` side shipped as a no-yield promise chain, so every mention below of yield-resume, `pending_attestations` / `PendingAttestation`, `data_id`, the "already pending" guard, `on_attestation_verified`, and the attached deposit and its refunds describes a design that was not built. The verifier-contract split, governance, crate layout, and the verifier's own API are accurate.

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.

what do you mean a no-yield promise chain? Afaik we still have the yields, else how are we handling the cross-contract return call?

@barakeinav1 barakeinav1 Jul 28, 2026

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.

not sure about this, but this is not strictly related to this PR, and we do have (#3825) by @pbeza . so I just put the todo and didn't dig into this.

```

_Note_: submit_participant_info - can be called either by the node or by the operator. Attestation storage is funded by the contract's own balance, so submissions attach no deposit and the node's function-call access key works for both a first-time (join) submission and re-attestations.
_Note_: submit_participant_info must be called by the node itself - the quote's report_data binds hash(tls_public_key, account_public_key) to env::signer_account_pk(), so a submission signed by any other key fails verification. Attestation storage is funded by the contract's own balance, so submissions attach no deposit and the node's function-call access key works for both a first-time (join) submission and re-attestations.

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.

nice fix :)

@barakeinav1
barakeinav1 added this pull request to the merge queue Jul 28, 2026
Merged via the queue into main with commit 3b65a18 Jul 28, 2026
17 checks passed
@barakeinav1
barakeinav1 deleted the 3857-attestation-storage-delta branch July 28, 2026 14:17
barakeinav1 added a commit that referenced this pull request Jul 30, 2026
…ragraph

The background implied contract-funded storage started with #3940. It did not:
the deployed 3.13.0 contains charging code that never collects, because it reads
the storage delta before the write is flushed, and #3714 -- the only version that
genuinely required a deposit -- never shipped. So this has been true of every
deployed version, and #3940 only made it intentional. That matters because it
means the drain is not a regression to be reverted but a gap that was always
there.

Also split the run-on sentence about the security implication into three bullets:
unlimited entries, roughly 7x cost asymmetry, and the contract being unable to
write state at all once its balance is gone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants