Skip to content

Score Storage and Data Model

RunTimeAdmin edited this page Oct 29, 2026 · 13 revisions

Score Storage and Data Model

**Referenced Files in This Document** - [SigvaraReputation.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReputation.sol) - [scoring.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/scoring.js) - [index.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/index.js) - [chain.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/chain.js) - [merkle.test.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/merkle.test.js) - [epoch-policy.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/epoch-policy.js) - [reputation.html](https://github.com/RunTimeAdmin/Sigvara/blob/main/site/docs/reputation.html) - [whitepaper.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/whitepaper.md) - [demo.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/site/assets/demo.js) - [SigvaraReputation.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/SigvaraReputation.t.sol) - [SigvaraReader.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReader.sol) - [InflowGuard.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/InflowGuard.sol) - [TimelockedGrants.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/TimelockedGrants.sol) - [score-caps.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/score-caps.js)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

This document explains the on-chain score storage and data model used by the reputation system. It focuses on how six factor scores are stored, validated, proposed, matured, and verified through evidence roots. The contract is a verified store: scoring computation happens off-chain in the oracle network, while the contract enforces caps, lifecycle state, maturity, and verifiable evidence.

The key concepts are:

  • ReputationData: the six-factor score plus a finalization timestamp.
  • PendingScore: the proposal lifecycle wrapper with a timestamp, evidence root, proposer and a finalizable time fixed at proposal.
  • Storage mappings for finalized reputations, pending proposals, matured anchors, and evidence roots.
  • Scoring caps that ensure the total never exceeds 100 points.
  • Maturity mechanics that delay spendable score growth while allowing immediate drops.

This page describes the contract on main. The Arc testnet proxy has run it since 26 October 2026. The September implementation it replaced had no proposer or finalizableAt in PendingScore, read the live challengeWindow at finalization, put no bounds on the window, checked the maturity rate only against zero, and returned a score at or below its anchor whole after an operator handover. A proposal made before the upgrade reads zero in the two new fields and is judged against the live window.

  • Gas optimization strategies embedded in the data layout and read/write paths.

Project Structure

The score storage and data model live primarily in the reputation contract, with supporting oracle logic computing factors and committing evidence roots. Tests and documentation provide examples and constraints.

graph TB
subgraph "On-chain"
SR["SigvaraReputation.sol"]
SI["SigvaraIdentity.sol"]
end
subgraph "Off-chain Oracle"
SC["scoring.js"]
IDX["index.js"]
CH["chain.js"]
MKT["merkle.test.js"]
EP["epoch-policy.js"]
end
subgraph "Docs & Examples"
RP["reputation.html"]
WP["whitepaper.md"]
DM["demo.js"]
TST["SigvaraReputation.t.sol"]
end
IDX --> CH
CH --> SR
SC --> IDX
MKT --> IDX
EP --> IDX
RP --> SR
WP --> SR
DM --> SC
TST --> SR
SR --> SI
Loading

Diagram sources

Section sources

Core Components

This section documents the two primary data structures and their mathematical relationships.

ReputationData

ReputationData stores the six factor scores and a finalization timestamp:

  • feeScore: up to 20 points, measuring settled fee volume or an attestation-count proxy when payment verification is off.
  • successScore: up to 15 points, measuring successful outcomes smoothed by a Bayesian prior.
  • ageScore: up to 30 points, measuring tenure based on span of paid activity and recency.
  • externalScore: up to 25 points, normalized ERC-8004 cross-protocol feedback.
  • communityScore: up to 5 points, derived from flags with decay.
  • propagationScore: up to 5 points, inherited trust from counterparties that are themselves scored agents.
  • lastUpdated: block timestamp of the last finalized write (a slash also stamps it when it zeroes the score).

Mathematical relationship:

  • Total earned score = sum of all six factors.
  • Each factor is capped independently; the sum is constrained to at most 100.

Storage pattern:

  • Finalized scores are stored per agent identifier (didHash) in a mapping keyed by bytes32.
  • The struct includes a finalization timestamp. Maturity is anchored separately, in maturedScore and maturedAt.
  • Nothing on chain ages a score, so lastUpdated is the only age signal. SigvaraReader reports it as scoreUpdatedAt, and its isTrustedFresh adds a limit on that age to isTrusted.

Section sources

PendingScore

PendingScore wraps a proposed ReputationData with lifecycle metadata:

  • data: nested ReputationData containing the six factors.
  • proposedAt: timestamp when the proposal was created.
  • exists: boolean flag indicating whether a proposal slot is occupied.
  • evidenceRoot: Merkle root committing to the set of payments used to compute the score.
  • proposer: the address that made the proposal. A proposal that was already pending when the field was added reads address(0) and is treated as live.
  • finalizableAt: when the proposal can first be finalized and the last moment it can still be rejected, fixed at proposal time as proposedAt plus the challenge window in force then. A proposal that was already pending when the field was added reads zero and is judged against the live window.

Lifecycle behavior:

  • A proposal opens a challenge window starting at proposedAt and ending at finalizableAt. Changing challengeWindow later moves no proposal already pending.
  • If unchallenged, anyone can finalize after the window elapses, provided its proposer still holds ORACLE_ROLE and, when an operator set is configured, is still an active operator in it. Otherwise finalization reverts with ProposerNotAuthorized.
  • A fresh proposeReputation replaces any still-pending proposal and restarts the window. proposeIfEmpty replaces one only when its proposer has lost its authority.
  • Evidence root is held with the proposal and only becomes live upon finalization.

Storage pattern:

  • Pending proposals are stored per didHash in a mapping keyed by bytes32.
  • The struct is appended rather than inserted into existing slots to preserve upgrade compatibility.

Section sources

Architecture Overview

The reputation system uses an optimistic scoring model:

  • Off-chain oracle computes six factors and proposes them on-chain.
  • On-chain contract validates caps, identity, stake, and operator bonding.
  • A challenge window allows committee rejection before finalization.
  • Finalization writes the earned score, evidence root, and maturity anchor.
  • Consumers read the matured score, which climbs toward the earned score over time.
sequenceDiagram
participant Oracle as "Oracle (index.js)"
participant Chain as "Chain (chain.js)"
participant Contract as "SigvaraReputation.sol"
participant Identity as "SigvaraIdentity.sol"
participant Consumer as "Consumer"
Oracle->>Oracle : Compute six factors (scoring.js)
Oracle->>Chain : Build evidence root (merkle)
Chain->>Contract : proposeReputation(didHash, data, evidenceRoot)
Contract->>Identity : Validate agent registered, not slashed, bonded
Contract->>Contract : Validate each factor cap
Contract->>Contract : Store PendingScore(proposedAt, exists, evidenceRoot, proposer, finalizableAt)
Note over Contract : Challenge window starts
Consumer->>Contract : finalizeReputation(didHash) after window
Contract->>Contract : Write ReputationData + evidenceRoot
Contract->>Contract : Set maturedScore anchor + maturedAt
Contract-->>Consumer : ReputationUpdated event
Consumer->>Contract : getTotalScore(didHash)
Contract-->>Consumer : Matured score (climbs toward earned)
Loading

Diagram sources

Detailed Component Analysis

Storage Layout and Mappings

The contract maintains four primary storage areas:

  • reputations: mapping from didHash to finalized ReputationData.
  • pendingScores: mapping from didHash to PendingScore.
  • maturedScore and maturedAt: mappings from didHash to matured anchor values and timestamps.
  • evidenceRoots: mapping from didHash to Merkle root of committed evidence.

Additional configuration:

  • challengeWindow: seconds a proposal remains open for challenges. The contract holds it between 1 hour and 7 days, in the initializers as well as the setter.
  • identityRegistry: pointer to the identity registry for validation.
  • maturityRatePerDay: points per day that matured score climbs toward earned score. Above 0 and at most 100.
  • operatorBond: optional bonded operator registry for oracle checks. On main, setOperatorBond requires UPGRADER_ROLE.

The guardian’s pause state (InflowGuard) is not in the contract’s own layout. It sits in an ERC-7201 namespaced slot, so adding it to a live proxy shifts no existing variable.

classDiagram
class SigvaraReputation {
+mapping bytes32 => ReputationData reputations
+mapping bytes32 => PendingScore pendingScores
+uint256 challengeWindow
+SigvaraIdentity identityRegistry
+uint256 maturityRatePerDay
+mapping bytes32 => uint8 maturedScore
+mapping bytes32 => uint256 maturedAt
+mapping bytes32 => bytes32 evidenceRoots
+IOperatorSet operatorBond
}
class ReputationData {
+uint8 feeScore
+uint8 successScore
+uint8 ageScore
+uint8 externalScore
+uint8 communityScore
+uint8 propagationScore
+uint256 lastUpdated
}
class PendingScore {
+ReputationData data
+uint256 proposedAt
+bool exists
+bytes32 evidenceRoot
+address proposer
+uint256 finalizableAt
}
SigvaraReputation --> ReputationData : "stores finalized"
SigvaraReputation --> PendingScore : "stores proposals"
SigvaraReputation --> SigvaraIdentity : "validates agent"
Loading

Diagram sources

Section sources

Scoring Caps and Constraints

The six factors have independent maximums that sum to 100:

  • MAX_FEE_SCORE = 20
  • MAX_SUCCESS_SCORE = 15
  • MAX_AGE_SCORE = 30
  • MAX_EXTERNAL_SCORE = 25
  • MAX_COMMUNITY_SCORE = 5
  • MAX_PROPAGATION_SCORE = 5

Constraints enforced:

  • Each factor is validated against its cap during proposal.
  • Total earned score is asserted to be at most 100.
  • Fuzz tests verify that total never exceeds 100 across valid inputs.
  • The oracle keeps the same six caps in one place, oracle/score-caps.js, and a test reads them from SigvaraReputation.sol, so a reweight in the contract that is not copied across fails the oracle suite.
flowchart TD
Start([Proposal Input]) --> CheckFee["Check feeScore <= 20"]
CheckFee --> CheckSuccess["Check successScore <= 15"]
CheckSuccess --> CheckAge["Check ageScore <= 30"]
CheckAge --> CheckExternal["Check externalScore <= 25"]
CheckExternal --> CheckCommunity["Check communityScore <= 5"]
CheckCommunity --> CheckPropagation["Check propagationScore <= 5"]
CheckPropagation --> SumTotal["Sum all six factors"]
SumTotal --> AssertTotal{"Total <= 100?"}
AssertTotal --> |Yes| Accept["Accept Proposal"]
AssertTotal --> |No| Reject["Reject with ScoreOutOfRange"]
Loading

Diagram sources

Section sources

Evidence Roots and Merkle Proofs

Evidence roots commit to the set of payments used to compute the score:

  • The oracle builds a Merkle tree from payment events.
  • The root is included with the proposal and stored in evidenceRoots upon finalization.
  • Verifiers can prove individual payments belong to the committed set using Merkle proofs.

Key behaviors:

  • Empty evidence sets produce a zero hash root.
  • Any change in payment fields changes the root.
  • Omission detection is possible because dropping a payment changes the root.
  • Block numbers are intentionally excluded from leaf hashing to maintain reproducibility.
sequenceDiagram
participant Oracle as "Oracle"
participant Merkle as "Merkle Tree"
participant Contract as "SigvaraReputation.sol"
participant Verifier as "Verifier"
Oracle->>Merkle : Build tree from payment events
Merkle-->>Oracle : Root hash
Oracle->>Contract : proposeReputation(..., evidenceRoot)
Contract->>Contract : Store evidenceRoot in pending proposal
Contract->>Contract : On finalize, move evidenceRoot to evidenceRoots[didHash]
Verifier->>Contract : verifyEvidence(didHash, leaf, proof)
Contract-->>Verifier : True if leaf in committed set
Loading

Diagram sources

Section sources

Maturity Mechanics

Matured score represents the spendable portion of the earned score:

  • Starts at an anchor value set at finalization.
  • Climbs toward the earned score at maturityRatePerDay.
  • Falls are immediate; rises are delayed to prevent farm-and-cash-out patterns.
  • Operator handover resets maturity clock to prevent buying scored identities, however mature the score was.

Read path:

  • getTotalScore returns matured score.
  • If the operator changed at or after the anchor (operatorChangedAt is non-zero and not earlier than maturedAt), the anchor restarts at 0 from the change time. This comes first, so a score that had fully matured, or had fallen below its anchor, is not returned whole to the buyer.
  • If earned score ≤ anchor, return earned score.
  • Otherwise, calculate released points based on elapsed time and rate.
  • Cap matured score at earned score.
flowchart TD
Start([Get Total Score]) --> GetEarned["Compute earned score"]
GetEarned --> CheckSince["Read anchor and its timestamp"]
CheckSince --> Handover{"Operator changed at or after the anchor?"}
Handover --> |Yes| ResetAnchor["Reset anchor to 0, use changedAt"]
Handover --> |No| UseAnchor["Use existing anchor and maturedAt"]
ResetAnchor --> CompareAnchor{"earned <= anchor?"}
UseAnchor --> CompareAnchor
CompareAnchor --> |Yes| ReturnEarned["Return earned score"]
CompareAnchor --> |No| CalcReleased["Calculate released = anchor + ((now - since) * rate / 1 day)"]
CalcReleased --> CapReleased{"released >= earned?"}
CapReleased --> |Yes| ReturnEarned
CapReleased --> |No| ReturnReleased["Return released"]
Loading

Diagram sources

Section sources

Example Score Calculations

Example scenarios demonstrate how factors combine:

  1. Maximum score: All six factors at their maximums sum to exactly 100.

    • Fee: 20, Success: 15, Age: 30, External: 25, Community: 5, Propagation: 5.
    • Total: 100.
  2. Realistic high score: With external feedback enabled, a realistic ceiling is around 89 due to success score approaching but not reaching 15.

    • Fee: 20, Success: 14, Age: 25, External: 25, Community: 5, Propagation: 0.
    • Total: 89.
  3. New agent with no activity: Only community score applies initially.

    • Community: 5, others: 0.
    • Total: 5.
  4. Slashed-like scenario: High flags reduce community score to 0.

    • Community: 0, other factors depend on activity.

These examples are validated by tests and oracle scoring logic.

Section sources

Dependency Analysis

The reputation system has clear dependency boundaries:

  • Contract dependencies:

    • SigvaraIdentity: validates agent registration, status, and stake requirements.
    • IOperatorSet: optional bonded operator registry for oracle authorization.
    • OpenZeppelin libraries for access control, upgradeability, and Merkle proofs.
    • TimelockedGrants and InflowGuard, shared bases on main: admin revocation of upgrader-administered roles, and the guardian’s pause on proposals.
  • Oracle dependencies:

    • scoring.js: computes six factors with clamping to contract caps.
    • index.js: orchestrates proposal flow and evidence commitment.
    • chain.js: ABI bindings for contract interactions.
    • merkle.test.js: validates evidence root construction and verification.
    • epoch-policy.js: divergence detection between oracle computations.
graph LR
SR["SigvaraReputation.sol"] --> SI["SigvaraIdentity.sol"]
SR --> IOps["IOperatorSet"]
SR --> OZ["OpenZeppelin Libraries"]
IDX["index.js"] --> SC["scoring.js"]
IDX --> CH["chain.js"]
IDX --> MKT["merkle.test.js"]
IDX --> EP["epoch-policy.js"]
CH --> SR
SC --> SR
Loading

Diagram sources

Section sources

Performance Considerations

The contract implements several gas optimization strategies:

  1. Compact data types:

    • Six factor scores use uint8 instead of larger types.
    • Timestamps use uint256 where needed for arithmetic.
    • Evidence roots use bytes32 for efficient hashing.
  2. Efficient storage layout:

    • Structs are designed to minimize storage slots.
    • PendingScore appends fields to avoid corrupting existing proposals during upgrades.
    • Mappings are declared in order to maintain upgrade compatibility.
  3. Optimized reads:

    • getTotalScore performs memory operations to avoid repeated storage reads.
    • Matured score calculation uses integer division for daily rate application.
    • View functions minimize external calls and handle fallback gracefully.
  4. Batch operations:

    • Finalization writes multiple related values in a single transaction.
    • Events emit comprehensive data to reduce off-chain reprocessing.
  5. Upgrade safety:

    • Storage layout preserves existing proxies without requiring migration.
    • New fields are appended to avoid shifting existing data.

Section sources

Troubleshooting Guide

Common issues and their resolutions:

Score Validation Errors

  • Symptom: Transaction reverts with ScoreOutOfRange.
  • Cause: One or more factor scores exceed their maximum caps.
  • Resolution: Ensure oracle computes factors within contract-defined limits.

Identity Validation Errors

  • Symptom: Reverts with AgentNotRegistered, AgentSlashed, or AgentNotBonded.
  • Cause: Agent not properly registered, already slashed, or insufficient stake.
  • Resolution: Verify agent registration, check stake requirements, and ensure agent is active.

Proposal Lifecycle Errors

  • Symptom: NoScorePending, ChallengeWindowActive, ChallengeWindowExpired, ProposerNotAuthorized, ScoreAlreadyPending, or InflowsPausedUntil.
  • Cause: Incorrect timing or state assumptions in proposal/finalization flow. ProposerNotAuthorized means the proposer lost ORACLE_ROLE, or its operator was removed or unbonded or its signer cleared. ScoreAlreadyPending comes from proposeIfEmpty when a proposal from an authorized proposer is pending. InflowsPausedUntil means a guardian pause is running on proposals.
  • Resolution: Check challenge window timing (finalizableAt) and proposal existence before calling functions. A proposal from an authorized proposer replaces a stranded one, and a pause ends by itself within three days.

Evidence Verification Failures

  • Symptom: verifyEvidence returns false.
  • Cause: Invalid Merkle proof, wrong evidence root, or payment not in committed set.
  • Resolution: Verify leaf construction matches oracle implementation and proof corresponds to correct root.

Maturity Issues

  • Symptom: Score appears lower than expected.
  • Cause: Maturity delay preventing immediate spendability.
  • Resolution: Understand that matured score climbs gradually; falls are immediate.

Section sources

Conclusion

The reputation system's score storage and data model provide a robust foundation for agent trust assessment. The six-factor scoring approach balances different aspects of agent behavior while maintaining strict caps to prevent manipulation. The optimistic proposal model with challenge windows ensures security, while evidence roots provide verifiable audit trails. Maturity mechanics prevent gaming of the system by delaying score spendability. The design prioritizes gas efficiency and upgrade safety while maintaining clear separation between off-chain computation and on-chain validation.

The system successfully addresses key challenges in decentralized reputation: preventing wash trading attacks through structural factors like tenure and external trust, ensuring accountability through slashing mechanisms, and providing transparency through merkle-provable evidence. The modular architecture allows for evolution while preserving existing deployments and data integrity.

Overview

Clone this wiki locally