Skip to content

Latest commit

 

History

History
149 lines (112 loc) · 7.44 KB

File metadata and controls

149 lines (112 loc) · 7.44 KB

Reputation Oracle — Soroban Contract Spec

The reputation oracle is a Soroban smart contract that stores anchor outcomes on-chain so any consumer can read an anchor's track record without Stellar Intel's permission.

Source of truth: contracts/reputation/ (Cargo.toml, src/lib.rs, src/admin.rs, src/anchors.rs, src/outcome.rs, tests/basic.rs).

Status. The contract is implemented and unit-tested (contracts/reputation/tests/basic.rs) and deployed to testnet at CCZ54NTEOVL2DKWCGJA5XHTHOGRDS7JHFKYWEC6QH2IMZLYNM3FBFKDG (see .deployments/testnet.json), with submitToOracle (packages/publisher/src/batch.ts) wired against it. Mainnet deployment, multi-signer admin, and the public read SDK are roadmap gates — see docs/ROADMAP.md Wave 2.1. Do not assume a live mainnet address yet.

Contract

ReputationContract (#[contract] in src/lib.rs). Entrypoints:

Outcomes — src/outcome.rs, src/lib.rs

pub fn submit_outcome(/* … outcome fields … */) -> Result<(), Error>

Records a single anchor outcome (the on-chain mirror of an off-chain outcome tuple — fill, slippage, settle latency). Writes are restricted to authorized publishers via the admin gate.

Anchor registry — src/anchors.rs

pub fn list(env: &Env) -> Vec<String>            // registered anchor ids
pub fn register(env: &Env, anchor_id: String) -> Result<(), Error>

Admin — src/admin.rs

pub fn set_admin(env: &Env, admin: &Address) -> Result<(), Error>
pub fn get_admin(env: &Env) -> Option<Address>
pub fn require_admin(env: &Env, caller: &Address) -> Result<(), Error>  // internal gate

require_admin is the authorization check that guards register.

Custody

There are two independent authorities, and conflating them is the mistake to avoid:

Authority Storage key Can do
Operational admin DataKey::Admin register anchors, add/revoke publishers, migrations
Upgrade admin UpgradeKey::UpgradeAdmin replace the contract WASM

Multisig requires no contract change. Both are soroban_sdk::Address values, so either may be a Stellar account with several signers and a threshold; require_auth() delegates the threshold check to the host. The two-step handoff (propose_adminaccept_admin, with cancel_admin_proposal) means authority is never transferred to an address that cannot sign.

What matters operationally is therefore which accounts these are, not what the contract supports. Read it back rather than assuming:

npx tsx scripts/verify-oracle-read.mts

It prints the admin, the upgrade admin, the pending admin and the contract version, and warns when the two authorities are the same account — one compromised key that can both forge data and replace the code.

Current testnet state (checked 2026-08-04). contract_version is 0, the upgrade admin is unset, and pending_admin / upgrade_admin are not implemented at all: the deployed bytecode predates them. That also means it predates the authorization fixes in #907, so the live testnet contract still has the unauthenticated set_corridor_metrics write path. Re-deploy before treating testnet reads as trustworthy. The anchor registry is also empty, so every score read returns "no data".

Who can write what

Every state-changing entrypoint is gated. There are two gates, and which one applies depends on whether the write is an operational data feed or a governance action.

Gate Entrypoints
Publisher (publishers::is_authorized + require_auth) submit_outcome, set_corridor_metrics, publish_corridor_rate, add_volume_savings
Admin (admin::require_admin) register_anchor, add_publisher, revoke_publisher, propose_admin, cancel_admin_proposal, migrate_corridor_v2, migrate_all_v2
Candidate self-auth accept_admin
Upgrade admin upgrade

set_corridor_metrics, migrate_corridor_v2 and migrate_all_v2 were unguarded until #907 — they took no caller at all, so any account could forge an anchor's score inputs or trigger a state migration. Adding the gate was a breaking ABI change: all three now take a leading caller Address and return Result<(), Error>.

Consuming the oracle

Off-chain, read the same data through /api/reputation/*. On-chain, a consumer contract calls the read entrypoints directly. A TypeScript read helper and JS/Python example consumers are roadmap deliverables (Wave 2.1).

Building & testing

cd contracts/reputation
cargo test                                          # runs tests/basic.rs
npx tsx scripts/deploy-oracle-testnet.ts             # build + deploy to testnet

Storage versions

The corridor aggregate exists in two shapes. Both are readable; v1 keys are never deleted, so an existing reader keeps working across a migration with no cutover window.

Version Read entrypoints Tuple
v1 get_corridor_aggregate, get_score_for_corridor (fill_rate_bps, slippage_bps, settle_seconds_p50, n)
v2 get_corridor_aggregate_v2, get_score_for_corridor_v2 (fill_rate_bps, slippage_bps, composite_bps, settle_seconds_p50, n)

Migration is admin-gated and idempotent, via migrate_corridor_v2 (one pair) or migrate_all_v2 (registered anchors × the compiled-in corridor list). The runbook, including the corridor-list caveat and what a partial migration means, is in ORACLE_MIGRATION.md.

Upgrade & governance

Publisher whitelist management and a two-step admin transfer are implemented; see GOVERNANCE.md for the custody runbook. The upgrade path exists (init_upgrade / upgrade) but has no rotation — once the upgrade admin is bound it cannot be changed (#963). Treat the deployed contract (testnet) as admin-controlled and not yet production-governed.

Related