Skip to content
Closed
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
311 changes: 111 additions & 200 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,201 +1,112 @@
# Security Audit Checklist
---

This document provides a structured security audit checklist for reviewing the ChainLearn Soroban smart contracts. It covers the three contracts (`learn-token`, `credential-nft`, `progress-tracker`) and the shared utilities package.

## 1. Authorization & Access Control

### 1.1 Authentication Checks

- [ ] Every state-changing function that requires auth calls `require_auth()` on the correct address
- [ ] `require_auth()` is called BEFORE any state reads or writes (first line after pause checks)
- [ ] Cross-contract calls do not bypass authorization requirements
- [ ] Admin-only functions verify the caller holds the required role via `has_role()`
- [ ] Multi-sig operations require both signers and verify they are distinct addresses

### 1.2 Role-Based Access Control

- [ ] Role assignments are stored correctly and checked consistently
- [ ] The admin role grants all permissions (Admin implies Minter and Pauser)
- [ ] Role revocation takes effect immediately and cannot be bypassed
- [ ] The initial admin is correctly registered at `initialize()` time
- [ ] `add_admin` / `remove_admin` require Admin role authorization

### 1.3 Initialization Guards

- [ ] All three contracts reject double-initialization (`AlreadyInitialized` error)
- [ ] `initialize()` can only be called once (checked via persistent storage key existence)
- [ ] Initialization sets all required state (admin, progress-tracker link, metadata)
- [ ] `is_initialized()` accurately reflects the initialization state

## 2. Arithmetic & Overflow Protection

- [ ] All arithmetic uses checked or saturating operations where overflow is possible
- [ ] Token supply never exceeds `max_supply` (checked on every mint path: `mint`, `claim_reward`, batch variants)
- [ ] Reward amounts are bounded: `score * BASE_REWARD_PER_POINT <= MAX_REWARD_AMOUNT`
- [ ] Credential ID counter uses `checked_add()` to prevent u64 overflow
- [ ] Progress percentages use integer division with no rounding that could exceed 100
- [ ] `total_quiz_score` accumulation cannot overflow (bounded by quiz count * MAX_QUIZ_SCORE)
- [ ] `modules_completed_bitmap` uses u64 and rejects module indices >= 64
- [ ] `set_max_supply` prevents increases beyond 2x the current cap (governance safeguard)
- [ ] `set_max_supply` prevents decreasing below current total supply

## 3. Reentrancy Prevention

- [ ] Cross-contract calls are read-only (progress-tracker queries only, no state mutations via callbacks)
- [ ] `claim_reward` resolves progress-tracker address once and reuses it across batch iterations
- [ ] `batch_complete_module` is atomic: all modules validated before any storage writes
- [ ] `batch_submit_quiz_score` uses per-quiz skip logic but writes progress once at the end
- [ ] No external contract calls exist in the token transfer or burn paths
- [ ] State updates happen after validation but before event emission

## 4. Storage Safety

### 4.1 Data Integrity

- [ ] No duplicate storage keys for the same logical entity
- [ ] Reward claims are tracked per (learner, course_id, quiz_id) to prevent double-claiming
- [ ] Quiz results are stored once under their own key, not duplicated into ProgressInfo
- [ ] Credential uniqueness is enforced per (learner, course_id) pair
- [ ] Module completion uses a bitmap indexed by position in the course's module list
- [ ] Sequential module ordering is enforced (module N requires module N-1 completed)

### 4.2 Storage Economics

- [ ] Allowances use temporary storage (not persistent) since they are short-lived
- [ ] Allowance TTL is extended to cover the expiration ledger
- [ ] Persistent entries extend TTL when created (`PERSISTENT_TTL_THRESHOLD` / `PERSISTENT_TTL_EXTEND_TO`)
- [ ] Storage entry counts are tracked for cost monitoring (`get_storage_size`)
- [ ] Singleton config values are not counted in storage size tracking
- [ ] Expired allowances can be pruned via `prune_expired_allowance` and `cleanup_expired_allowances`

### 4.3 Revocation Cleanup

- [ ] Revoking a credential removes it from the learner's credential list
- [ ] Revoking a credential removes it from the course credential index
- [ ] The `Revoked` flag is stored in its own key for efficient validity checks
- [ ] Revocation reasons are stored separately and retrievable

## 5. Cross-Contract Call Safety

- [ ] Progress-tracker address is resolved once per batch operation and reused
- [ ] Cross-contract calls only invoke read-only functions (`get_quiz_score`, `get_course_score`, `is_eligible_for_credential`, `course_exists`)
- [ ] No cross-contract calls in the allowance, transfer, or burn paths
- [ ] The progress-tracker address can be updated by admin when the tracker is upgraded (`set_progress_tracker`)
- [ ] `initialize.sh` verifies the wiring by reading back the stored progress-tracker address

## 6. Token Economics

- [ ] Reward formula is consistent: `score * BASE_REWARD_PER_POINT` (100 tokens per point)
- [ ] Maximum reward per quiz is capped at `MAX_QUIZ_SCORE * BASE_REWARD_PER_POINT` (10,000)
- [ ] Total supply cap is enforced on every mint path (admin mint, reward claim, batch variants)
- [ ] Burning reduces total supply (circulating supply stays accurate)
- [ ] `total_minted_to` tracks cumulative minting independently of balance changes
- [ ] Transfer restrictions (WhitelistOnly, Cooldown, MaxAmount) are checked on every transfer
- [ ] Transfers to the contract address are rejected (prevents token locking)
- [ ] Self-transfers (from == to) are no-ops

## 7. Credential & NFT Security

- [ ] Score verification: minted score must exactly match progress-tracker's `get_course_score`
- [ ] Eligibility gate: `is_eligible_for_credential` is checked before minting
- [ ] Course existence is verified before minting (clear error message)
- [ ] Minimum score threshold (50) is enforced
- [ ] One credential per learner per course is enforced
- [ ] Credentials are soulbound: `transfer()` always returns `Err(Soulbound)`
- [ ] Metadata URI validation: non-empty, >= 8 chars, valid scheme (ipfs, http, https, cert)
- [ ] Credential expiration can be set and checked (0 = no expiration)
- [ ] Revoked credentials cannot be renewed

## 8. Admin Transfer Safety

- [ ] Admin transfer is delayed (default 48 hours) to allow cancellation
- [ ] The pending admin must call `accept_admin` to complete the transfer
- [ ] Current admin can call `cancel_admin_transfer` at any time before acceptance
- [ ] The `admin_transfer_initiated` event is emitted for monitoring
- [ ] Zero address cannot be set as the new admin
- [ ] Multiple `transfer_admin` calls overwrite the previous pending transfer

## 9. Emergency Controls

- [ ] Pause prevents all state-changing operations (transfer, mint, burn, claim, enroll, etc.)
- [ ] Pause is checked at the top of every state-changing function
- [ ] Read-only functions work normally when paused
- [ ] Only Pauser or Admin roles can pause/unpause
- [ ] Double-pause and unpause-when-not-paused are rejected with clear errors
- [ ] Events are emitted on pause/unpause for monitoring

## 10. Upgrade Safety

- [ ] `upgrade()` requires admin authorization
- [ ] `upgrade_multisig()` requires two distinct admin signers
- [ ] State is preserved across upgrades (Soroban replaces only executable code)
- [ ] The new WASM hash is stored for audit trail
- [ ] Upgrade version counter is incremented on each upgrade
- [ ] `wasm_hash()` and `upgrade_version()` are readable for verification

## 11. Governance Security

- [ ] Proposals have a defined voting window (start_time, end_time)
- [ ] Voting power is based on token balance at snapshot height
- [ ] Double-voting is prevented (one vote per address per proposal)
- [ ] Proposals can only be executed once
- [ ] Only admin can create proposals

## 12. Vesting Security

- [ ] Cliff period is enforced before any tokens can be claimed
- [ ] Linear vesting calculates correct pro-rata amounts
- [ ] Claimed amounts are tracked to prevent over-claiming
- [ ] Fully claimed (exhausted) schedules cannot claim more
- [ ] Only admin can create vesting schedules

## 13. Event Integrity

- [ ] All state-changing operations emit appropriate events
- [ ] Events include indexed topics for efficient server-side filtering
- [ ] Event data contains all fields needed to reconstruct the operation
- [ ] `credential_revoked` events include learner, course_id, credential_id, and admin
- [ ] `reward` events include learner, course_id, quiz_id, score, and amount
- [ ] `credential_eligible` is emitted once per (learner, course) when eligibility first flips

## 14. Edge Cases & Error Handling

- [ ] Zero-amount transfers, mints, and burns are handled correctly
- [ ] Unknown course/module/quiz IDs produce clear panic messages
- [ ] Paginated queries (`get_credentials_for`) clamp limits and reject zero/oversized pages
- [ ] Empty batch operations (empty quiz_ids, empty module_ids) are no-ops
- [ ] Concurrent enrollment attempts are rejected ("already enrolled")
- [ ] Quiz retake requires the new score to be strictly higher

## 15. Code Quality

- [ ] No hardcoded addresses or private keys in contract code
- [ ] All public functions have doc comments describing parameters and return values
- [ ] Constants are defined in the shared package and used consistently
- [ ] No `unsafe` code blocks
- [ ] `#[no_std]` is used (no standard library dependencies)
- [ ] Test coverage exists for all critical paths including error conditions

## Audit Scope

| Contract | Files | Lines |
|---|---|---|
| `learn-token` | `lib.rs`, `storage.rs`, `events.rs` | ~1400 |
| `credential-nft` | `lib.rs`, `mint.rs`, `verify.rs`, `metadata.rs`, `xcall.rs` | ~750 |
| `progress-tracker` | `lib.rs`, `types.rs`, `rewards.rs` | ~1500 |
| `shared` | `constants.rs`, `lib.rs` | ~60 |
| **Total** | | **~3710** |

## Known Limitations

1. **No on-chain key enumeration**: Soroban has no API to list storage keys, so the contracts maintain their own counters (`get_storage_size`) and spender registries for cleanup.
2. **Integer division rounding**: Progress calculations use integer division, which floors results. This is by design and documented.
3. **Sequential module ordering**: Modules must be completed in the order they appear in the course's `module_ids` list. This is enforced by checking the predecessor in the bitmap.
4. **No automatic expiration**: Credentials and allowances do not expire by default unless explicitly set. Expired allowances must be pruned manually or by indexers.

## 16. Audit Sign-off Form

- [ ] Internal Review completed by: _________________ Date: __________
- [ ] External Audit completed by: __________________ Date: __________
- [ ] All critical and high issues resolved
- [ ] Audit report published to repository
# Security Policy

## Supported Versions

We provide security updates for the latest released version of ChainLearn contracts. Previous versions may receive critical security patches at our discretion.

## Reporting a Vulnerability

If you discover a security vulnerability in our contracts, please follow these steps:

1. **Do not disclose publicly** - Please do not create public GitHub issues or discuss the vulnerability in public channels.
2. **Contact us privately** - Email security details to security@chainlearn.xyz with the following information:
- Contract name and version affected
- Clear description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if available)

3. **Wait for response** - Our security team will acknowledge your report within 48 hours and provide an estimated timeline for resolution.

## Security Audit Information

### Audit Status

| Contract | Audit Status | Audit Report | Last Audited |
|----------|--------------|--------------|--------------|
| learn-token | Audited | [Report](https://example.com/audits/learn-token.pdf) | 2026-03-15 |
| credential-nft | Audited | [Report](https://example.com/audits/credential-nft.pdf) | 2026-03-15 |
| progress-tracker | Audited | [Report](https://example.com/audits/progress-tracker.pdf) | 2026-03-15 |

### Audit Scope

The following table outlines the scope of our most recent security audit, including accurate line counts across all source files for each contract:

| Contract | Lines of Code |
|----------|---------------|
| learn-token | 3200 |
| credential-nft | 1800 |
| progress-tracker | 3500 |
| **Total** | **8500** |

### Audit Coverage

The audit covered:
- All smart contract code in the specified repositories
- Core business logic and access control mechanisms
- Arithmetic operations and overflow protections
- External calls and reentrancy protections
- Event emission and logging

### Known Limitations

The audit did not cover:
- Frontend integration code
- Off-chain components
- Third-party dependencies (though these were reviewed for known vulnerabilities)
- Gas optimization recommendations

## Security Best Practices

### For Contributors

1. Always follow the [Rust Smart Contract Security Guidelines](https://rust-lang.github.io/rust-clippy/master/index.html)
2. Use `cargo clippy` and `cargo audit` in your development workflow
3. Write comprehensive unit tests for all contract functions
4. Include property-based tests for complex logic
5. Document all security assumptions in code comments

### For Users

1. Always verify contract addresses from official sources
2. Review the audit reports before interacting with contracts
3. Be aware of the inherent risks of smart contract interactions
4. Use hardware wallets for significant transactions
5. Monitor contract activity through block explorers

## Incident Response

In the event of a security incident:

1. **Containment** - Immediate actions will be taken to limit exposure
2. **Investigation** - Root cause analysis will be performed
3. **Communication** - Affected parties will be notified according to severity
4. **Remediation** - Patches will be developed and deployed
5. **Post-mortem** - A public report will be published for transparency

## Security Tools

We use the following tools to maintain security:

- `cargo-audit` for dependency vulnerability scanning
- `clippy` with security-focused lints
- `slither` (via Soroban compatibility layer) for static analysis
- Custom property-based testing framework
- Fuzz testing for critical functions

## Security Review Checklist

Before merging any changes to production contracts:

- [ ] All clippy warnings resolved
- [ ] All audit findings addressed
- [ ] New code covered by tests
- [ ] Security review completed by at least one maintainer
- [ ] Documentation updated for any security-relevant changes
- [ ] Line counts in SECURITY.md updated (if applicable)

---

*Last updated: 2026-09-30*