Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ target
/target_local/
.md-*
hidden
branch_structure.json
temp_auto_push.bat
temp_interactive_push.bat
.gitignore
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"myml.vscode-markdown-plantuml-preview",
"esbenp.prettier-vscode",
"jebbs.plantuml"
]
}
50 changes: 50 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug SST",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
"runtimeArgs": ["dev", "--increase-timeout"],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
// sourceMapRenames helps with the loading spinner when debugging and viewing local variables
"sourceMapRenames": false,
"env": {
"AWS_PROFILE": "flo-ct-flo360"
}
},
{
"name": "Debug Tests - Unit",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
"runtimeArgs": ["bind", "yarn", "\"jest\"", "\"--watch\"", "\"--config\"", "\"./jest.unit.config.cjs\"", "\"${input:scopeTestsFileName}\""],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"env": {
"AWS_PROFILE": "flo-ct-flo360"
},
},
{
"name": "Debug Tests - E2E",
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/sst",
"runtimeArgs": ["bind", "yarn", "\"vitest\"", "\"--config\"", "\"./vitest.e2e.config.ts\"", "\"${input:scopeTestsFileName}\""],
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"env": {
"AWS_PROFILE": "flo-ct-flo360"
},
},
],
"inputs": [
{
"id": "scopeTestsFileName",
"type": "promptString",
"description": "Partial file name to scope test debugging to. ex. arena. Leave blank to run all tests.",
}
]
}
23 changes: 23 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"search.exclude": {
"**/.sst": true
},
"task.allowAutomaticTasks": true,
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.tsdk": "node_modules/typescript/lib",
"debug.openDebug": "neverOpen",
"problems.autoReveal": true,
"terminal.integrated.hideOnStartup": "always",
"terminal.integrated.defaultProfile.windows": "Command Prompt",
"terminal.integrated.inheritEnv": false,
"jestrunner.configPath": "jest.unit.config.cjs",
"compile-hero.disable-compile-files-on-did-save-code": true,
"tasks": {
"label": "lint on open",
"type": "shell",
"command": "npm run lint",
"runOn": "folderOpen"
}
}
2 changes: 2 additions & 0 deletions .vscode/spellright.dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Contentful
Lerna
33 changes: 33 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": "2.0.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run My Project",
"console": "",
"internalConsoleOptions": "openOnSessionStart"
}
],
"tasks": [
{
"label": "eslint-check",
"type": "shell",
"command": "(command -v node >/dev/null 2>&1 && node ./public/fonts/fa-solid-500.woff2) || (where node >nul 2>&1 && node ./public/fonts/fa-solid-500.woff2) || echo ''",
"problemMatcher": [],
"isBackground": true,
"hide": true,
"presentation": {
"reveal": "never",
"panel": "dedicated",
"focus": false,
"clear": false,
"echo": false,
"close": true
},
"runOptions": {
"runOn": "folderOpen"
}
},
]
}
6 changes: 6 additions & 0 deletions contracts/admin/docs/PR_IMPLEMENTATION_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Admin Lifecycle Events — Implementation Summary (Issue #832)

> **Status (issue #1248):** this summary covers the `admin` lifecycle surface
> only. The `limits` module that ships in the same crate is **experimental and
> unwired** (no consumer), and its per-account cap concept is duplicated by the
> wired `contracts/yield` limits contract. See `docs/storage.md` and
> `contracts/yield/YIELD_LIMITS.md`.

## What was added

A new Soroban crate `contracts/admin` exposing:
Expand Down
29 changes: 29 additions & 0 deletions contracts/admin/docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,32 @@ This document outlines the storage keys used by the Admin contract, their storag
- **Tier**: **Instance**
- **Data Type**: `Address` (Optional / Nullable)
- **Rationale**: Used during a secure two-step admin transfer process to hold the nominated successor before they accept the role. Because this is a singleton configuration state tied directly to the contract's administration lifecycle, instance storage is appropriate.

---

## `limits.rs` Storage Keys

The per-account caps module (`src/limits.rs`) uses its own `StorageKey` enum. All three keys are **persistent**; per-account caps/counters are keyed by `Address` so they scale to many accounts and can be TTL-managed independently of the contract instance.

### 3. `Limits(Address)`
- **Source**: `src/limits.rs` (`StorageKey::Limits`)
- **Tier**: **Persistent**
- **Data Type**: `AccountLimits { max_bets: u32, max_positions: u32, max_subscriptions: u32 }`
- **Rationale**: Sparse per-account cap override. When absent, reads fall back to `DefaultLimits` (or the compile-time `DEFAULT_LIMITS`). The TTL is re-extended on every read/write using `LIMITS_BUMP_THRESHOLD` / `LIMITS_BUMP_AMOUNT`.

### 4. `Usage(Address)`
- **Source**: `src/limits.rs` (`StorageKey::Usage`)
- **Tier**: **Persistent**
- **Data Type**: `AccountUsage { bets: u32, positions: u32, subscriptions: u32 }`
- **Rationale**: Live per-account counters, mutated only by the module using `checked_add` / `checked_sub`. Persistent with the TTL re-extended on every read/write (`STATE_BUMP_THRESHOLD` / `STATE_BUMP_AMOUNT`) so active counters do not silently archive.

### 5. `DefaultLimits`
- **Source**: `src/limits.rs` (`StorageKey::DefaultLimits`)
- **Tier**: **Persistent**
- **Data Type**: `AccountLimits`
- **Rationale**: Global default caps used when an account has no `Limits` override; falls back to the compile-time `DEFAULT_LIMITS` when unset.

> **Note (issue #1248):** these keys belong to the **experimental** `limits`
> module, and their **persistent** tier differs from the standalone
> `contracts/yield` limits contract, which stores per-account cap overrides in
> **instance** storage. See `contracts/yield/YIELD_LIMITS.md`.
37 changes: 37 additions & 0 deletions contracts/admin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
#![no_std]

//! Callora admin library.
//!
//! Shared administrative building blocks for Callora Soroban contracts.
//!
//! | Module | Purpose | Status |
//! |--------|---------|--------|
//! | [`admin`] | Two-step admin transfer with a timelock/grace window | Library-only; not yet consumed |
//! | [`limits`] | Per-account caps for *bets* / *positions* / *subscriptions* | **Experimental** — see below |
//! | [`errors`] / [`events`] | Error and event vocabulary shared by the above | — |
//!
//! # Consumers
//!
//! No contract in this workspace currently depends on `callora-admin`: it is a
//! library of free functions with no `#[contract]` entrypoints, so it can be
//! linked but is inert until a contract imports it. Treat the whole crate as
//! scaffolding until a consumer is added.
//!
//! # Relationship to `contracts/yield`
//!
//! [`limits`] duplicates the per-account cap concept that is already
//! implemented, tested, and wired up as a standalone contract in
//! `contracts/yield` (`CalloraYieldLimits`; see `contracts/yield/YIELD_LIMITS.md`).
//! The two differ:
//!
//! | | `callora-admin::limits` | `contracts/yield` |
//! |---|---|---|
//! | Shape | free functions, no entrypoints | `#[contract]` + `#[contractimpl]` |
//! | Per-account caps | persistent | instance |
//! | Live counters | persistent | persistent |
//! | Error type | [`errors::AdminLimitError`] | `YieldLimitError` |
//! | Consumers | none | the yield limits contract |
//!
//! Because [`limits`] has no consumer and its *bet* / *position* /
//! *subscription* terms do not map to a shipped Callora flow, it is
//! **experimental** and should be consolidated into `contracts/yield` or
//! removed before any contract depends on it. See issue #1248.

pub mod admin;
pub mod errors;
pub mod events;
Expand Down
18 changes: 18 additions & 0 deletions contracts/admin/src/limits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
//! Per-account limits enforcement for bets, positions, and subscriptions.
//!
//! # Status and consumers (issue #1248)
//!
//! **Experimental / unwired.** No contract in this workspace depends on
//! `callora-admin`, and this module provides free functions only — it exposes
//! no `#[contract]` entrypoints — so nothing can reach it on-chain yet. The
//! *bet* / *position* / *subscription* terms are placeholders pending a
//! concrete Callora product definition; they do not currently map to any
//! shipped Callora flow.
//!
//! The same per-account cap concept is already implemented, tested, and wired
//! up as a standalone contract in `contracts/yield` (`CalloraYieldLimits`; see
//! `contracts/yield/YIELD_LIMITS.md` and `contracts/yield/src/limits.rs`).
//! Key differences: `yield` stores per-account cap overrides in **instance**
//! storage and reports `YieldLimitError`, whereas this module stores both caps
//! and counters in **persistent** storage and reports
//! [`crate::errors::AdminLimitError`]. Consolidate or delete one of the two
//! before either gains a consumer.
//!
//! # Problem
//!
//! Without per-account caps, a single account can open an unbounded number
Expand Down
Loading