This document describes the security model, trust boundaries, threat model, and mitigation strategies for ChainLearn smart contracts.
ChainLearn uses role-based access control with the following roles:
| Role | Permissions | Functions |
|---|---|---|
| Admin | Full administrative access | initialize, transfer_admin, pause, unpause, upgrade |
| Minter | Token minting | mint (learn-token only) |
| Pauser | Emergency pause/unpause | pause, unpause |
| User | Standard operations | enroll, complete_module, submit_quiz_score, claim_reward |
// Direct authorization - requires the caller to prove ownership
learner.require_auth();
// Role-based authorization - checks for specific admin role
let admin = storage::get_admin(&env);
admin.require_auth();
if !storage::has_role(&env, &caller, &AdminRole::Minter) {
panic!("not authorized");
}Critical operations require multi-signature authorization:
- upgrade_multisig: Requires two distinct admin signatures
- execute_multisig_op: Generic multi-sig operation execution
// Both callers must be admins and distinct
caller.require_auth();
co_signer.require_auth();
if caller == co_signer {
panic!("distinct co-signer required");
}graph TD
subgraph External Trust Boundary
Stellar[Stellar Network]
Callers[External Callers]
Storage[Off-chain Storage]
end
subgraph ChainLearn Contracts
Checks[Auth checks on all state changes]
Validation[Input validation bounds, duplicates]
Verification[Cross-contract verification]
end
Stellar --> ChainLearn Contracts
Callers --> ChainLearn Contracts
Storage --> ChainLearn Contracts
graph TD
subgraph Internal Trust Boundary
Tracker(progress-tracker <br> Source of Truth)
Token(learn-token <br> Reward Minter) -->|verified reads| Tracker
NFT(credential-nft <br> Credential Minter) -->|verified reads| Tracker
end
Threat: Attacker mints tokens without completing quizzes.
Mitigation:
claim_rewardqueriesprogress-tracker.get_quiz_score()directly- Score cannot be supplied by the caller
- Each quiz can only be claimed once per learner
// Score is fetched from progress-tracker, not provided by caller
let score = Self::fetch_quiz_score(&env, &progress_tracker, ...);
let reward_amount = (score as i128) * BASE_REWARD_PER_POINT;Threat: Attacker mints credential without course completion.
Mitigation:
mint_credentialcallsprogress-tracker.is_eligible_for_credential()- Score must match
progress-tracker.get_course_score()exactly - One credential per learner per course
// Eligibility verified on-chain
if !xcall::is_eligible_for_credential(&env, &progress_tracker, &learner, &course_id) {
panic!("learner has not completed the course requirements");
}
// Score must match exactly
if score != verified_score {
panic!("score {} does not match verified score {}", score, verified_score);
}Threat: Learner claims reward multiple times for same quiz.
Mitigation:
RewardClaimed(learner, course_id, quiz_id)flag set after first claim- Subsequent claims rejected with "reward already claimed"
Threat: Attacker replays old transactions.
Mitigation:
- Stellar transaction sequence numbers prevent replay
- Allowance expiration via
expiration_ledger - Admin transfer delay prevents immediate key compromise exploitation
Threat: Attacker front-runs allowance changes.
Mitigation:
increase_allowanceadds to existing allowance (no reset window)- Allowances have expiration ledgers
Threat: Admin inflates token supply beyond safe limits.
Mitigation:
max_supplycap enforced on every mintset_max_supplylimited to 2x increase per update- Cannot reduce below current circulating supply
// 2x governance limit per update
let max_allowed = old_max_supply.checked_mul(2).expect("overflow");
if new_max_supply > max_allowed {
panic!("max supply increase exceeds governance limit");
}Threat: Attacker gains control of admin key.
Mitigation:
- Delayed admin transfer: 24-hour delay before new admin can accept
- Cancel window: Current admin can cancel pending transfer
- Multi-sig for critical ops: Upgrades require two admin signatures
- Emergency pause: Can freeze all operations
Threat: Malicious contract upgrade.
Mitigation:
- Multi-sig required for upgrades
- State preserved across upgrades (only wasm replaced)
- Upgrade version tracked on-chain
- Wasm hash stored for verification
- Input Validation: Check all inputs at contract entry
- Authorization Checks: Require auth before state changes
- Cross-Contract Verification: Verify claims against source of truth
- Idempotency Guards: Prevent duplicate operations
- Emergency Controls: Pause capability for all contracts
- All external calls use direct
env.invoke_contract(no dynamic dispatch) - No force unwraps in production code
- Panics used for error handling (revert entire transaction)
- Events emitted for all state changes (indexer-friendly)
- Transfer cooldown mechanism (per-sender ledger tracking)
- Maximum page sizes for paginated reads
- Supply caps on token minting
- All state-changing functions require authorization
- Cross-contract calls verify on-chain data
- Double-claim prevention for rewards
- Double-mint prevention for credentials
- Soulbound (non-transferable) credentials
- Supply cap enforced on all minting paths
- Admin transfer delay for key compromise mitigation
- Multi-sig for critical operations
- Emergency pause capability
- Input validation on all parameters
- No force unwraps in production code
- Events for all state changes