Skip to content

test(contract): sandbox coverage + stub verifier for async attestation#3715

Open
pbeza wants to merge 9 commits into
3642-async-attestation-corefrom
3642-async-attestation-tests
Open

test(contract): sandbox coverage + stub verifier for async attestation#3715
pbeza wants to merge 9 commits into
3642-async-attestation-corefrom
3642-async-attestation-tests

Conversation

@pbeza

@pbeza pbeza commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Second half of the #3664 split, stacked on #3714 (the async-attestation feature). Pure test infrastructure and docs, with zero production-code impact.

What changes

  • Adds the test-tee-verifier stub contract: a wire-compatible verify_quote that returns a test-chosen response (Verified / Rejected / Panic) instead of running real dcap-qvl, so sandbox tests can drive every branch of the async flow deterministically.
  • Adds sandbox tests for submit_participant_info's Dstack branches: verifier-not-configured, verifier rejection (refund + nothing stored), no-verdict (the ~200-block yield timeout cleans up), and an out-of-gas resolve_verification (its receipt rolls back atomically and the same timeout cleans up, guarding against a refunded-but-still-pending entry that would wedge the account).
  • Adds the has_pending_attestation sandbox view and the supporting test helpers.
  • Adds the design doc for the verifier-contract flow and refreshes two TDX/localnet docs whose example error messages changed.

Copilot AI review requested due to automatic review settings June 30, 2026 17:17

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

Adds deterministic sandbox-test infrastructure for the async TEE attestation verifier-contract flow by introducing a stub tee-verifier contract, new sandbox helpers, and targeted sandbox tests; also refreshes related docs/design notes to reflect updated error messages and verifier configuration behavior.

Changes:

  • Introduces a new test-tee-verifier stub contract that returns a test-chosen verify_quote outcome (Verified / Rejected / Panic).
  • Adds sandbox utilities + tests covering key submit_participant_info async-attestation branches (verifier not configured, verifier rejection w/ refund, yield-timeout cleanup).
  • Updates the verifier-contract design doc and refreshes TDX/localnet documentation examples for the updated error wording.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/running-an-mpc-node-in-tdx-external-guide.md Updates the example on-chain attestation failure message wording.
docs/localnet/tee-localnet.md Updates localnet example error output to match the new attestation verification failure wording.
docs/design/attestation-verifier-contract.md Refreshes design doc to reflect Option verifier account and the async/yield flow return shape.
crates/test-tee-verifier/src/lib.rs Adds a test-only stub verifier contract implementing verify_quote with canned outcomes.
crates/test-tee-verifier/Cargo.toml Declares the stub contract crate and its ABI/schema feature wiring.
crates/contract/tests/sandbox/utils/mpc_contract.rs Adds sandbox helpers for deposits, pending-attestation checks, and verifier-vote wiring.
crates/contract/tests/sandbox/utils/contract_build.rs Adds build helper for the stub verifier WASM artifact.
crates/contract/tests/sandbox/tee_verifier.rs Adds sandbox tests that deterministically exercise async attestation resolution branches.
crates/contract/tests/sandbox/mod.rs Registers the new tee_verifier sandbox test module.
crates/contract/src/sandbox_test_methods.rs Adds a sandbox-only view method has_pending_attestation.
Cargo.toml Adds crates/test-tee-verifier to the workspace members.
Cargo.lock Locks the new workspace crate dependency graph entry.

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

Comment thread docs/localnet/tee-localnet.md Outdated

```
(ExecutionError("Smart contract panicked: Invalid TEE Remote Attestation.: TeeQuoteStatus is invalid: the allowed mpc image hashes list is empty"
(ExecutionError("Smart contract panicked: Invalid TEE Remote Attestation.: attestation verification failed: the allowed mpc image hashes list is empty"
Comment on lines +52 to 55
pub fn has_pending_attestation(&self, account_id: AccountId) -> bool {
self.pending_attestations.contains_key(&account_id)
}
}
@claude

claude Bot commented Jun 30, 2026

Copy link
Copy Markdown

Pull request overview

Adds sandbox test coverage for the new async submit_participant_info flow (yield-resume + verifier contract) introduced in PR 3714. Adds a test-only test-tee-verifier stub crate whose verify_quote returns a verdict chosen by the test (Verified / Rejected / Panic), three new sandbox tests exercising the Rejected, no-verdict, and verifier-not-configured branches, a has_pending_attestation sandbox view, and small doc-corrections to match renamed error strings.

Changes:

  • New crates/test-tee-verifier crate: a near-sdk contract whose verify_quote returns a StubResponse fixed at init time.
  • New crates/contract/tests/sandbox/tee_verifier.rs with three async tests + deploy_and_trust_stub helper.
  • mpc-contract sandbox view has_pending_attestation(account_id) -> bool.
  • submit_participant_info_with_deposit and vote_tee_verifier_change test helpers; existing submit_participant_info now delegates to the deposit-aware variant.
  • Design-doc rewrite (docs/design/attestation-verifier-contract.md) to reflect Result<(), Error> return + the fail_attestation_submission cleanup-separation pattern, and minor wording updates in two TDX/localnet docs.

Findings

Blocking (must fix before merge):

  • crates/contract/tests/sandbox/tee_verifier.rs:222assert_deposit_refunded uses NearToken::saturating_sub, but CLAUDE.md explicitly forbids checked / saturating arithmetic in test code: 'Raw arithmetic operators (+, -, *, /) are fine in tests — overflow will cause a panic, which is the desired behavior in tests.' This is not just a style nit — it is a real correctness gap: if balance_after > balance_before (e.g. the contract over-refunds, or a future bug accidentally credits the submitter), saturating_sub returns 0, which is < 1 NEAR, and the assertion silently passes. The neighboring crates/contract/tests/sandbox/sign.rs:122 already uses the conventional pattern — drop into as_yoctonear() / as_millinear() and subtract with - so an unexpected refund direction panics the test. Please mirror that pattern.

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

  • crates/contract/src/sandbox_test_methods.rs:52has_pending_attestation lacks the short doc-comment that the two other sandbox view methods in this file carry (which explain why the method exists for tests). A one-liner like '/// Used by yield-resume sandbox tests to assert pending-entry cleanup after rejection / timeout.' would keep the file consistent.
  • crates/contract/tests/sandbox/tee_verifier.rs:39-46StubResponse is duplicated between the stub crate and the test module, with the test mirror relying purely on Borsh tag/field-order parity. The doc comment explains the why (ABI-symbol collision), but there is no compile-time link between the two: a future contributor reordering the variants in test-tee-verifier::StubResponse will silently break encoding here. Consider extracting the enum into a small library crate (or a pub re-export gated so it does not bring in the near-sdk contract symbols) so both sides share a single source of truth. Acceptable as-is given the narrow blast radius, but worth a follow-up.
  • crates/contract/tests/sandbox/tee_verifier.rs:33YIELD_TIMEOUT_BLOCKS = 250 is a magic-number duplicate of the contract's ~200-block window. If the contract ever exposes the actual timeout (via mpc_contract::config or a pub const), prefer sourcing it from there + a small safety margin so the two cannot drift.
  • crates/contract/tests/sandbox/tee_verifier.rs:51-71deploy_and_trust_stub returns the Contract handle but both callers discard it (the binding is dropped). Either return () / anyhow::Result<()>, or use the returned handle (e.g. to assert verifier-side state).
  • crates/contract/tests/sandbox/tee_verifier.rs:42#[expect(dead_code)] on the Verified variant signals an untested code path. The PR description acknowledges this; please file (or link) a follow-up issue for the Verified happy-path test so it is not left as a silent gap.
  • crates/test-tee-verifier/src/lib.rs:18 — the comment 'Match the real verifier's getrandom handling on wasm so the crate links.' paraphrases the cfg-block; per docs/engineering-standards.md 'Write helpful code comments' this kind of comment is the type to drop unless the why is non-obvious. If it is, link to where the real verifier does the same thing; otherwise the cfg-attr stands on its own.
  • crates/test-tee-verifier/src/lib.rs:52-56 — the panicking Default impl with a multi-line justification could collapse to a single env::panic_str(...). The current comment about the #[near(contract_state)] bound is the kind of framework-knowledge content engineering-standards.md flags as unnecessary.

⚠️ Issues found

@pbeza pbeza force-pushed the 3642-async-attestation-tests branch from 0e60532 to 47655a4 Compare July 1, 2026 11:01
@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR includes changes to source code files (crates/contract/src/tee_state.rs, crates/contract/src/sandbox_test_methods.rs), not just tests. The type should likely be feat: instead of test:.
Suggested title: feat(contract): sandbox coverage + stub verifier for async attestation

pbeza added 3 commits July 2, 2026 12:16
Adds the test-tee-verifier stub contract (a wire-compatible verify_quote
that returns a test-chosen response instead of running dcap-qvl) and
sandbox tests exercising the async submit_participant_info branches:
verifier-not-configured, Rejected, and no-verdict (yield timeout). Also
adds the has_pending_attestation sandbox view and the design doc for the
verifier-contract flow.

Builds on the async-verification feature PR.
Adds in-process coverage the reviewer asked for on the async attestation
flow:
- submit_participant_info rejects a Dstack submission with
  VerifierNotConfigured when no verifier is voted in (fails before the
  yield)
- TeeState::revert_dstack_store restores the displaced entry on an update
  and removes a newly-inserted one

The VerificationAlreadyPending / pending-insert and InsufficientDeposit
invariants aren't unit-testable: near_sdk's mock VM does not support
promise_yield_create and does not simulate storage_usage() deltas. Those
paths are exercised by the sandbox tee_verifier tests.
Adds a sandbox test that configures resolve_verification_tera_gas far
below what the post-DCAP work needs, so the callback receipt runs out
of gas and rolls back atomically without resuming the yield. Asserts
the ~200-block timeout branch of on_attestation_verified still cleans
up the pending entry and refunds the deposit, guarding the invariant
that a partial resolve_verification receipt cannot leave a
refunded-but-still-pending entry that wedges the account
@pbeza pbeza force-pushed the 3642-async-attestation-tests branch from 98a7a50 to d3ecae2 Compare July 2, 2026 10:44
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR adds source code (a new test utility crate for the stub verifier and modifies contract source files), not just tests. The type prefix should be feat: instead of test:.

Suggested title: feat(contract): sandbox coverage + stub verifier for async attestation

pbeza added 2 commits July 2, 2026 13:03
- assert_deposit_refunded: use raw subtraction instead of saturating_sub
  so an over-refund (balance_after > balance_before) panics rather than
  clamping to 0 and silently passing (CLAUDE.md forbids saturating
  arithmetic in tests)
- document the has_pending_attestation sandbox view, matching its siblings
- fix a stray period in the example panic string in tee-localnet.md
  (Invalid TEE Remote Attestation: ..., no period before the colon)
Self-review (adversarially verified) surfaced 11 issues; applied the
mechanical + correctness ones:

- init_config was silently dropped on the init_running setup path, so
  the OOG test ran with default gas and re-tested the rejection branch.
  Plumb init_config through init_contract_running.
- assert_deposit_refunded: bound to the gas envelope (~50 mNEAR) so a
  partial refund fails, instead of the loose < 1 NEAR check.
- not-configured test asserts the specific VerifierNotConfigured error
  rather than only is_failure().
- StubResponse: pin Borsh discriminants in a unit test + keep-in-sync
  comments on both mirror declarations.
- rename revert_dstack_store tests to the __should_ form; drop em dashes.
- docs: correct the submission-path panic strings (no "Invalid TEE
  Remote Attestation" prefix), request_verify_foreign_tx ->
  verify_foreign_transaction, resolve_verification sample param, and
  drop the non-existent DstackAttestation::dcap_report reference.
- deny.toml: ignore RUSTSEC-2026-0194/0195 (transitive quick-xml, fixed
  in >=0.41.0) to unblock cargo-deny.

Add a verified_report() test-utils fixture (mints the real report via
verify_dcap_quote) toward the deferred Verified-path coverage. The OOG
test is #[ignore]d with TODO(#3730): forcing a real OOG needs the
allowlist populated so execution reaches the gas-heavy RTMR3 replay;
that governance setup is shared with the pending happy-path test.
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR title type suggestion: This PR adds async attestation verification functionality to the contract (substantial changes to crates/contract/src/tee/tee_state.rs). While it includes sandbox test coverage and a stub verifier, the primary intent is implementing new production features. The type prefix should be feat: instead of test:.

Suggested title: feat(contract): async attestation verification with sandbox coverage

The #[ignore] reason string contained a bare 'TODO(#3730)' token, which
the check-todo-format CI gate rejects (it requires TODO(#N): with a
trailing colon). Reword to 'tracked in #3730'; the canonical
TODO(#3730): comment above the test carries the reference
@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown

PR title type suggestion: Since this PR modifies source files under crates/contract/src/ (not just test files), the type prefix should be refactor: rather than test:

Suggested title: refactor(contract): sandbox coverage + stub verifier for async attestation

pbeza added 3 commits July 3, 2026 11:43
Convert linkable code references in the stub's doc comments to proper
[`...`] intra-doc links (TestTeeVerifier::verify_quote, the StubResponse
variants, VerificationResult::{Verified,Rejected}). Leave unlinkable
references as prose: cross-crate non-deps (dcap_qvl, the real tee-verifier
method), the mpc-contract crate name, and refs inside plain // comments
that can't host intra-doc links.
StubResponse was declared twice (stub contract + test mirror) kept
aligned by a KEEP-IN-SYNC comment and a discriminant-pinning test,
because importing the #[near] stub crate as a dep breaks
cargo test --all-features (duplicate contract-ABI symbol + abi-feature
unification; see PR #3664).

Extract it into a new plain-lib crate test-tee-verifier-types that both
the stub and the contract's test binary depend on. A non-#[near] crate
emits no contract-ABI symbol, so it can be a dev-dep of mpc-contract
without the collision, giving a real single source of truth: the mirror,
the sync comment, and the stub_response_discriminants test are removed.

Verified in nix: cargo test --no-run --all-features -p mpc-contract
links cleanly (the step that regressed in #3664), and the sandbox
tee_verifier suite passes driven through the shared type.
- revert_dstack_store unit tests: reuse create_node_id, compare the whole
  NodeAttestation (add PartialEq/Eq to NodeAttestation, VerifiedAttestation,
  MockAttestation, ValidatedDstackAttestation) instead of a field + a _ match
- add a verified_report() test-utils fixture (mints the real report via
  verify_dcap_quote) and rework the OOG test onto the Verified path; it stays
  #[ignore]d pending the allowlist fixture setup (TODO(#3730))
- compress the sandbox tee_verifier tests: submit_dstack + setup_with_stub +
  assert_submission_cleaned_up helpers remove the repeated preamble/asserts;
  inline the trivial dstack_attestation()/tls_key() wrappers
- switch the sandbox tests to unwrap() style (repo-wide majority) instead of
  Result<()>/?
- assert the exact VerifierNotConfigured message via the error Display
- gate/name has_pending_attestation via method_names::HAS_PENDING_ATTESTATION
- extract TEST_COLLATERAL_STRING / SUBMIT_DEPOSIT consts; doc + intra-doc-link
  cleanups
@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown

PR title type suggestion: Since this PR modifies production source code (crates/contract/src/tee/tee_state.rs, crates/mpc-attestation/src/attestation.rs, etc.), the type should probably be feat: or refactor: instead of test:. Per conventional commits, test: is typically used when only test files are changed. Could you verify this is the correct type?

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