Skip to content

ENG-484: add custodial NAV report timestamps#529

Open
carrion256 wants to merge 2 commits into
devfrom
codex/eng-484-reported-at
Open

ENG-484: add custodial NAV report timestamps#529
carrion256 wants to merge 2 commits into
devfrom
codex/eng-484-reported-at

Conversation

@carrion256

@carrion256 carrion256 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add reported_at(asset) -> Result<Option<u64>, AdapterError> to the custodial adapter.
  • Stamp the current ledger timestamp only after a successful explicit set_reported_assets call.
  • Keep allocation, supply, return, and withdrawal lifecycle mutations from refreshing the report timestamp.
  • Preserve existing nonce and absolute-report replay protections.
  • Bump templar-soroban-custodial-adapter to 1.1.0 and update its lockfile entry.
  • Add operator docs/recipes plus a self-contained optimized-Wasm ABI and execution gate.

Why

Custodial NAV reports exposed their value and nonce but not when the last explicit off-chain report
was accepted. Curators and monitors therefore could not measure report freshness independently of
normal custody lifecycle activity.

Compatibility

  • Existing deployed adapter state requires no migration.
  • A missing timestamp key returns None until the next successful explicit report.
  • Failed or replayed reports do not update the timestamp.
  • This PR does not change adapter administration or deploy a replacement adapter.

Stack

This is PR 1 of 3 and targets dev.

  1. This PR: custodial NAV report timestamps.
  2. PR #530: runtime version/capability discovery and curator-proxy replacement.
  3. PR #524: explicit adapter-admin guardrails and the Gami rollout checklist.

Validation

  • cargo test -q -p templar-soroban-custodial-adapter — 42 passed.
  • just -f contract/vault/soroban/justfile verify-custodial-adapter-release-abi
    • built the optimized adapter;
    • checked the exact reported_at(address) -> Result<Option<u64>, AdapterError> interface;
    • invoked the real Wasm artifact and verified its initial result is None.
  • cargo fmt --all -- --check
  • git diff --check
  • Optimized adapter: 11981 bytes, hash
    03c1c749613d67d5ed460bc4fbb12eedb1a043068665a295fbefb774588a796b.

Issue

ENG-484


This change is Reviewable

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 506e4321-ca77-47ea-8cbc-b2afc8e6b95d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/eng-484-reported-at

Comment @coderabbitai help to get the list of available commands.

@carrion256
carrion256 force-pushed the codex/eng-484-reported-at branch from 28cbd4e to 11ba5c0 Compare July 24, 2026 14:24

env.as_contract(&contract_id, || {
store_reported_assets(&env, &asset, 50);
store_report_nonce(&env, &asset, 7);
asset.clone(),
9,
11,
2,
@carrion256
carrion256 marked this pull request as ready for review July 24, 2026 14:56

@coderabbitai coderabbitai Bot 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contract/vault/soroban/custodial-adapter/src/lib.rs`:
- Around line 1332-1356: Update
failed_explicit_report_does_not_change_reported_at to first submit a successful
report with the initial nonce, then advance the ledger timestamp and replay that
same nonce with otherwise valid input. Assert the replay returns the expected
nonce-validation error and reported_at remains at the original timestamp,
ensuring replay rejection is checked after expected_current validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: abb78618-4589-4776-9736-aa0342331aeb

📥 Commits

Reviewing files that changed from the base of the PR and between 92d0c33 and 11ba5c0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .github/workflows/test.yml
  • contract/vault/soroban/AGENTS.md
  • contract/vault/soroban/README.md
  • contract/vault/soroban/custodial-adapter/Cargo.toml
  • contract/vault/soroban/custodial-adapter/src/lib.rs
  • contract/vault/soroban/custodial-adapter/tests/artifact_reported_at.rs
  • contract/vault/soroban/justfile
  • contract/vault/soroban/scripts/check_custodial_adapter_release_abi.py

Comment on lines +1332 to +1356
fn failed_explicit_report_does_not_change_reported_at() {
let env = Env::default();
env.mock_all_auths();
let (contract_id, _admin, vault, _custodian, asset) = setup_adapter(&env);

env.ledger().set_timestamp(100);
set_reported_assets_for(&env, &contract_id, vault.clone(), asset.clone(), 10);

env.ledger().set_timestamp(200);
env.as_contract(&contract_id, || {
assert_eq!(
CustodialAdapterContract::set_reported_assets(
env.clone(),
vault,
asset.clone(),
9,
11,
2,
),
Err(AdapterError::InvalidInput)
);
});

assert_eq!(reported_at(&env, &contract_id, &asset), Some(100));
}

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Cover the replay-specific timestamp invariant.

This test fails on expected_current before nonce validation. Add a literal replay after advancing the ledger timestamp and assert reported_at remains unchanged.

Suggested test
+    #[test]
+    fn replayed_report_does_not_change_reported_at() {
+        let env = Env::default();
+        env.mock_all_auths();
+        let (contract_id, _admin, vault, _custodian, asset) = setup_adapter(&env);
+
+        env.ledger().set_timestamp(100);
+        set_reported_assets_for(&env, &contract_id, vault.clone(), asset.clone(), 0);
+
+        env.ledger().set_timestamp(200);
+        env.as_contract(&contract_id, || {
+            assert_eq!(
+                CustodialAdapterContract::set_reported_assets(
+                    env.clone(), vault, asset.clone(), 0, 0, 1,
+                ),
+                Err(AdapterError::InvalidInput)
+            );
+        });
+        assert_eq!(reported_at(&env, &contract_id, &asset), Some(100));
+    }

As per coding guidelines, “Write comprehensive unit tests for new non-trivial logic.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn failed_explicit_report_does_not_change_reported_at() {
let env = Env::default();
env.mock_all_auths();
let (contract_id, _admin, vault, _custodian, asset) = setup_adapter(&env);
env.ledger().set_timestamp(100);
set_reported_assets_for(&env, &contract_id, vault.clone(), asset.clone(), 10);
env.ledger().set_timestamp(200);
env.as_contract(&contract_id, || {
assert_eq!(
CustodialAdapterContract::set_reported_assets(
env.clone(),
vault,
asset.clone(),
9,
11,
2,
),
Err(AdapterError::InvalidInput)
);
});
assert_eq!(reported_at(&env, &contract_id, &asset), Some(100));
}
fn failed_explicit_report_does_not_change_reported_at() {
let env = Env::default();
env.mock_all_auths();
let (contract_id, _admin, vault, _custodian, asset) = setup_adapter(&env);
env.ledger().set_timestamp(100);
set_reported_assets_for(&env, &contract_id, vault.clone(), asset.clone(), 10);
env.ledger().set_timestamp(200);
env.as_contract(&contract_id, || {
assert_eq!(
CustodialAdapterContract::set_reported_assets(
env.clone(),
vault,
asset.clone(),
9,
11,
2,
),
Err(AdapterError::InvalidInput)
);
});
assert_eq!(reported_at(&env, &contract_id, &asset), Some(100));
}
#[test]
fn replayed_report_does_not_change_reported_at() {
let env = Env::default();
env.mock_all_auths();
let (contract_id, _admin, vault, _custodian, asset) = setup_adapter(&env);
env.ledger().set_timestamp(100);
set_reported_assets_for(&env, &contract_id, vault.clone(), asset.clone(), 0);
env.ledger().set_timestamp(200);
env.as_contract(&contract_id, || {
assert_eq!(
CustodialAdapterContract::set_reported_assets(
env.clone(), vault, asset.clone(), 0, 0, 1,
),
Err(AdapterError::InvalidInput)
);
});
assert_eq!(reported_at(&env, &contract_id, &asset), Some(100));
}
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 1349-1349: Hard-coded cryptographic value
This hard-coded value is used as a nonce.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contract/vault/soroban/custodial-adapter/src/lib.rs` around lines 1332 -
1356, Update failed_explicit_report_does_not_change_reported_at to first submit
a successful report with the initial nonce, then advance the ledger timestamp
and replay that same nonce with otherwise valid input. Assert the replay returns
the expected nonce-validation error and reported_at remains at the original
timestamp, ensuring replay rejection is checked after expected_current
validation.

Source: Coding guidelines

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