Skip to content

Commit 64fd60c

Browse files
authored
ci(#291): add cargo fmt, clippy, and deny checks to soroban CI (#325)
* ci(#291): add cargo fmt, clippy, and deny checks to soroban CI Add .github/workflows/soroban.yml with four jobs that run on every pull request and push touching quantara/soroban/contracts/** or the workflow file itself: - fmt: cargo fmt --all -- --check (fails on unformatted code) - clippy: cargo clippy --all-targets --all-features -- -D warnings - deny: EmbarkStudios/cargo-deny-action@v2 with deny.toml policy - build: cargo build --target wasm32-unknown-unknown --release (gated on fmt + clippy + deny passing) Also bootstraps the Soroban Rust workspace that the CI targets: - quantara/soroban/contracts/Cargo.toml (workspace: looping, vault, rewards) - quantara/soroban/contracts/rustfmt.toml - quantara/soroban/contracts/deny.toml (licenses, advisories, bans, sources) - looping/ vault/ rewards/ (Cargo.toml + src/lib.rs stubs) Closes #291 * fix(ci): bump toolchain to 1.85.0 and fix deny.toml schema Two CI failures from PR #325: 1. clippy: digest v0.11.3 (transitive soroban-sdk dep) requires the edition2024 Cargo feature, which is only stable from Rust 1.85.0. Bumped RUST_TOOLCHAIN from 1.82.0 to 1.85.0 across all four jobs. 2. deny: cargo-deny v0.14+ (used by EmbarkStudios/cargo-deny-action@v2) removed the keys unlicensed, copyleft, allow-osi-fsf-free, default, and deny from [licenses] in PR #611. All licenses not in [allow] are now denied by default. Rewrote deny.toml to use only the current schema fields (allow, unused-allowed-license, clarify). * fix(ci): bump toolchain to 1.88.0; add Unicode-3.0 to deny allow-list Two more CI failures on run #29618318865: 1. clippy: darling 0.23.0, darling_core/macro 0.23.0, and serde_with 3.21.0 (transitive soroban-sdk testutils deps) require rustc >= 1.88. Bumped RUST_TOOLCHAIN from 1.85.0 to 1.88.0. 2. deny: unicode-ident declares license '(MIT OR Apache-2.0) AND Unicode-3.0'. Unicode-3.0 was not in the allow list. Added it — it is a permissive OSI-approved license used by the Unicode Consortium. * fix(ci): remove testutils/alloc features to fix rand_core version conflict clippy failure on run #29618382503: soroban-env-host v22.1.3 (pulled in by soroban-sdk testutils feature) has an incompatible rand_core dependency mix: it requires rand_core 0.6.4 (via rand_chacha/ChaCha20Rng) but also pulls rand_core 0.10.1 (via ed25519_dalek), causing a trait bound failure on ChaCha20Rng: CryptoRng. Fix: remove the testutils and alloc features from all three contract Cargo.toml files. The stubs do not use the test harness; the base soroban-sdk without testutils resolves cleanly. Also remove the stale Cargo.lock so it regenerates against the new leaner dependency set on the next cargo invocation. * fix(ci): replace I128 with i128 in all contract stubs soroban_sdk::I128 does not exist in soroban-sdk v22 without the alloc feature. Soroban contracts use native Rust primitive types (i128, u64, etc.) directly. Replaced all three I128 usages with i128 across looping, vault, and rewards contracts.
1 parent f19a241 commit 64fd60c

10 files changed

Lines changed: 421 additions & 0 deletions

File tree

.github/workflows/soroban.yml

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
name: Soroban Contracts CI
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: true
6+
7+
on:
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- 'quantara/soroban/contracts/**'
13+
- '.github/workflows/soroban.yml'
14+
pull_request:
15+
branches:
16+
- main
17+
paths:
18+
- 'quantara/soroban/contracts/**'
19+
- '.github/workflows/soroban.yml'
20+
21+
env:
22+
CARGO_TERM_COLOR: always
23+
# 1.88.0 is required by transitive soroban-sdk deps:
24+
# - edition2024 Cargo feature (stable since 1.85.0)
25+
# - darling 0.23.0 and serde_with 3.21.0 require rustc >= 1.88.0
26+
RUST_TOOLCHAIN: "1.88.0"
27+
28+
defaults:
29+
run:
30+
working-directory: quantara/soroban/contracts
31+
32+
jobs:
33+
# ── 1. Formatting ───────────────────────────────────────────────────────────
34+
fmt:
35+
name: Formatting (cargo fmt --check)
36+
runs-on: ubuntu-latest
37+
permissions:
38+
contents: read
39+
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v7
43+
44+
- name: Install Rust toolchain
45+
uses: dtolnay/rust-toolchain@stable
46+
with:
47+
toolchain: ${{ env.RUST_TOOLCHAIN }}
48+
targets: wasm32-unknown-unknown
49+
components: rustfmt
50+
51+
- name: Cache Cargo registry & build artifacts
52+
uses: actions/cache@v4
53+
with:
54+
path: |
55+
~/.cargo/registry/index/
56+
~/.cargo/registry/cache/
57+
~/.cargo/git/db/
58+
quantara/soroban/contracts/target/
59+
key: ${{ runner.os }}-cargo-fmt-${{ hashFiles('quantara/soroban/contracts/Cargo.toml', 'quantara/soroban/contracts/**/Cargo.toml') }}
60+
restore-keys: |
61+
${{ runner.os }}-cargo-fmt-
62+
63+
- name: Check formatting
64+
run: cargo fmt --all -- --check
65+
66+
# ── 2. Lint ─────────────────────────────────────────────────────────────────
67+
clippy:
68+
name: Lint (cargo clippy -- -D warnings)
69+
runs-on: ubuntu-latest
70+
permissions:
71+
contents: read
72+
73+
steps:
74+
- name: Checkout repository
75+
uses: actions/checkout@v7
76+
77+
- name: Install Rust toolchain
78+
uses: dtolnay/rust-toolchain@stable
79+
with:
80+
toolchain: ${{ env.RUST_TOOLCHAIN }}
81+
targets: wasm32-unknown-unknown
82+
components: clippy
83+
84+
- name: Cache Cargo registry & build artifacts
85+
uses: actions/cache@v4
86+
with:
87+
path: |
88+
~/.cargo/registry/index/
89+
~/.cargo/registry/cache/
90+
~/.cargo/git/db/
91+
quantara/soroban/contracts/target/
92+
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('quantara/soroban/contracts/Cargo.toml', 'quantara/soroban/contracts/**/Cargo.toml') }}
93+
restore-keys: |
94+
${{ runner.os }}-cargo-clippy-
95+
96+
- name: Run Clippy (deny all warnings)
97+
run: cargo clippy --all-targets --all-features -- -D warnings
98+
99+
# ── 3. Dependency audit ──────────────────────────────────────────────────────
100+
deny:
101+
name: Dependency audit (cargo deny check)
102+
runs-on: ubuntu-latest
103+
permissions:
104+
contents: read
105+
106+
steps:
107+
- name: Checkout repository
108+
uses: actions/checkout@v7
109+
110+
- name: Run cargo deny
111+
uses: EmbarkStudios/cargo-deny-action@v2
112+
with:
113+
rust-version: ${{ env.RUST_TOOLCHAIN }}
114+
command: check
115+
manifest-path: quantara/soroban/contracts/Cargo.toml
116+
arguments: --config quantara/soroban/contracts/deny.toml
117+
118+
# ── 4. Build ────────────────────────────────────────────────────────────────
119+
build:
120+
name: Build (wasm32-unknown-unknown)
121+
runs-on: ubuntu-latest
122+
permissions:
123+
contents: read
124+
needs: [fmt, clippy, deny]
125+
126+
steps:
127+
- name: Checkout repository
128+
uses: actions/checkout@v7
129+
130+
- name: Install Rust toolchain
131+
uses: dtolnay/rust-toolchain@stable
132+
with:
133+
toolchain: ${{ env.RUST_TOOLCHAIN }}
134+
targets: wasm32-unknown-unknown
135+
136+
- name: Cache Cargo registry & build artifacts
137+
uses: actions/cache@v4
138+
with:
139+
path: |
140+
~/.cargo/registry/index/
141+
~/.cargo/registry/cache/
142+
~/.cargo/git/db/
143+
quantara/soroban/contracts/target/
144+
key: ${{ runner.os }}-cargo-build-${{ hashFiles('quantara/soroban/contracts/Cargo.toml', 'quantara/soroban/contracts/**/Cargo.toml') }}
145+
restore-keys: |
146+
${{ runner.os }}-cargo-build-
147+
148+
- name: Build contracts
149+
run: cargo build --target wasm32-unknown-unknown --release
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[workspace]
2+
resolver = "2"
3+
members = [
4+
"looping",
5+
"vault",
6+
"rewards",
7+
]
8+
9+
[profile.release]
10+
opt-level = "z"
11+
overflow-checks = true
12+
debug = 0
13+
strip = "symbols"
14+
debug-assertions = false
15+
panic = "abort"
16+
codegen-units = 1
17+
lto = true
18+
19+
[profile.release-with-logs]
20+
inherits = "release"
21+
debug-assertions = true
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# cargo-deny configuration for Quantara Soroban contracts
2+
# Compatible with cargo-deny v0.14+ (EmbarkStudios/cargo-deny-action@v2)
3+
# Keys removed in PR #611 (unlicensed, copyleft, allow-osi-fsf-free, default, deny)
4+
# have been dropped — all licenses not in [licenses.allow] are denied by default.
5+
6+
# ── Graph ─────────────────────────────────────────────────────────────────────
7+
[graph]
8+
targets = [
9+
{ triple = "wasm32-unknown-unknown" },
10+
]
11+
12+
# ── Advisories ────────────────────────────────────────────────────────────────
13+
[advisories]
14+
version = 2
15+
db-urls = ["https://github.com/rustsec/advisory-db"]
16+
yanked = "deny"
17+
18+
# ── Licenses ──────────────────────────────────────────────────────────────────
19+
# All licenses not in [allow] are denied by default (no extra keys needed).
20+
[licenses]
21+
version = 2
22+
allow = [
23+
"MIT",
24+
"Apache-2.0",
25+
"Apache-2.0 WITH LLVM-exception",
26+
"ISC",
27+
"BSD-2-Clause",
28+
"BSD-3-Clause",
29+
"Unicode-DFS-2016",
30+
"Unicode-3.0",
31+
"CC0-1.0",
32+
"0BSD",
33+
"Zlib",
34+
]
35+
unused-allowed-license = "allow"
36+
37+
[[licenses.clarify]]
38+
name = "ring"
39+
expression = "MIT AND ISC AND OpenSSL"
40+
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
41+
42+
# ── Bans ──────────────────────────────────────────────────────────────────────
43+
[bans]
44+
multiple-versions = "warn"
45+
wildcards = "deny"
46+
highlight = "all"
47+
48+
# ── Sources ───────────────────────────────────────────────────────────────────
49+
[sources]
50+
unknown-registry = "deny"
51+
unknown-git = "deny"
52+
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "looping"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
publish = false
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
doctest = false
11+
12+
[dependencies]
13+
soroban-sdk = { version = "22.0.0" }
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Looping contract - leverage loop engine for the Quantara protocol.
2+
//!
3+
//! This contract allows users to create and manage leveraged positions on
4+
//! the Stellar network by automating the borrow->swap->redeposit loop.
5+
6+
#![no_std]
7+
8+
use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env};
9+
10+
/// Quantara looping contract.
11+
#[contract]
12+
pub struct LoopingContract;
13+
14+
#[contractimpl]
15+
impl LoopingContract {
16+
/// Open a leveraged position.
17+
///
18+
/// # Arguments
19+
/// * `env` - The Soroban environment.
20+
/// * `user` - The wallet address opening the position.
21+
/// * `collateral` - The amount of collateral to deposit (in base units).
22+
/// * `leverage` - The desired leverage multiplier (1-5, scaled x100).
23+
///
24+
/// # Returns
25+
/// The position ID assigned to this new position.
26+
pub fn open_position(env: Env, user: Address, collateral: i128, leverage: u32) -> u64 {
27+
user.require_auth();
28+
29+
assert!(collateral > 0, "collateral must be positive");
30+
assert!(
31+
(100..=500).contains(&leverage),
32+
"leverage must be 1x-5x (100-500)"
33+
);
34+
35+
let key = symbol_short!("pos_cnt");
36+
let count: u64 = env.storage().instance().get(&key).unwrap_or(0u64);
37+
let position_id = count + 1;
38+
env.storage().instance().set(&key, &position_id);
39+
40+
position_id
41+
}
42+
43+
/// Close an existing leveraged position.
44+
///
45+
/// # Arguments
46+
/// * `env` - The Soroban environment.
47+
/// * `user` - The wallet address that owns the position.
48+
/// * `position_id` - The ID of the position to close.
49+
pub fn close_position(_env: Env, user: Address, _position_id: u64) {
50+
user.require_auth();
51+
// Stub: full unwind logic will be implemented in a future PR.
52+
}
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "rewards"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
publish = false
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
doctest = false
11+
12+
[dependencies]
13+
soroban-sdk = { version = "22.0.0" }
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Rewards contract - reward distribution for the Quantara protocol.
2+
//!
3+
//! Accumulates protocol fees and distributes them to liquidity providers
4+
//! proportionally to their share of the vault.
5+
6+
#![no_std]
7+
8+
use soroban_sdk::{contract, contractimpl, Address, Env};
9+
10+
/// Quantara rewards contract.
11+
#[contract]
12+
pub struct RewardsContract;
13+
14+
#[contractimpl]
15+
impl RewardsContract {
16+
/// Accrue rewards for a user based on their position size.
17+
///
18+
/// # Arguments
19+
/// * `env` - The Soroban environment.
20+
/// * `user` - The wallet address to accrue rewards for.
21+
/// * `accrual` - The reward amount to add (in base units, must be >= 0).
22+
pub fn accrue(env: Env, user: Address, accrual: i128) {
23+
assert!(accrual >= 0, "accrual must be non-negative");
24+
25+
let pending: i128 = env.storage().persistent().get(&user).unwrap_or(0i128);
26+
env.storage().persistent().set(&user, &(pending + accrual));
27+
}
28+
29+
/// Claim all pending rewards for a user.
30+
///
31+
/// # Arguments
32+
/// * `env` - The Soroban environment.
33+
/// * `user` - The wallet address claiming rewards.
34+
///
35+
/// # Returns
36+
/// The total amount of rewards claimed.
37+
pub fn claim(env: Env, user: Address) -> i128 {
38+
user.require_auth();
39+
40+
let pending: i128 = env.storage().persistent().get(&user).unwrap_or(0i128);
41+
42+
// Reset pending balance and return claimed amount.
43+
env.storage().persistent().set(&user, &0i128);
44+
pending
45+
}
46+
47+
/// Query pending rewards for a user without claiming.
48+
pub fn pending_rewards(env: Env, user: Address) -> i128 {
49+
env.storage().persistent().get(&user).unwrap_or(0i128)
50+
}
51+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
edition = "2021"
2+
max_width = 100
3+
tab_spaces = 4
4+
newline_style = "Unix"
5+
use_small_heuristics = "Default"
6+
reorder_imports = true
7+
reorder_modules = true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "vault"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
publish = false
7+
8+
[lib]
9+
crate-type = ["cdylib"]
10+
doctest = false
11+
12+
[dependencies]
13+
soroban-sdk = { version = "22.0.0" }

0 commit comments

Comments
 (0)