Skip to content

Commit 01a22b4

Browse files
committed
feat: add root npm test to CI, agent audit gate, dependency policy, and deposit balance assertions
- #173: add test-root CI job that runs npm ci + npm test at the repository root so the root jest suite is gated on every push and PR - #176: add npm audit --audit-level=high step to the test-agent CI job; blocks merge on any high or critical vulnerability in agent dependencies - #182: add docs/dependency-update-policy.md documenting update cadence, process, and exception handling; add .github/dependabot.yml for automated weekly dependency PRs across npm (root, agent, frontend) and Cargo workspaces - #165: add explicit InsufficientBalance assertion in deposit() and transfer_usdc_from_user() before each token transfer; add no-op approve() to StatefulMockToken so it can be used in LP-flow tests; update test_soroswap_integration, test_withdraw_unwinds_blend_and_lp, and test_gold_allocation_tracking to use StatefulMockToken with proper minting; add test_deposit_rejects_insufficient_balance and test_supply_to_blend_rejects_insufficient_balance Closes #165 Closes #173 Closes #176 Closes #182
1 parent d39d84c commit 01a22b4

4 files changed

Lines changed: 179 additions & 4 deletions

File tree

.github/dependabot.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
open-pull-requests-limit: 5
8+
labels:
9+
- "dependencies"
10+
11+
- package-ecosystem: "npm"
12+
directory: "/agent"
13+
schedule:
14+
interval: "weekly"
15+
open-pull-requests-limit: 5
16+
labels:
17+
- "dependencies"
18+
19+
- package-ecosystem: "npm"
20+
directory: "/frontend"
21+
schedule:
22+
interval: "weekly"
23+
open-pull-requests-limit: 5
24+
labels:
25+
- "dependencies"
26+
27+
- package-ecosystem: "cargo"
28+
directory: "/contracts"
29+
schedule:
30+
interval: "weekly"
31+
open-pull-requests-limit: 5
32+
labels:
33+
- "dependencies"

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ on:
77
branches: ["main", "master"]
88

99
jobs:
10+
# #173 — run root-level jest suite
11+
test-root:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Setup Node
16+
uses: actions/setup-node@v4
17+
with:
18+
node-version: 20
19+
cache: "npm"
20+
cache-dependency-path: "./package-lock.json"
21+
- run: npm ci
22+
- run: npm test
23+
1024
test-and-build-frontend:
1125
runs-on: ubuntu-latest
1226
defaults:
@@ -35,6 +49,9 @@ jobs:
3549
with:
3650
node-version: 20
3751
- run: npm install
52+
# #176 — gate on high-severity vulnerabilities in agent dependencies
53+
- name: Audit agent dependencies (high gate)
54+
run: npm audit --audit-level=high
3855
- run: npm test --ignore-scripts || echo "Agent tests completed"
3956

4057
build-contracts:

contracts/src/lib.rs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,13 @@ impl SmasageYieldRouter {
360360
let usdc_token =
361361
Self::get_usdc_token(env.clone()).ok_or(ContractError::UsdcTokenNotInitialized)?;
362362
let token_client = TokenClient::new(env, &usdc_token);
363+
364+
// Issue #165: Assert sender has sufficient balance before initiating transfer
365+
let sender_balance = token_client.balance(from);
366+
if sender_balance < amount {
367+
return Err(ContractError::InsufficientBalance);
368+
}
369+
363370
token_client.transfer(from, &env.current_contract_address(), &amount);
364371
Ok(())
365372
}
@@ -590,6 +597,13 @@ impl SmasageYieldRouter {
590597
.get(&DataKey::UsdcToken)
591598
.ok_or(ContractError::UsdcTokenNotInitialized)?;
592599
let usdc = TokenClient::new(&env, &usdc_addr);
600+
601+
// Issue #165: Assert sender has sufficient token balance before deposit transfer
602+
let user_balance = usdc.balance(&from);
603+
if user_balance < amount {
604+
return Err(ContractError::InsufficientBalance);
605+
}
606+
593607
usdc.transfer(&from, &env.current_contract_address(), &amount);
594608

595609
let mut balance: i128 = env
@@ -1045,13 +1059,17 @@ mod test {
10451059
let admin = Address::generate(&env);
10461060
let user = Address::generate(&env);
10471061

1048-
// Register mocks
1062+
// Use StatefulMockToken for USDC so balance assertions in deposit pass
10491063
let router_id = env.register(MockRouter, ());
1050-
let usdc_id = env.register(MockToken, ());
1064+
let usdc_id = env.register(StatefulMockToken, ());
10511065
let xlm_id = env.register(MockToken, ());
10521066

10531067
env.mock_all_auths();
10541068

1069+
// Mint enough USDC for two 1000-unit deposits
1070+
StatefulMockTokenClient::new(&env, &usdc_id).initialize(&user);
1071+
StatefulMockTokenClient::new(&env, &usdc_id).mint(&user, &2000);
1072+
10551073
client.initialize(&admin);
10561074
client.initialize_soroswap(&admin, &router_id, &usdc_id, &xlm_id);
10571075

@@ -1075,10 +1093,14 @@ mod test {
10751093
let admin = Address::generate(&env);
10761094
let user = Address::generate(&env);
10771095
let router = env.register(MockRouter, ());
1078-
let usdc = env.register(MockToken, ());
1096+
let usdc = env.register(StatefulMockToken, ());
10791097
let xlm = env.register(MockToken, ());
10801098
env.mock_all_auths();
10811099

1100+
// Mint enough USDC for the single 1000-unit deposit
1101+
StatefulMockTokenClient::new(&env, &usdc).initialize(&user);
1102+
StatefulMockTokenClient::new(&env, &usdc).mint(&user, &1000);
1103+
10821104
client.initialize(&admin);
10831105
client.initialize_soroswap(&admin, &router, &usdc, &xlm);
10841106

@@ -1107,10 +1129,14 @@ mod test {
11071129
let admin = Address::generate(&env);
11081130
let user = Address::generate(&env);
11091131
let router = env.register(MockRouter, ());
1110-
let usdc = env.register(MockToken, ());
1132+
let usdc = env.register(StatefulMockToken, ());
11111133
let xlm = env.register(MockToken, ());
11121134
env.mock_all_auths();
11131135

1136+
// Mint enough USDC for the 2000-unit deposit
1137+
StatefulMockTokenClient::new(&env, &usdc).initialize(&user);
1138+
StatefulMockTokenClient::new(&env, &usdc).mint(&user, &2000);
1139+
11141140
client.initialize(&admin);
11151141
client.initialize_soroswap(&admin, &router, &usdc, &xlm);
11161142

@@ -1190,6 +1216,16 @@ mod test {
11901216
.get(&TokenDataKey::Balance(id))
11911217
.unwrap_or(0)
11921218
}
1219+
1220+
// no-op approve required when this token is used in LP-flow tests
1221+
pub fn approve(
1222+
_env: Env,
1223+
_from: Address,
1224+
_spender: Address,
1225+
_amount: i128,
1226+
_expiration_ledger: u32,
1227+
) {
1228+
}
11931229
}
11941230
}
11951231

@@ -1955,4 +1991,60 @@ mod test {
19551991
let result = client.try_initialize_soroswap(&admin, &router, &usdc, &xlm);
19561992
assert_eq!(result, Ok(()));
19571993
}
1994+
1995+
// ============================================
1996+
// Issue #165 — Token balance assertions
1997+
// ============================================
1998+
1999+
#[test]
2000+
fn test_deposit_rejects_insufficient_balance() {
2001+
let env = Env::default();
2002+
let contract_id = env.register(SmasageYieldRouter, ());
2003+
let client = SmasageYieldRouterClient::new(&env, &contract_id);
2004+
2005+
let admin = Address::generate(&env);
2006+
let user = Address::generate(&env);
2007+
let router = env.register(MockRouter, ());
2008+
let usdc = env.register(StatefulMockToken, ());
2009+
let xlm = env.register(MockToken, ());
2010+
2011+
env.mock_all_auths();
2012+
2013+
// Mint only 100 USDC — less than the 1000 deposit attempt
2014+
StatefulMockTokenClient::new(&env, &usdc).initialize(&user);
2015+
StatefulMockTokenClient::new(&env, &usdc).mint(&user, &100);
2016+
2017+
client.initialize(&admin);
2018+
client.initialize_soroswap(&admin, &router, &usdc, &xlm);
2019+
2020+
let result = client.try_deposit(&user, &1000, &0, &0, &0);
2021+
assert_eq!(result, Err(Ok(ContractError::InsufficientBalance)));
2022+
}
2023+
2024+
#[test]
2025+
fn test_supply_to_blend_rejects_insufficient_balance() {
2026+
let env = Env::default();
2027+
let contract_id = env.register(SmasageYieldRouter, ());
2028+
let client = SmasageYieldRouterClient::new(&env, &contract_id);
2029+
2030+
let blend_pool_id = env.register(MockBlendPool, ());
2031+
let blend_pool_client = MockBlendPoolClient::new(&env, &blend_pool_id);
2032+
2033+
let token_id = env.register(StatefulMockToken, ());
2034+
let token_client = StatefulMockTokenClient::new(&env, &token_id);
2035+
2036+
let user = Address::generate(&env);
2037+
2038+
env.mock_all_auths();
2039+
2040+
// Mint only 100 USDC — less than the 1000 supply attempt
2041+
token_client.initialize(&user);
2042+
token_client.mint(&user, &100);
2043+
2044+
blend_pool_client.initialize(&INDEX_RATE_PRECISION);
2045+
client.initialize_blend(&blend_pool_id, &token_id);
2046+
2047+
let result = client.try_supply_to_blend(&user, &1000);
2048+
assert_eq!(result, Err(Ok(ContractError::InsufficientBalance)));
2049+
}
19582050
}

docs/dependency-update-policy.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Dependency Update Policy
2+
3+
## Scope
4+
5+
This policy applies to all package managers used in this repository:
6+
- **npm** — root workspace, `agent/`, `frontend/`
7+
- **Cargo**`contracts/`
8+
9+
## Update cadence
10+
11+
| Severity | Action | Timeline |
12+
|----------|--------|----------|
13+
| Critical / High CVE | Patch immediately | Within 2 business days of disclosure |
14+
| Moderate CVE | Schedule patch | Within the next sprint (≤ 2 weeks) |
15+
| Low CVE | Batch with routine updates | Monthly |
16+
| No CVE (minor/patch) | Routine upgrade | Monthly |
17+
| Major version bump | Planned upgrade with testing | Quarterly or as needed |
18+
19+
## Process
20+
21+
1. **Detection** — The `npm audit --audit-level=high` gate in CI blocks merges when any high or critical vulnerability is present in `agent/` dependencies. Developers should also run `npm audit` locally before raising a PR.
22+
2. **Assessment** — Confirm whether the vulnerable code path is reachable in this project. Document findings in the PR description if the upgrade is non-trivial.
23+
3. **Upgrade** — Prefer the minimum version that resolves the issue. Update the lock file (`package-lock.json` / `Cargo.lock`) and run the full test suite locally before opening a PR.
24+
4. **Review** — All dependency upgrades require at least one reviewer approval before merging to `main`.
25+
5. **Lock-file commits** — Always commit updated lock files together with `package.json` / `Cargo.toml` changes so CI operates on a reproducible dependency tree.
26+
27+
## Automation
28+
29+
Dependabot is configured (see `.github/dependabot.yml`) to open weekly PRs for outdated dependencies across npm and Cargo workspaces. Maintainers should review and merge these promptly to keep the audit baseline clean.
30+
31+
## Overrides and exceptions
32+
33+
If a vulnerability cannot be patched immediately (e.g., no fix released yet, or the upgrade is a major breaking change), open a tracking issue labelled `security` and document the accepted risk, the mitigating controls in place, and the expected resolution date.

0 commit comments

Comments
 (0)