Skip to content

Contract Overview and Architecture

RunTimeAdmin edited this page Oct 29, 2026 · 13 revisions

Contract Overview & Architecture

**Referenced Files in This Document** - [SigvaraIdentity.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraIdentity.sol) - [SigvaraStaking.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraStaking.sol) - [SigvaraReputation.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReputation.sol) - [SigvaraOracleBond.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraOracleBond.sol) - [Deploy.s.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/script/Deploy.s.sol) - [Upgrade.s.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/script/Upgrade.s.sol) - [foundry.toml](https://github.com/RunTimeAdmin/Sigvara/blob/main/foundry.toml) - [Upgrade.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/Upgrade.t.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) - [SigvaraReader.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReader.sol) - [0004-operator-custody.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/adr/0004-operator-custody.md) - [0005-governance-and-emergency-powers.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/adr/0005-governance-and-emergency-powers.md)

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
  10. Appendices

Introduction

This document describes the architecture of Sigvara’s smart contract system with a focus on:

  • The foundational identity registry and its dependent contracts for staking, reputation, and oracle bonding.
  • The UUPS proxy upgrade pattern using OpenZeppelin’s upgradeable primitives.
  • Access control, role-based permissions, and upgradeability safeguards.
  • Data flow between components, storage layout discipline, gas optimization strategies, and cross-contract communication patterns.
  • Deployment, initialization, and upgrade procedures.

The design is modular: identity is the root registry, staking enforces collateral and slashing, reputation stores scored trust, and oracle bonding manages operator performance bonds. All core contracts are upgradeable through UUPS proxies controlled by an UPGRADER_ROLE.

Project Structure

At the protocol level, the relevant source files live under src/, deployment logic under script/, configuration under foundry.toml, and upgrade tests under test/.

graph TB
subgraph "Deployment Scripts"
Deploy["Deploy.s.sol"]
Upgrade["Upgrade.s.sol"]
end
subgraph "Core Contracts"
Identity["SigvaraIdentity.sol"]
Staking["SigvaraStaking.sol"]
Reputation["SigvaraReputation.sol"]
OracleBond["SigvaraOracleBond.sol"]
end
subgraph "Configuration"
Foundry["foundry.toml"]
end
subgraph "Tests"
UpgradeTest["Upgrade.t.sol"]
end
Deploy --> Identity
Deploy --> Reputation
Deploy --> Staking
Upgrade --> Identity
Upgrade --> Reputation
Upgrade --> Staking
Upgrade --> OracleBond
Foundry --> Deploy
UpgradeTest --> Identity
UpgradeTest --> Reputation
UpgradeTest --> Staking
Loading

Diagram sources

Section sources

Core Components

  • SigvaraIdentity: Foundational registry that anchors agent identities (DIDs), tracks operators, status transitions, public key rotation, and operator transfers. It also gates activation against stake via a view into the staking contract.
  • SigvaraStaking: Manages agent stake tokens, minimum stake enforcement, unbonding withdrawals, and the slash lifecycle including initiation, dispute, execution, and proceeds distribution.
  • SigvaraReputation: Stores six-factor reputation scores proposed by oracles, with optimistic challenge windows, maturity over time, evidence Merkle roots, and bond-gated oracle admission.
  • SigvaraOracleBond: Separate performance-bond registry for oracle operators, with deposit/admit/unbond/slash flows and active-operator checks consumed by reputation.

All four contracts inherit from OpenZeppelin’s Initializable, AccessControlUpgradeable, and UUPSUpgradeable, and implement _authorizeUpgrade gated by UPGRADER_ROLE.

On main they also inherit two small shared bases, TimelockedGrants and InflowGuard. Each registry’s initialize names UPGRADER_ROLE as the admin of the roles that move bonded funds or change an agent’s standing, so once the timelock holds UPGRADER_ROLE, as planned for mainnet, granting one waits out its delay, and TimelockedGrants lets the admin revoke any of them at once, except UPGRADER_ROLE itself. InflowGuard lets a guardian pause new registrations, stake deposits, score proposals and bond deposits for three days at a time, and nothing else. SigvaraReader is separate: a stateless, non-upgradeable contract that answers isTrusted, isTrustedFresh and trust for on-chain consumers, and that the protocol itself never calls. The Arc testnet proxies have run these implementations since 26 October 2026, but they were initialised by the 21 September code, so their role administrators are the older ones and the timelocked grants do not apply to them. The reader is not yet deployed anywhere.

Section sources

Architecture Overview

The system follows a layered model:

  • Identity is the root; it knows about agents and their operators.
  • Staking depends on Identity to enforce slash-related state changes and uses Reputation only for zeroing scores after slash.
  • Reputation depends on Identity to validate scorable agents and optionally on OracleBond to require bonded operators.
  • OracleBond is independent but feeds Reputation’s operator admission check.
classDiagram
class SigvaraIdentity {
+initialize(admin, stakingCore)
+registerAgent(agentAddress, ed25519PubKey, signature)
+rotatePublicKey(didHash, newEd25519PubKey)
+updateStatus(didHash, newStatus)
+offerOperatorTransfer(didHash, newOperator)
+acceptOperatorTransfer(didHash)
+setStakeView(stakeView_)
+computeDidHash(agentAddress)
+isActive(didHash)
+getOperatorAgents(operator)
+getOperatorAgentsPaged(operator, offset, limit)
+_authorizeUpgrade(newImplementation)
}
class SigvaraStaking {
+initialize(admin, identityRegistry_, reputationRegistry_, svrToken_, minimumStake_, challengePeriod_, unbondingPeriod_)
+depositStake(didHash, amount)
+initiateWithdrawal(didHash, amount)
+claimWithdrawal(didHash)
+initiateSlash(didHash, victim, evidenceHash)
+disputeSlash(didHash)
+resolveDispute(didHash, uphold)
+expireDispute(didHash)
+expireSlash(didHash)
+cancelSlash(didHash)
+vetoSlash(didHash)
+executeSlash(didHash)
+claimSlashProceeds()
+hasMinimumStake(didHash)
+_authorizeUpgrade(newImplementation)
}
class SigvaraReputation {
+initialize(admin, oracle, stakingCore, slashingCommittee, challengeWindow_)
+proposeReputation(didHash, data, evidenceRoot)
+proposeIfEmpty(didHash, data, evidenceRoot)
+finalizeReputation(didHash)
+rejectReputation(didHash)
+zeroReputation(didHash)
+setOperatorBond(operatorBond_)
+getTotalScore(didHash)
+verifyEvidence(didHash, leaf, proof)
+_authorizeUpgrade(newImplementation)
}
class SigvaraOracleBond {
+initialize(admin, slasher, svrToken, bondAmount_, unbondingPeriod_, slashBeneficiary_)
+depositBond(amount)
+initiateUnbond()
+withdrawBond()
+admit(operator)
+removeOperator(operator)
+slash(operator, amount)
+isActiveOperator(operator)
+_authorizeUpgrade(newImplementation)
}
SigvaraStaking --> SigvaraIdentity : "reads/writes agent state"
SigvaraStaking --> SigvaraReputation : "zeros score on slash"
SigvaraReputation --> SigvaraIdentity : "validates scorable agent"
SigvaraReputation --> SigvaraOracleBond : "optional operator bond check"
Loading

Diagram sources

Detailed Component Analysis

SigvaraIdentity

SigvaraIdentity is the foundational registry. It:

  • Computes deterministic DIDs per chain and agent address.
  • Requires agent-signed registration digests to bind an Ethereum address to an Ed25519 public key.
  • Tracks operator ownership, status transitions, and operator transfer offers.
  • Enforces collateral checks before allowing an agent to become Active again.
  • Uses a one-way interface IStakeView to avoid circular imports while still checking stake.

Key behaviors:

  • Registration requires a valid signature over a digest bound to chain ID, registry address, agent address, operator, and public key.
  • Status updates allow operator toggling between Active and Suspended, but Slashed is terminal and can only be set by STAKING_CORE_ROLE.
  • Operator transfers are two-step: offer then accept, with collateral checks at acceptance.
  • Slash suspension lock prevents self-reinstatement during pending slashes.

Storage layout discipline:

  • New mappings are appended after existing ones to preserve slot positions across upgrades.
  • A secondary index maps operator to agent list plus position tracking for constant-time removal.

Access control:

  • DEFAULT_ADMIN_ROLE and UPGRADER_ROLE granted to admin.
  • STAKING_CORE_ROLE granted to SigvaraStaking to mark agents as Slashed or manage slash suspension locks.
  • On main, UPGRADER_ROLE administers itself and STAKING_CORE_ROLE, and GUARDIAN_ROLE may pause registerAgent.

Upgradeability:

  • _authorizeUpgrade restricts upgrades to UPGRADER_ROLE.
  • initializeV2 wires the stake view after staking is deployed.
  • On main, setStakeView repoints it and requires UPGRADER_ROLE, so once the timelock holds that role a change takes its delay.
flowchart TD
Start(["Register Agent"]) --> ValidateInputs["Validate agentAddress and ed25519PubKey"]
ValidateInputs --> VerifySignature["Verify agent signature over registrationDigest"]
VerifySignature --> ComputeDid["Compute didHash from chainId and agentAddress"]
ComputeDid --> CheckRegistered{"Already registered?"}
CheckRegistered --> |Yes| RevertAlready["Revert AlreadyRegistered"]
CheckRegistered --> |No| CreateIdentity["Create AgentIdentity<br/>status = PendingBond"]
CreateIdentity --> IndexOperator["Push to operatorAgents and record position"]
IndexOperator --> EmitEvent["Emit AgentRegistered"]
EmitEvent --> End(["Done"])
Loading

Diagram sources

Section sources

SigvaraStaking

SigvaraStaking manages agent stake and slashing:

  • Deposits credit actual received amounts to handle fee-on-transfer tokens safely.
  • Enforces minimum stake for activation and withdrawal constraints.
  • Implements unbonding periods where queued withdrawals remain slashable.
  • Slash lifecycle: initiate -> challenge period -> dispute or execute -> settlement.
  • On main, an upheld ruling waits for the challenge deadline fixed when the slash was filed and is refused once the dispute can be expired, a dispute nobody rules on expires after 14 days (expireDispute), and a pending slash nobody executes can be lapsed by anyone 14 days after its deadline (expireSlash). Every dropped proposal starts a cooldown of one challenge period before the committee can file against the same agent again.
  • Settlement burns half, distributes quarter to victim and quarter to reporter, marks agent Slashed, and zeros reputation.

Access control:

  • SLASHING_COMMITTEE_ROLE allows slash initiation and cancellation, and a slash can be executed only while its reporter still holds it. On main it no longer rules on disputes: the admin (DEFAULT_ADMIN_ROLE) calls resolveDispute and can drop a pending or disputed slash with vetoSlash.
  • UPGRADER_ROLE controls upgrades and, on main, grants SLASHING_COMMITTEE_ROLE.
  • Admin roles update parameters like minimum stake, challenge period, and unbonding period. On main the minimum stake must be above zero, the challenge period between 3 and 30 days and the unbonding period between 1 and 90 days, in the initializers as well as the setters.
  • GUARDIAN_ROLE may pause depositStake.

Cross-contract interactions:

  • Reads AgentIdentity from Identity to validate operator and status.
  • Calls identityRegistry.updateStatus to suspend or slash agents.
  • Calls reputationRegistry.zeroReputation upon slash execution.
sequenceDiagram
participant Committee as "Slashing Committee"
participant Staking as "SigvaraStaking"
participant Identity as "SigvaraIdentity"
participant Reputation as "SigvaraReputation"
Committee->>Staking : initiateSlash(didHash, victim, evidenceHash)
Staking->>Identity : updateStatus(Suspended)
Note over Staking : Record proposal with snapshot challengeDeadline
alt Operator disputes
Operator->>Staking : disputeSlash(didHash)
Staking->>Staking : Set state=Disputed
Admin->>Staking : resolveDispute(didHash, uphold)
else No dispute
Any->>Staking : executeSlash(didHash)
Staking->>Staking : _settleSlash()
Staking->>Identity : updateStatus(Slashed)
Staking->>Reputation : zeroReputation(didHash)
Staking->>Staking : Transfer burn to 0xdead
Staking-->>Victim : Credit claimable
Staking-->>Reporter : Credit claimable
end
Loading

Diagram sources

Section sources

SigvaraReputation

SigvaraReputation stores six-factor reputation scores:

  • Oracle proposes scores within capped factors; a challenge window opens. On main each proposal records its proposer and a finalizableAt fixed when it is made, so a later change to the window moves no pending proposal.
  • Anyone finalizes after the window if not rejected by the slashing committee, as long as the proposer still holds ORACLE_ROLE and, with an operator set, is still active in it. A stranded proposal is replaced by a new one, or by proposeIfEmpty from a checking operator.
  • Scores mature over time at a configurable rate; handovers restart maturity, however mature the score was (on the Arc testnet proxy a score at or below its anchor is still returned whole).
  • Evidence roots commit to payment leaves; Merkle proofs verify individual payments.
  • Optional bonded operator registry integration ensures proposers are admitted and bonded, or are the signer of an admitted operator.

Access control:

  • ORACLE_ROLE allows proposing scores.
  • SLASHING_COMMITTEE_ROLE allows rejecting proposals.
  • STAKING_CORE_ROLE allows zeroing scores on slash.
  • UPGRADER_ROLE controls upgrades.
  • On main, UPGRADER_ROLE also grants STAKING_CORE_ROLE and SLASHING_COMMITTEE_ROLE, and setOperatorBond requires it. ORACLE_ROLE stays with the admin. GUARDIAN_ROLE may pause proposeReputation and proposeIfEmpty.
  • The challenge window is held between 1 hour and 7 days and the maturity rate between 1 and 100 points a day.

Cross-contract interactions:

  • Reads Identity to ensure the agent exists and is not slashed.
  • Uses stakeView.hasMinimumStake via Identity to enforce bonding requirement.
  • Optionally reads operatorBond.isActiveOperator to gate oracle proposer eligibility.
flowchart TD
Propose["Oracle proposes score"] --> ValidateFactors["Validate factor caps"]
ValidateFactors --> RequireScorable["Require scorable agent via Identity"]
RequireScorable --> StorePending["Store PendingScore with evidenceRoot"]
StorePending --> ChallengeWindow["Challenge window starts"]
ChallengeWindow --> Decision{"Rejected by committee?"}
Decision --> |Yes| Discard["Delete pending score"]
Decision --> |No| Finalize["Finalize after window"]
Finalize --> UpdateReputation["Write finalized ReputationData"]
UpdateReputation --> Maturity["Set anchor and maturedAt"]
Loading

Diagram sources

Section sources

SigvaraOracleBond

SigvaraOracleBond manages oracle operator performance bonds:

  • Operators deposit bonds, get admitted by governance, and can exit via unbonding cooldown.
  • Slashes reduce bond balances and may demote active operators below the required threshold.
  • Provides isActiveOperator which checks both status and current bond against the required amount.
  • On main, an Active operator can name one signer (setSigner, clearSigner). isActiveOperator answers for the signer as for the operator, so the key a service signs with need not be the key that owns the bond.

Access control:

  • SLASHER_ROLE allows slashing. On main it is granted by UPGRADER_ROLE, and the address that receives slashed bond cannot hold it.
  • UPGRADER_ROLE controls upgrades.
  • Admin can admit/remove operators and adjust parameters. On main the unbonding period is held between 1 and 90 days and the bond amount above zero, setSlashBeneficiary requires UPGRADER_ROLE, and GUARDIAN_ROLE may pause depositBond.

Integration:

  • Consumed by SigvaraReputation to require bonded operators when configured.
stateDiagram-v2
[*] --> None
None --> Bonded : "depositBond()"
Bonded --> Active : "admit()"
Active --> Exiting : "initiateUnbond()/removeOperator()"
Bonded --> Exiting : "initiateUnbond()/removeOperator()"
Exiting --> None : "withdrawBond()"
Active --> Bonded : "slash() drops below bondAmount"
Loading

Diagram sources

Section sources

Dependency Analysis

The dependency graph reflects a clear hierarchy:

  • Identity has no protocol dependencies beyond OpenZeppelin primitives and a minimal view of staking.
  • Staking depends on Identity and Reputation.
  • Reputation depends on Identity and optionally OracleBond.
  • OracleBond is standalone.
  • All four share the TimelockedGrants and InflowGuard bases on main. Both are abstract and exist only as part of each registry. SigvaraReader reads Identity, Reputation and Staking, and nothing depends on it.
graph LR
Identity["SigvaraIdentity"] --> Staking["SigvaraStaking"]
Staking --> Reputation["SigvaraReputation"]
Reputation --> Identity
Reputation --> OracleBond["SigvaraOracleBond"]
Loading

Diagram sources

Section sources

Performance Considerations

  • Storage layout discipline: all contracts append new state variables after existing ones to keep base slots stable across upgrades. This avoids costly reindexing and corruption risks.
  • Gas optimizations:
    • Constant-time removal from operator lists using recorded indices and swap-and-pop.
    • Pull-based payouts for slash proceeds to avoid recipient revert failures.
    • Reading total score in memory and emitting totals from memory to reduce storage reads.
    • Using SafeERC20 and measuring balance deltas to safely support fee-on-transfer tokens without over-crediting.
  • View functions minimize writes and use efficient paging for large operator lists.

Troubleshooting Guide

Common issues and mitigations:

  • Upgrade ordering: upgrading Reputation before Identity can cause read failures if newer code calls functions not present in older Identity versions. The upgrade script checks for required function signatures and reverts if dependencies are missing.
  • Role separation: deploying with deployer holding multiple roles (oracle, committee, admin) is warned and blocked on mainnet to prevent single-key attacks. Mainnet also requires a guardian that is none of those keys.
  • Stake view wiring: Identity must have stakeView set to SigvaraStaking before allowing agents to return to Active; otherwise activation attempts revert with StakeViewNotSet.
  • Unbonding and challenge periods: parameters have minimum bounds enforced to prevent trivially short windows that undermine dispute rights. On main they also have ceilings (a 30 day slash challenge period, a 7 day score window, a 90 day unbonding period, a maturity rate of 100 points a day), refused in the initializers as well as the setters.
  • Paused inflows: on main, registerAgent, depositStake, proposeReputation, proposeIfEmpty and depositBond revert with InflowsPausedUntil while a guardian pause runs. Exits, disputes, rulings, slash execution and finalization are never paused.

Operational steps:

  • Use script/Upgrade.s.sol to simulate and broadcast upgrades with dependency checks.
  • Ensure deployments/<chainId>.json contains correct proxy addresses.
  • Run slot-pinning and upgrade tests (test/Upgrade.t.sol) before broadcasting.

Section sources

Conclusion

Sigvara’s contract architecture separates concerns cleanly:

  • Identity anchors agents and operators.
  • Staking enforces economic security through bonds and slashing.
  • Reputation captures off-chain verified trust with optimistic governance and maturity.
  • OracleBond secures operator integrity independently.

The UUPS proxy pattern enables safe upgrades guarded by UPGRADER_ROLE, with careful storage layout and dependency checks ensuring continuity and security. The modular design supports future evolution while maintaining strong access control and slashing incentives.

Appendices

Deployment Strategy

  • The deploy script constructs ERC1967 proxies for each implementation and initializes them in order:
    1. Token (existing or testnet).
    2. Identity proxy initialized with admin and placeholder staking core.
    3. Reputation proxy initialized with admin, oracle, staking core, committee, and challenge window (the script passes zero for oracle, staking core and committee and grants those roles in step 5); then wired to Identity.
    4. Staking proxy initialized with token, Identity, Reputation, minimum stake, challenge period, and unbonding period. The script then calls initializeV2 with the same unbonding period, which uses up the one call a fresh deployment leaves open.
    5. Cross-role wiring: grant STAKING_CORE_ROLE to Staking on Identity and Reputation; grant ORACLE_ROLE and SLASHING_COMMITTEE_ROLE on Reputation; grant SLASHING_COMMITTEE_ROLE on Staking.
    6. If GUARDIAN_ADDRESS is set, grant GUARDIAN_ROLE on Identity, Reputation and Staking. It is required on mainnet.
  • On chain 5042 (Arc mainnet) the script first refuses a missing or non-18-decimal bond token, periods outside the contracts’ floors and ceilings, a maturity rate above 100, a minimum stake under 10,000 SVR, and a missing guardian or one that is the deployer, the oracle or the committee.
  • Addresses are written to deployments/<chainId>.json.

Section sources

Initialization Procedures

  • Each contract uses initializer and reinitializer versioning to support multi-step setup:
    • Identity: initialize then initializeV2 to wire stake view.
    • Reputation: initialize, initializeV2 for slashing committee and window, initializeV3 to wire Identity, initializeV4 to set maturity rate.
    • Staking: initialize then optional initializeV2 to set unbonding period for pre-upgrade deployments. initializeV2 is held to the same floor and ceiling as setUnbondingPeriod.
    • OracleBond: initialize sets admin, slasher, token, bond amount, unbonding period, and slash beneficiary.

Section sources

Migration Considerations for Upgrades

  • Always run slot-pinning tests to ensure storage layout compatibility.
  • Use script/Upgrade.s.sol to deploy a fresh implementation and call upgradeToAndCall atomically with any necessary initializer calldata. It broadcasts with the key it is given, so it fits a deployer that still holds UPGRADER_ROLE.
  • After the mainnet handover the timelock holds UPGRADER_ROLE, which the script cannot act as. The implementation is deployed from any funded key, the Safe schedules upgradeToAndCall(impl, data) on the timelock, and anyone executes it once the delay has run. data cannot be a reinitializer there, because reinitializers need DEFAULT_ADMIN_ROLE and the timelock is not the admin, so the upgrade lands with empty data and the Safe calls the reinitializer in a second transaction.
  • The script verifies dependency readiness (e.g., Identity exposes required functions before upgrading Reputation), and that the proxy answers a getter only the target has (ProxyIsNotTheTarget).
  • After upgrade, record live implementations using provided tooling to keep artifacts consistent.

Section sources

Overview

Clone this wiki locally