Skip to content
 
 

Repository files navigation

Vero Core Contracts

On-chain GitHub PR verification for the Stellar ecosystem. Guardians — trusted off-chain validators — cast weighted votes on registered tasks (pull requests). Once cumulative reputation weight meets a configurable threshold the task is marked done, creating a tamper-proof audit trail on Soroban.


Architecture

┌──────────────────────────────────────────────────────────────────┐
│                         VeroContract                             │
│                                                                  │
│  initialize(token, threshold)                                    │
│  add_guardian(admin, guardian)                                   │
│  register_task(admin, task_id)                                   │
│  vote(guardian, task_id) ──► weight check ──► threshold check   │
│  get_task(task_id) ──► Task { id, votes, is_done, weight }       │
│                                                                  │
│  pause(admin) / unpause(admin) / toggle_pause(admin)            │
│  record_failure() ──► circuit breaker (auto-pause at >50)       │
│  reset_circuit_breaker(admin)                                    │
└──────────────────────────────────────┬───────────────────────────┘
                                       │ instance storage
                         ┌─────────────┴──────────────┐
                         │          DataKey            │
                         │  Guardian(Address)          │
                         │  Reputation(Address)        │
                         │  Task(u64)                  │
                         │  Voted(u64, Address)        │
                         │  Paused                     │
                         │  FailureCount               │
                         └─────────────────────────────┘

Flow

  1. Admin calls initialize with a token address and lock threshold.
  2. Admin registers a GitHub PR as a Task with a unique numeric ID.
  3. Admin whitelists trusted validator addresses as guardians and assigns reputation scores.
  4. Guardians lock tokens above the threshold, then call vote.
  5. Each vote adds the guardian's reputation weight to total_weight_accrued.
  6. When total_weight_accrued >= weight_threshold (default 300) the task's is_done flips to true.

Modules

Module Responsibility
types Task, DataKey, ContractError, RewardStream
guardian Guardian registry with TTL-extended instance storage
task Task registration and retrieval
reputation Guardian reputation scores and voting power calculation
circuit_breaker Emergency halt: require_not_paused, record_failure, reset
reentrancy Mutex lock/unlock guarding vote and register_task
drips Cross-contract reward stream initiation via Drips protocol
vault Cross-contract escrow release on task resolution
events On-chain event emission
lib Public contract surface and vote orchestration

Quick Start

Prerequisites

rustup target add wasm32-unknown-unknown
cargo install --locked soroban-cli

Build

cargo build --target wasm32-unknown-unknown --release

Test

cargo test

Code Snippets

Initialize the contract

client.initialize(&admin, &token_address, &100i128); // lock threshold = 100

Important: initialize() only grants Role::Admin to the admin address. Before calling any other role-gated entrypoint you must explicitly grant the required roles to the caller (typically the admin itself in single-operator deployments).

Grant management roles to the admin

// Required for add_guardian / remove_guardian / set_reputation
client.grant_role(&admin, &admin, &Role::GuardianManager);

// Required for register_task / cancel_task / purge_task
client.grant_role(&admin, &admin, &Role::TaskManager);

// Required for set_weight_threshold / set_vault_address / set_fee_bps / set_treasury_address
client.grant_role(&admin, &admin, &Role::ConfigManager);

// Required for pause / unpause / toggle_pause / reset_circuit_breaker / emergency_recover
client.grant_role(&admin, &admin, &Role::EmergencyManager);

// Required for start_reward_stream
client.grant_role(&admin, &admin, &Role::TreasuryManager);

Add a guardian and set reputation

// Requires Role::GuardianManager (granted above)
client.add_guardian(&admin, &validator_address);
client.set_reputation(&admin, &validator_address, &300u64); // score = 300

Lock tokens (guardian must do this before voting)

client.lock_tokens(&guardian, &150i128); // amount > threshold

Register a task

// Requires Role::TaskManager (granted above). min_votes_required = minimum guardian votes before resolution.
client.register_task(&admin, &pr_number, &1u32);

Cast a vote

client.vote(&guardian, &pr_number)?;

Query task state

let task = client.get_task(&pr_number).unwrap();
assert!(task.is_done); // true once weight threshold is reached

Storage Design

All state lives in instance storage — scoped to the contract instance and extended with a 100 000-ledger TTL window on every guardian write. Keys are typed via the DataKey enum so there are no raw string collisions.

pub enum DataKey {
    Guardian(Address),             // bool — is this address a guardian?
    Reputation(Address),           // u64 — reputation score
    WeightThreshold,               // u64 — cumulative weight required to resolve
    Task(u64),                     // Task — the live (active) task entry
    Voted(u64, Address),           // bool — has this guardian voted on this task?
    TaskVoters(u64),               // Vec<Address> — guardians who voted on this task
    Admin,                         // Address — the multi-sig admin account
    RoleAssignment(Address, Role), // bool — does this address hold this RBAC role?
    DripsAddress,                  // Address — the Drips protocol contract
    VaultAddress,                  // Address — escrow vault contract
    RewardStream(u64),             // RewardStream — active drip stream for a task
    TokenAddress,                  // Address — locked token contract
    LockThreshold,                 // i128 — minimum locked balance to vote
    LockedBalance(Address),        // i128 — tokens locked by a guardian
    Lock,                          // re-entrancy mutex
    FailureCount,                  // u32 — circuit breaker failure counter
    Paused,                        // bool — emergency halt flag
    AllGuardians,                  // Vec<Address> — index of every registered guardian
    AllTasks,                      // Vec<u64> — index of every task id tracked by the contract
    AllVotes,                      // reserved — superseded by the TaskVoters(u64) index, not currently written
    AllRewardStreams,              // Vec<u64> — index of task ids with an active reward stream
    Snapshot(u64),                 // Snapshot — recorded contract state at a given timestamp
    AllSnapshots,                  // Vec<u64> — index of recorded snapshot timestamps
    ActiveTask(u64),               // reserved — active tasks are stored under Task(u64), not currently written
    ArchivedTask(u64),             // Task — archived copy of a resolved, stale task
    Initialized,                   // bool — has the contract's constructor already run?
    WithdrawalTimelock(Address),   // u64 — timestamp a guardian's token unlock was requested
    UpgradeSigners,                // Vec<Address> — addresses authorized to approve contract upgrades
    UpgradeThreshold,              // u32 — number of signer approvals required to execute an upgrade
    PendingUpgradeWasm,            // BytesN<32> — hash of the WASM proposed for the pending upgrade
    PendingUpgradeApprovals,       // Vec<Address> — signers who have approved the pending upgrade
    StorageVersion,                // u32 — schema version of on-chain storage, used by migrations
    FeeBps,                        // u32 — protocol fee in basis points
    TreasuryAddress,               // Address — destination for collected protocol fees
}

Error Codes

Code Variant Meaning
1 NotAuthorized Caller is not a registered guardian or admin
2 DuplicateVote Guardian already voted on this task
3 TaskNotVerified Task is not yet resolved; cannot start reward stream
4 StreamAlreadyActive A reward stream for this task already exists
5 DripsCallFailed Cross-contract call to Drips protocol reverted
6 Locked Re-entrancy guard is active
7 AlreadyInitialized Contract has already been initialized
8 NotInitialized Contract has not been initialized
9 InsufficientLockedBalance Guardian's locked balance does not exceed the threshold
10 WeightOverflow Adding vote weight would overflow u64
11 StillGuardian Cannot unlock tokens while still registered as a guardian
12 NotGuardian Address is not a registered guardian
13 ZeroWeightVote Guardian's reputation score is zero
14 NoReputationScore Guardian has no reputation score assigned
15 ContractPaused Contract is paused; all state-changing calls are blocked
16 EscrowUnavailable Cross-contract call to vault/escrow reverted
17 TaskCancelled Task has been cancelled and cannot be processed
18 InvalidAddress Invalid address provided
19 InvalidAmount Invalid amount provided
20 InvalidConfig Invalid configuration
21 InvalidRange Value is outside valid range
22 BatchTooLarge Batch operation is too large
23 TaskNotFound Task not found
24 TaskAlreadyArchived Task has already been archived
25 TaskNotStale Task is not stale enough to be pruned
26 SnapshotNotFound Snapshot not found
27 WithdrawalTimelockActive Withdrawal timelock is still active
28 TaskNotTerminal Task is not in terminal state
29 InsufficientReputation Insufficient reputation score
30 NotUpgradeSigner Caller is not authorized as a multi-sig upgrade signer
31 UpgradeThresholdNotMet Not enough upgrade approvals collected yet
32 NoPendingUpgrade No pending upgrade proposal to act on
33 AlreadyApproved Signer has already approved this upgrade proposal
34 InvalidUpgradeConfig Invalid multi-sig upgrade configuration (threshold > signers or zero)
35 LastAdminRemovalBlocked Cannot revoke the last remaining Admin role holder (would cause lockout)
36 DuplicateGuardian Attempted to add a guardian that is already registered
37 InvalidVersion Storage version mismatch during pre-flight checks

Emergency Halt (Circuit Breaker)

The contract has a two-track emergency halt system that allows an admin to immediately freeze all state-changing operations if a vulnerability is discovered, without requiring a contract migration.

Manual pause / unpause

// Immediately block all state-changing entry points
client.pause(&admin);

// Restore normal operation
client.unpause(&admin);

// Or toggle the current state
client.toggle_pause(&admin);

// Check current state
let frozen: bool = client.is_paused();

Both pause and unpause require the caller to hold Role::EmergencyManager (granted via grant_role). The admin address can call them after being granted the EmergencyManager role in the setup steps above.

When paused, any call to register_task, vote, add_guardian, set_reputation, set_weight_threshold, or start_reward_stream returns Err(ContractError::ContractPaused) immediately.

Automatic circuit breaker

Off-chain monitors can report observed failures via record_failure. After 50 cumulative failures the contract pauses itself automatically and emits a cb_trip event.

// Called by off-chain monitor after observing a failed invocation
client.record_failure();

To resume after investigation:

// Resets the failure counter and unpauses
client.reset_circuit_breaker(&admin);

Emergency halt procedure

  1. Detect — Either trigger pause manually, or wait for record_failure to trip the breaker at >50 failures.
  2. Verify — Call is_paused() on-chain to confirm the contract is frozen.
  3. Investigate — Audit storage state and transaction history off-chain.
  4. Remediate — Deploy a patched WASM via upgrade_contract if needed.
  5. Resume — Call reset_circuit_breaker (resets counter + unpauses) or unpause if the failure counter was not the trigger.

Security note: Only addresses holding the Role::EmergencyManager role can call pause, unpause, and reset_circuit_breaker. The grant_role call to assign EmergencyManager itself requires Role::Admin, ensuring role delegation is strictly controlled. The record_failure entry point is permissionless so that any observer can report failures, but it only increments a counter — it cannot directly manipulate task or guardian state.


Contributing

Contributions are welcome! See CONTRIBUTING.md for dev environment setup, build/test/lint instructions, branch and PR conventions, and how to find good first issues.


License

Apache-2.0

About

The Vero Core Contracts are the decentralized brain of the system. Built on Soroban, they replace single-maintainer bias with "Guardian" consensus. The contract tracks PR IDs and requires a threshold of on-chain votes before marking work as verified, ensuring reward integrity.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages