Contract signatures and oracles: context, freshness and units
Test signed-action context and price-consumer assumptions with local keys, mock feeds and explicit negative controls.
- 01Record trust contract
- 02Prove valid baseline
- 03Vary one binding
- 04Vary feed metadata
- 05Calculate consequence
At each step: retain the control → record the observation → continue only while the next boundary is understood.
Used in these playbooks#
Setup and invariants#
Create two locally deployed verifier instances, a local-only signer, two ordinary actors and a mock feed whose state you can vary. Write the expected signed action as a tuple: signer, action type, recipient, amount, nonce, deadline, chain and verifying contract. Record which fields the application intentionally binds. The invariant is: a signed instruction permits only its intended action and replay policy. EIP-712 defines typed hashing and domain separation; replay protection remains the application's responsibility. EIP-712
For price consumers, record base/quote currency, feed decimals, token decimals, supported price range, freshness policy and dependency-failure behavior. The invariant is: an accepted observation has the meaning and validity required for the resulting economic decision. Chainlink exposes round metadata and timestamps; consumers must interpret them in their own integration. Chainlink data-feed API
Controlled procedure#
- Prove both baselines. A valid local signature executes its harmless ledger action once. A fresh positive-price feed produces the expected collateral or quote calculation. Verify an invalid signature and an explicitly disallowed stale observation are rejected, where that policy is part of the product's contract.
- Vary one signature dimension. After the valid action, replay exactly the same bytes. Then use fresh baselines to vary only the recipient, amount, action type, verifier instance, chain context or expired timestamp. Record the recovered signer and nonce-consumption point. A reverting transaction normally rolls back its state; prove whether the replay state survives a successful action rather than assuming a pre-revert write consumed the nonce.
- Check alternate signer paths. If contract wallets are supported, compare an owned wallet returning the ERC-1271 success value
0x1626ba7e, a wallet returning a different value, and a reverting wallet. A non-reverting call alone does not establish a valid signature. ERC-1271 validity can depend on current contract state: change the authorized local signer after a valid baseline and repeat only where the consumer promises current authorization. Record validation at execution versus an intentionally earlier irrevocable authorization. Do not classify unsupported signature formats as a vulnerability. Reuse identity and session binding for off-chain issuance. ERC-1271. - Vary feed metadata. Starting from a valid response, test an old timestamp, zero or negative answer where the feed type can produce it, changed decimals, failed feed call and mismatched quote currency. Keep all other fields realistic. Demonstrate the consumer's accepted result and economic effect; an artificial impossible feed response is a robustness result until reachable conditions are shown.
- Test L2 recovery if applicable. Model sequencer down, just restarted and beyond the configured grace period separately. This is integration-specific: not every chain or feed uses identical checks. Chainlink's L2 guidance explains uptime signals and post-recovery grace periods. L2 sequencer feeds
- Calculate the consequence. Compare collateral, debt, quote or liquidation outcome with an independently calculated expectation. Include price units, rounding, capital, timing, fees and user constraints. A missing named check without a reachable incorrect decision remains a review lead.
Local cheat sheet and case matrix#
Implement these names in your reviewed local Foundry harness; they are not scanner features. The signer key must be generated or hard-coded solely for this disposable local fixture.
rg -n 'ecrecover|nonces|deadline|DOMAIN_SEPARATOR|latestRoundData|decimals' src
forge test --match-test '^test_ValidSignature$' -vvvv
forge test --match-test '^test_ReplayRejected$' -vvvv
forge test --match-test '^test_WrongDomainRejected$' -vvvv
forge test --match-test '^test_StaleFeedRejected$' -vvvv
forge test --match-test '^test_PriceUnitInvariant$' -vvvv
--match-test selects target-specific tests and trace verbosity helps identify the accepted transition. Use preinstalled compiler/dependencies and no RPC/fork/FFI setup for these local fixtures. Foundry test reference
| Comparison | Hold constant | Vary |
|---|---|---|
| Replay | Signature, actor, verifier | Prior successful consumption |
| Domain | Message and signature | Local verifier or chain context |
| Freshness | Price and units | Observation timestamp |
| Unit conversion | Economic value | Supported decimal representation |
Evidence and checklist#
- Exact message schema, domain and successful baseline are saved.
- Only one context field changes in each negative case.
- Mock feed deviations are separated from reachable attacker powers.
- Nonces, timestamps, units and downstream decisions appear in evidence.
- No real signatures, transactions or funds are used.
Vulnerability reverse references#
- Signed contract action can be replayed unexpectedly — A successfully consumed authorization remains reusable despite a one-use or otherwise bounded execution contract.
- Signed action does not bind its intended context — A valid signature is accepted for another recipient, action, verifier or chain context contrary to the authorization contract.
- Oracle freshness or dependency state permits an invalid decision — A consumer accepts an observation outside its required freshness or availability conditions and makes an incorrect economic decision.
- Oracle denomination or decimal conversion is incorrect — The consumer mixes quote currencies, token precision or feed precision and assigns the wrong economic value.
Nearby shared procedures#
Object, function, and property authorization · Authentication, recovery, federation, and session lifecycle · Trace input into queries, commands, and templates · Server-side fetching and network trust boundaries · Browser execution, cross-origin reads, and forged actions · Uploads, downloads, paths, archives, and XML · Workflow invariants, replay, idempotency, and races · REST, GraphQL, gRPC, WebSocket, and SSE permission parity