Skip to content

Commit 9ad15a4

Browse files
authored
Merge branch 'main' into main
2 parents 549c9b8 + 5ac9d8a commit 9ad15a4

19 files changed

Lines changed: 1366 additions & 26 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
name: Bug Report
3+
about: Report a bug in QuorumCredit
4+
labels: bug
5+
---
6+
7+
**Describe the bug**
8+
A clear description of what the bug is.
9+
10+
**To Reproduce**
11+
Steps to reproduce the behavior:
12+
1.
13+
2.
14+
3.
15+
16+
**Expected behavior**
17+
What you expected to happen.
18+
19+
**Environment**
20+
- OS:
21+
- Rust version:
22+
- Stellar CLI version:
23+
- Network (testnet/mainnet):
24+
25+
**Additional context**
26+
Add any other context or logs here.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
name: Feature Request
3+
about: Suggest a new feature for QuorumCredit
4+
labels: enhancement
5+
---
6+
7+
**Problem / Motivation**
8+
What problem does this feature solve?
9+
10+
**Proposed Solution**
11+
Describe the feature you'd like.
12+
13+
**Alternatives Considered**
14+
Any alternative approaches you've thought about.
15+
16+
**Additional context**
17+
Add any other context, mockups, or references here.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## Description
2+
Brief summary of what this PR does and why.
3+
4+
Closes #
5+
6+
## Type of Change
7+
- [ ] Bug fix
8+
- [ ] New feature
9+
- [ ] Refactor
10+
- [ ] Documentation
11+
- [ ] Infrastructure / CI
12+
13+
## Checklist
14+
- [ ] `cargo check` passes
15+
- [ ] `cargo clippy` passes with no warnings
16+
- [ ] `cargo test` passes
17+
- [ ] New tests added for new functionality
18+
- [ ] Documentation updated if needed

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: 2
2+
3+
updates:
4+
- package-ecosystem: "cargo"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
day: "monday"
9+
open-pull-requests-limit: 10

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ jobs:
6262
uses: dtolnay/rust-toolchain@stable
6363
with:
6464
targets: wasm32-unknown-unknown
65+
targets: wasm32-unknown-unknown
66+
components: rustfmt, clippy
6567

6668
- name: Cache cargo registry
6769
uses: actions/cache@v4

.github/workflows/coverage.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Coverage
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
coverage:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install Rust
16+
uses: dtolnay/rust-toolchain@stable
17+
18+
- name: Install cargo-tarpaulin
19+
run: cargo install cargo-tarpaulin
20+
21+
- name: Run coverage
22+
run: cargo tarpaulin --out Xml --output-dir coverage
23+
24+
- name: Upload to Codecov
25+
uses: codecov/codecov-action@v4
26+
with:
27+
files: coverage/cobertura.xml
28+
token: ${{ secrets.CODECOV_TOKEN }}

Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.PHONY: build test deploy-testnet deploy-mainnet
2+
3+
# ── Config ────────────────────────────────────────────────────────────────────
4+
5+
CONTRACT_DIR := QuorumCredit
6+
WASM_TARGET := wasm32-unknown-unknown
7+
8+
# ── Targets ───────────────────────────────────────────────────────────────────
9+
10+
## Compile the contract (native + WASM release build)
11+
build:
12+
cd $(CONTRACT_DIR) && cargo build --target $(WASM_TARGET) --release
13+
14+
## Run the full test suite
15+
test:
16+
cd $(CONTRACT_DIR) && cargo test
17+
18+
## Deploy to Stellar testnet
19+
deploy-testnet:
20+
stellar contract deploy \
21+
--wasm $(CONTRACT_DIR)/target/$(WASM_TARGET)/release/quorum_credit.wasm \
22+
--network testnet \
23+
--source $(DEPLOYER_SECRET_KEY)
24+
25+
## Deploy to Stellar mainnet — requires interactive confirmation
26+
deploy-mainnet:
27+
@echo "WARNING: You are about to deploy to MAINNET."
28+
@read -p "Are you sure you want to deploy to MAINNET? [y/N]: " confirm && \
29+
[ "$${confirm:-N}" = "y" ] || [ "$${confirm:-N}" = "Y" ] || \
30+
(echo "Deployment aborted."; exit 1)
31+
stellar contract deploy \
32+
--wasm $(CONTRACT_DIR)/target/$(WASM_TARGET)/release/quorum_credit.wasm \
33+
--network mainnet \
34+
--source $(DEPLOYER_SECRET_KEY)

QuorumCredit/src/admin.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::helpers::{config, require_admin_approval, validate_admin_config};
1+
use crate::helpers::{config, extend_ttl, require_admin_approval, validate_admin_config};
22
use crate::types::{Config, DataKey};
33
use soroban_sdk::{symbol_short, Address, BytesN, Env, Vec};
44

@@ -113,7 +113,8 @@ pub fn whitelist_voucher(env: Env, admin_signers: Vec<Address>, voucher: Address
113113
require_admin_approval(&env, &admin_signers);
114114
env.storage()
115115
.persistent()
116-
.set(&DataKey::VoucherWhitelist(voucher), &true);
116+
.set(&DataKey::VoucherWhitelist(voucher.clone()), &true);
117+
extend_ttl(&env, &DataKey::VoucherWhitelist(voucher));
117118
}
118119

119120
pub fn set_fee_treasury(env: Env, admin_signers: Vec<Address>, treasury: Address) {
@@ -153,7 +154,8 @@ pub fn blacklist(env: Env, admin_signers: Vec<Address>, borrower: Address) {
153154
require_admin_approval(&env, &admin_signers);
154155
env.storage()
155156
.persistent()
156-
.set(&DataKey::Blacklisted(borrower), &true);
157+
.set(&DataKey::Blacklisted(borrower.clone()), &true);
158+
extend_ttl(&env, &DataKey::Blacklisted(borrower));
157159
}
158160

159161
pub fn set_config(env: Env, admin_signers: Vec<Address>, config: Config) {

QuorumCredit/src/governance.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::errors::ContractError;
2-
use crate::helpers::{add_slash_balance, config, get_active_loan_record, require_not_paused, validate_loan_active};
3-
use crate::types::{DataKey, SlashVoteRecord, VouchRecord, TimelockProposal, TimelockAction};
2+
use crate::helpers::{
3+
add_slash_balance, config, get_active_loan_record, require_not_paused, validate_loan_active,
4+
};
5+
use crate::types::{DataKey, SlashVoteRecord, TimelockAction, TimelockProposal, VouchRecord};
46
use soroban_sdk::{symbol_short, Address, Env, Vec};
57

68
/// Default quorum: 50% of total vouched stake must approve.
@@ -192,7 +194,7 @@ fn execute_slash(env: &Env, borrower: &Address) -> Result<(), ContractError> {
192194
}
193195

194196
/// ── Issue 109: Slash Proposal Confirmation Window ──
195-
///
197+
///
196198
/// Implements a two-step slash with timelock pattern:
197199
/// 1. propose_slash: Admin creates a proposal, sets execution time (eta)
198200
/// 2. execute_slash_proposal: After delay, anyone can execute
@@ -313,10 +315,7 @@ pub fn cancel_slash_proposal(
313315
.ok_or(ContractError::NoActiveLoan)?;
314316

315317
// Only proposer can cancel
316-
assert!(
317-
caller == proposal.proposer,
318-
"only proposer can cancel"
319-
);
318+
assert!(caller == proposal.proposer, "only proposer can cancel");
320319

321320
if proposal.executed || proposal.cancelled {
322321
return Err(ContractError::SlashAlreadyExecuted);

QuorumCredit/src/helpers.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ use crate::errors::ContractError;
22
use crate::types::{Config, DataKey, LoanRecord};
33
use soroban_sdk::{token, Address, Env, String, Vec};
44

5+
/// Ledgers to live for persistent storage entries (~1 year at ~5s/ledger).
6+
const PERSISTENT_TTL_LEDGERS: u32 = 6_307_200;
7+
8+
/// Extend the TTL of a persistent storage entry after every write.
9+
/// Call this immediately after `env.storage().persistent().set(key, ...)`.
10+
pub fn extend_ttl(env: &Env, key: &DataKey) {
11+
env.storage()
12+
.persistent()
13+
.extend_ttl(key, PERSISTENT_TTL_LEDGERS, PERSISTENT_TTL_LEDGERS);
14+
}
15+
516
/// Returns true if the address is the all-zeros account or contract address.
617
pub fn is_zero_address(env: &Env, addr: &Address) -> bool {
718
// Stellar zero account: all-zero 32-byte ed25519 key
@@ -223,3 +234,34 @@ pub fn validate_admin_config(
223234

224235
Ok(())
225236
}
237+
238+
#[cfg(test)]
239+
mod ttl_tests {
240+
use super::*;
241+
use crate::{QuorumCreditContract, QuorumCreditContractClient};
242+
use soroban_sdk::{testutils::Address as _, Address, Env, Vec};
243+
244+
/// Verify extend_ttl does not panic when called on an existing persistent key.
245+
#[test]
246+
fn test_extend_ttl_does_not_panic() {
247+
let env = Env::default();
248+
env.mock_all_auths();
249+
250+
let contract_id = env.register_contract(None, QuorumCreditContract);
251+
let client = QuorumCreditContractClient::new(&env, &contract_id);
252+
253+
let deployer = Address::generate(&env);
254+
let admin = Address::generate(&env);
255+
let admins = Vec::from_array(&env, [admin.clone()]);
256+
let token = Address::generate(&env);
257+
258+
client.initialize(&deployer, &admins, &1, &token);
259+
260+
// Write a persistent key then call extend_ttl — must not panic.
261+
env.as_contract(&contract_id, || {
262+
let key = DataKey::LoanCount(deployer.clone());
263+
env.storage().persistent().set(&key, &42u32);
264+
extend_ttl(&env, &key);
265+
});
266+
}
267+
}

0 commit comments

Comments
 (0)