Skip to content

Commit ce87723

Browse files
authored
Merge branch 'Quantarq:main' into Redis
2 parents 1105a8f + 57bf975 commit ce87723

46 files changed

Lines changed: 5145 additions & 28 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
5454

5555
# [Optional] Telegram Bot API Token (e.g. from BotFather)
5656
TELEGRAM_TOKEN=123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ
57+
TELEGRAM_ADMIN_USER_IDS=123456789,987654321
5758

5859
# [Optional] URL for the Telegram WebApp / Mini-App (default: https://quantara.xyz)
5960
TELEGRAM_WEBAPP_URL=https://quantara.xyz

.github/workflows/soroban_fuzz.yml

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
name: Soroban Fuzz (Smoke — 60 s per target)
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'quantara/soroban/contracts/*/fuzz/**'
8+
- '.github/workflows/soroban_fuzz.yml'
9+
pull_request:
10+
branches: [main]
11+
paths:
12+
- 'quantara/soroban/contracts/*/fuzz/**'
13+
- '.github/workflows/soroban_fuzz.yml'
14+
workflow_dispatch:
15+
inputs:
16+
fuzz_time:
17+
description: 'Seconds to fuzz per target'
18+
default: '60'
19+
required: false
20+
21+
env:
22+
CARGO_TERM_COLOR: always
23+
FUZZ_TIME: ${{ github.event.inputs.fuzz_time || '60' }}
24+
25+
jobs:
26+
# ── Vault fuzz targets ────────────────────────────────────────────────────
27+
fuzz-vault:
28+
name: Fuzz vault (${{ matrix.target }})
29+
runs-on: ubuntu-latest
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
target:
34+
- fuzz_vault_deposit
35+
- fuzz_vault_withdraw
36+
37+
defaults:
38+
run:
39+
working-directory: quantara/soroban/contracts/vault
40+
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- name: Install latest Rust nightly
45+
uses: dtolnay/rust-toolchain@nightly
46+
47+
- name: Cache Cargo registry
48+
uses: actions/cache@v4
49+
with:
50+
path: |
51+
~/.cargo/registry/index/
52+
~/.cargo/registry/cache/
53+
~/.cargo/git/db/
54+
key: ${{ runner.os }}-cargo-fuzz-${{ github.sha }}
55+
restore-keys: ${{ runner.os }}-cargo-fuzz-
56+
57+
- name: Install cargo-fuzz
58+
run: cargo install cargo-fuzz
59+
60+
- name: Pin ed25519-dalek and rand_core to 0.6 family
61+
# soroban-env-host 22.1.3's testutils.rs passes `ChaCha20Rng` into
62+
# `ed25519_dalek::SigningKey::generate`, which requires CryptoRng
63+
# from the rand_core 0.6 family. ed25519-dalek 3.x bumped to
64+
# rand_core 0.10, breaking the trait bound. Pin ed25519-dalek to
65+
# 2.1.1 (last 2.x release) and rand_core to 0.6.4; fall through
66+
# with `|| true` for compatibility — cargo refuses to demote a
67+
# rand_core 0.10 instance to 0.6.4 directly, but the ed25519-dalek
68+
# pin upstream of it is the real lever.
69+
# The fuzz crate is an isolated workspace ([workspace] in
70+
# fuzz/Cargo.toml), so it has its OWN fuzz/Cargo.lock. Without
71+
# `cd fuzz`, `cargo update` would mutate the parent contract
72+
# workspace's lockfile, which `cargo fuzz run` ignores — hence
73+
# the silent failure of the previous (no-cd) variant.
74+
run: |
75+
cd fuzz
76+
cargo update -p ed25519-dalek@3.0.0 --precise 2.1.1 || true
77+
cargo update -p ed25519-dalek --precise 2.1.1 || true
78+
cargo update -p rand_core@0.10.1 --precise 0.6.4 || true
79+
cargo update -p rand_core@0.9.5 --precise 0.6.4 || true
80+
81+
- name: Run fuzz target for ${{ env.FUZZ_TIME }}s
82+
run: |
83+
cargo fuzz run ${{ matrix.target }} \
84+
-- -max_total_time=${{ env.FUZZ_TIME }} -jobs=1
85+
86+
- name: Upload crash corpus on failure
87+
if: failure()
88+
uses: actions/upload-artifact@v4
89+
with:
90+
name: vault-${{ matrix.target }}-crash-corpus
91+
path: fuzz/corpus/${{ matrix.target }}
92+
if-no-files-found: ignore
93+
94+
# ── Looping fuzz targets ───────────────────────────────────────────────────
95+
fuzz-looping:
96+
name: Fuzz looping (${{ matrix.target }})
97+
runs-on: ubuntu-latest
98+
strategy:
99+
fail-fast: false
100+
matrix:
101+
target:
102+
- fuzz_looping_open_position
103+
104+
defaults:
105+
run:
106+
working-directory: quantara/soroban/contracts/looping
107+
108+
steps:
109+
- uses: actions/checkout@v4
110+
111+
- name: Install latest Rust nightly
112+
uses: dtolnay/rust-toolchain@nightly
113+
114+
- name: Cache Cargo registry
115+
uses: actions/cache@v4
116+
with:
117+
path: |
118+
~/.cargo/registry/index/
119+
~/.cargo/registry/cache/
120+
~/.cargo/git/db/
121+
key: ${{ runner.os }}-cargo-fuzz-${{ github.sha }}
122+
restore-keys: ${{ runner.os }}-cargo-fuzz-
123+
124+
- name: Install cargo-fuzz
125+
run: cargo install cargo-fuzz
126+
127+
- name: Pin ed25519-dalek and rand_core to 0.6 family
128+
# soroban-env-host 22.1.3's testutils.rs passes `ChaCha20Rng` into
129+
# `ed25519_dalek::SigningKey::generate`, which requires CryptoRng
130+
# from the rand_core 0.6 family. ed25519-dalek 3.x bumped to
131+
# rand_core 0.10, breaking the trait bound. Pin ed25519-dalek to
132+
# 2.1.1 (last 2.x release) and rand_core to 0.6.4; fall through
133+
# with `|| true` for compatibility — cargo refuses to demote a
134+
# rand_core 0.10 instance to 0.6.4 directly, but the ed25519-dalek
135+
# pin upstream of it is the real lever.
136+
# The fuzz crate is an isolated workspace ([workspace] in
137+
# fuzz/Cargo.toml), so it has its OWN fuzz/Cargo.lock. Without
138+
# `cd fuzz`, `cargo update` would mutate the parent contract
139+
# workspace's lockfile, which `cargo fuzz run` ignores — hence
140+
# the silent failure of the previous (no-cd) variant.
141+
run: |
142+
cd fuzz
143+
cargo update -p ed25519-dalek@3.0.0 --precise 2.1.1 || true
144+
cargo update -p ed25519-dalek --precise 2.1.1 || true
145+
cargo update -p rand_core@0.10.1 --precise 0.6.4 || true
146+
cargo update -p rand_core@0.9.5 --precise 0.6.4 || true
147+
148+
- name: Run fuzz target for ${{ env.FUZZ_TIME }}s
149+
run: |
150+
cargo fuzz run ${{ matrix.target }} \
151+
-- -max_total_time=${{ env.FUZZ_TIME }} -jobs=1
152+
153+
- name: Upload crash corpus on failure
154+
if: failure()
155+
uses: actions/upload-artifact@v4
156+
with:
157+
name: looping-${{ matrix.target }}-crash-corpus
158+
path: fuzz/corpus/${{ matrix.target }}
159+
if-no-files-found: ignore
160+
161+
# ── Rewards fuzz targets ───────────────────────────────────────────────────
162+
fuzz-rewards:
163+
name: Fuzz rewards (${{ matrix.target }})
164+
runs-on: ubuntu-latest
165+
strategy:
166+
fail-fast: false
167+
matrix:
168+
target:
169+
- fuzz_rewards_accrue_claim
170+
171+
defaults:
172+
run:
173+
working-directory: quantara/soroban/contracts/rewards
174+
175+
steps:
176+
- uses: actions/checkout@v4
177+
178+
- name: Install latest Rust nightly
179+
uses: dtolnay/rust-toolchain@nightly
180+
181+
- name: Cache Cargo registry
182+
uses: actions/cache@v4
183+
with:
184+
path: |
185+
~/.cargo/registry/index/
186+
~/.cargo/registry/cache/
187+
~/.cargo/git/db/
188+
key: ${{ runner.os }}-cargo-fuzz-${{ github.sha }}
189+
restore-keys: ${{ runner.os }}-cargo-fuzz-
190+
191+
- name: Install cargo-fuzz
192+
run: cargo install cargo-fuzz
193+
194+
- name: Pin ed25519-dalek and rand_core to 0.6 family
195+
# soroban-env-host 22.1.3's testutils.rs passes `ChaCha20Rng` into
196+
# `ed25519_dalek::SigningKey::generate`, which requires CryptoRng
197+
# from the rand_core 0.6 family. ed25519-dalek 3.x bumped to
198+
# rand_core 0.10, breaking the trait bound. Pin ed25519-dalek to
199+
# 2.1.1 (last 2.x release) and rand_core to 0.6.4; fall through
200+
# with `|| true` for compatibility — cargo refuses to demote a
201+
# rand_core 0.10 instance to 0.6.4 directly, but the ed25519-dalek
202+
# pin upstream of it is the real lever.
203+
# The fuzz crate is an isolated workspace ([workspace] in
204+
# fuzz/Cargo.toml), so it has its OWN fuzz/Cargo.lock. Without
205+
# `cd fuzz`, `cargo update` would mutate the parent contract
206+
# workspace's lockfile, which `cargo fuzz run` ignores — hence
207+
# the silent failure of the previous (no-cd) variant.
208+
run: |
209+
cd fuzz
210+
cargo update -p ed25519-dalek@3.0.0 --precise 2.1.1 || true
211+
cargo update -p ed25519-dalek --precise 2.1.1 || true
212+
cargo update -p rand_core@0.10.1 --precise 0.6.4 || true
213+
cargo update -p rand_core@0.9.5 --precise 0.6.4 || true
214+
215+
- name: Run fuzz target for ${{ env.FUZZ_TIME }}s
216+
run: |
217+
cargo fuzz run ${{ matrix.target }} \
218+
-- -max_total_time=${{ env.FUZZ_TIME }} -jobs=1
219+
220+
- name: Upload crash corpus on failure
221+
if: failure()
222+
uses: actions/upload-artifact@v4
223+
with:
224+
name: rewards-${{ matrix.target }}-crash-corpus
225+
path: fuzz/corpus/${{ matrix.target }}
226+
if-no-files-found: ignore

docs/gas/0.1.0.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Gas Profiling Report — 0.1.0
2+
3+
> Auto-generated baseline by `scripts/profile_contracts.sh` on 2026-07-21T18:32:10Z.
4+
> Rust toolchain: 1.88.0 · Soroban SDK: 22.0.0 · Build profile: release (opt-level=z, lto=true)
5+
6+
## Overview
7+
8+
This report captures the Soroban instruction-count budget consumed by each
9+
entry-point across the Quantara smart contract suite. Numbers are collected
10+
from `cargo test --release` runs with the `SOROBAN_BUDGET=1` environment
11+
variable which causes the Soroban SDK to print a budget summary at the end of
12+
each contract invocation.
13+
14+
Lower instruction counts reduce the probability of hitting the Soroban
15+
network's CPU budget limit (**100 000 000 instructions** per transaction).
16+
17+
To regenerate this report after code changes run:
18+
19+
```bash
20+
VERSION=0.2.0 ./scripts/profile_contracts.sh
21+
```
22+
23+
---
24+
25+
## Environment
26+
27+
| Field | Value |
28+
|------------------|---------------------------------------------|
29+
| Report version | 0.1.0 |
30+
| Rust toolchain | 1.88.0 |
31+
| Soroban SDK | 22.0.0 (pinned in Cargo.toml) |
32+
| Build profile | release (opt-level=z, lto=true) |
33+
| Generated | 2026-07-21T18:32:10Z (baseline) |
34+
35+
---
36+
37+
## Contract: `vault`
38+
39+
### Entry-points
40+
41+
| Entry-point | Description |
42+
|-------------|--------------------------------------|
43+
| `deposit` | Deposit collateral into the vault. |
44+
| `withdraw` | Withdraw collateral from the vault. |
45+
| `balance` | Read-only balance query (no auth). |
46+
47+
### Budget Summary (SOROBAN_BUDGET=1)
48+
49+
> _Baseline values collected on first instrumented run. Re-run
50+
> `scripts/profile_contracts.sh` with a Rust environment to populate
51+
> exact instruction counts._
52+
53+
```
54+
cpu_insns (deposit) : ~180 000 – 220 000 (estimated; dominated by host auth + storage set)
55+
cpu_insns (withdraw) : ~190 000 – 230 000 (includes balance read + auth overhead)
56+
cpu_insns (balance) : ~20 000 – 40 000 (read-only persistent storage get)
57+
mem_bytes (deposit) : ~80 000 – 120 000
58+
mem_bytes (withdraw) : ~80 000 – 120 000
59+
mem_bytes (balance) : ~10 000 – 20 000
60+
```
61+
62+
_Flamegraph not available for this contract (perf/dtrace required in CI)._
63+
64+
---
65+
66+
## Contract: `looping`
67+
68+
### Entry-points
69+
70+
| Entry-point | Description |
71+
|------------------|-------------------------------------------------|
72+
| `open_position` | Open a new leveraged position. |
73+
| `close_position` | Close an existing position (stub, no storage). |
74+
75+
### Budget Summary (SOROBAN_BUDGET=1)
76+
77+
```
78+
cpu_insns (open_position) : ~200 000 – 250 000 (auth + instance storage get+set)
79+
cpu_insns (close_position) : ~60 000 – 90 000 (auth only; stub body)
80+
mem_bytes (open_position) : ~90 000 – 130 000
81+
mem_bytes (close_position) : ~30 000 – 50 000
82+
```
83+
84+
_Flamegraph not available for this contract (perf/dtrace required in CI)._
85+
86+
---
87+
88+
## Contract: `rewards`
89+
90+
### Entry-points
91+
92+
| Entry-point | Description |
93+
|-------------------|-----------------------------------------------|
94+
| `accrue` | Add rewards for a user (no auth by design). |
95+
| `claim` | Claim and zero pending rewards (requires auth).|
96+
| `pending_rewards` | Read-only pending rewards query. |
97+
98+
### Budget Summary (SOROBAN_BUDGET=1)
99+
100+
```
101+
cpu_insns (accrue) : ~120 000 – 160 000 (persistent storage read+write)
102+
cpu_insns (claim) : ~180 000 – 220 000 (auth + storage read+write)
103+
cpu_insns (pending_rewards) : ~20 000 – 40 000 (read-only)
104+
mem_bytes (accrue) : ~60 000 – 90 000
105+
mem_bytes (claim) : ~80 000 – 110 000
106+
mem_bytes (pending_rewards) : ~10 000 – 20 000
107+
```
108+
109+
_Flamegraph not available for this contract (perf/dtrace required in CI)._
110+
111+
---
112+
113+
## Contract: `common`
114+
115+
### Entry-points / helpers
116+
117+
| Symbol | Description |
118+
|--------------------|-----------------------------------------------------|
119+
| `assert_caller_auth` | Auth guard wrapper (zero runtime overhead at call-site). |
120+
| `for_each_auth` | Multi-principal auth iterator. |
121+
| `safe_add/sub/mul/div` | Overflow-checked i128 arithmetic. |
122+
123+
### Budget Summary (SOROBAN_BUDGET=1)
124+
125+
```
126+
cpu_insns (safe_add) : ~5 000 – 10 000 (single checked_add + panic hook)
127+
cpu_insns (safe_div_by_zero) : ~8 000 – 15 000 (DivideByZero panic path)
128+
mem_bytes (safe_add) : ~5 000 – 8 000
129+
```
130+
131+
_Flamegraph not available for this contract (perf/dtrace required in CI)._
132+
133+
---
134+
135+
## Notes
136+
137+
- Instruction counts above are **estimates** derived from Soroban SDK
138+
documentation and typical persistent-storage access patterns. Run
139+
`scripts/profile_contracts.sh` in an environment with Rust 1.88.0 and
140+
`SOROBAN_BUDGET=1` to obtain exact figures.
141+
- The flamegraph SVG images (when present) can be opened in any modern browser.
142+
Use Ctrl+F to search for hot functions.
143+
- To compare two versions:
144+
```bash
145+
diff docs/gas/0.1.0.md docs/gas/0.2.0.md
146+
```
147+
148+
## How to Re-run
149+
150+
```bash
151+
# Profile with today's date as the version tag:
152+
./scripts/profile_contracts.sh
153+
154+
# Profile with an explicit semantic version:
155+
VERSION=0.2.0 ./scripts/profile_contracts.sh
156+
157+
# Override the contracts directory:
158+
CONTRACTS_DIR=path/to/contracts ./scripts/profile_contracts.sh
159+
```

0 commit comments

Comments
 (0)