Skip to content

feat: calendar contract scaffold and governance#63

Merged
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
arandomogg:calendar-scaffold
Mar 29, 2026
Merged

feat: calendar contract scaffold and governance#63
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
arandomogg:calendar-scaffold

Conversation

@arandomogg
Copy link
Copy Markdown

@arandomogg arandomogg commented Mar 29, 2026

Summary

  • Scaffold new calendar-contract with full admin governance
  • initialize, pause/unpause, transfer_admin, upgrade_contract
  • Storage keys: Admin, VaultAddress, IsPaused
  • Events: contract_paused, admin_transferred
  • 11 tests covering init, double-init, pause, unpause, transfer, auth, and event emission

Closes #56

Test plan

  • All 11 tests pass
  • Zero compiler errors

Summary by CodeRabbit

Release Notes

  • New Features

    • Calendar contract with admin initialization and management capabilities
    • Pause/unpause functionality for contract state control
    • Admin transfer capability with authorization enforcement
    • Contract upgrade functionality for WASM updates
    • Event logging for operation tracking
  • Tests

    • Comprehensive test coverage for contract initialization, pause/unpause operations, admin transfer, and authorization enforcement

Add calendar-contract with initialize, pause/unpause, transfer_admin,
upgrade_contract. Storage keys: Admin, VaultAddress, IsPaused.
Events for pause and admin transfer. 11 tests covering all paths.

Closes LightForgeHub#56
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

Introduced a new Soroban smart contract calendar-contract with complete admin governance infrastructure. The contract provides initialization, pause/unpause, admin transfer, and upgrade capabilities. Includes error definitions, event handlers, persistent storage management, and comprehensive test coverage for functionality and authorization.

Changes

Cohort / File(s) Summary
Package Configuration
contracts/calendar-contract/Cargo.toml
Defined calendar-contract as a non-publishable Rust package targeting edition 2021, configured for Soroban with cdylib and lib outputs, enabled soroban-sdk workspace dependency, and set optimization profiles with LTO for release builds.
Core Contract Logic
contracts/calendar-contract/src/contract.rs, contracts/calendar-contract/src/lib.rs
Implemented contract entrypoint and five public admin functions (initialize, pause, unpause, transfer_admin, upgrade_contract). Each enforces authorization checks, manages state transitions, and delegates to helper modules.
Error & Event Handling
contracts/calendar-contract/src/error.rs, contracts/calendar-contract/src/events.rs
Defined CalendarError enum with three error variants (NotInitialized, AlreadyInitialized, ContractPaused), and added event-publishing helpers (contract_paused, admin_transferred) for contract state changes.
Persistent Storage
contracts/calendar-contract/src/storage.rs
Introduced DataKey enum and typed accessor functions for managing admin address, vault address, and pause state in contract instance storage with sensible defaults.
Test Suite
contracts/calendar-contract/src/test.rs
Comprehensive test coverage including initialization, pause/unpause mechanics, admin transfer flow, pause enforcement, and authorization validation via mocked auth.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A calendar contract hops into view,
With admin controls, tried and true!
Pause, transfer, upgrade with flair,
Smart contracts flourish beyond compare! 🌟

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides a clear summary, test plan, and issue reference, but is significantly incomplete compared to the template's required sections and checkboxes. Fill in all required template sections including type of change, complete test evidence (screenshots/logs), and ensure all checkboxes are properly marked.
Docstring Coverage ⚠️ Warning Docstring coverage is 41.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: calendar contract scaffold and governance' directly and clearly summarizes the main change: adding a new calendar contract with administrative governance features.
Linked Issues check ✅ Passed The PR implementation fulfills all coding requirements from issue #56: error codes, storage keys, admin functions, and event publishing are all present; comprehensive tests verify initialization, permissions, and event emission.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #56 requirements; the new calendar-contract files implement exactly the required governance structure without unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Bosun-Josh121
Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
contracts/calendar-contract/src/contract.rs (1)

35-44: Consider validating new admin is different from current admin.

transfer_admin allows transferring to the same address, which would emit a misleading event and waste gas. While not strictly a bug, it's a defensive check worth adding.

Proposed validation
 pub fn transfer_admin(env: &Env, new_admin: &Address) -> Result<(), CalendarError> {
     let admin = storage::get_admin(env).ok_or(CalendarError::NotInitialized)?;
     admin.require_auth();
     if storage::is_paused(env) {
         return Err(CalendarError::ContractPaused);
     }
+    if admin == *new_admin {
+        return Ok(()); // No-op if same admin
+    }
     storage::set_admin(env, new_admin);
     events::admin_transferred(env, &admin, new_admin);
     Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/calendar-contract/src/contract.rs` around lines 35 - 44, In
transfer_admin, add a defensive check after retrieving the current admin
(storage::get_admin) to ensure new_admin != admin; if equal, return a distinct
error (e.g., CalendarError::SameAdmin or CalendarError::AlreadyAdmin) and skip
calling storage::set_admin and events::admin_transferred. If that CalendarError
variant doesn't exist, add it to the CalendarError enum and use it in
transfer_admin to avoid emitting a misleading admin_transferred event and
wasting gas.
contracts/calendar-contract/src/test.rs (1)

17-105: Consider adding test coverage for upgrade_contract and unpause edge cases.

The test suite covers most scenarios well, but a few gaps exist:

  • No test for upgrade_contract (could at least test auth requirement)
  • No test for unpause requiring auth (similar to test_pause_requires_auth)
  • No test for calling unpause before initialization

These are non-blocking but would strengthen the test suite.

Example test for unpause auth requirement
#[test]
#[should_panic]
fn test_unpause_requires_auth() {
    let env = Env::default();
    let contract_id = env.register(CalendarContract, ());
    let client = CalendarContractClient::new(&env, &contract_id);
    let admin = Address::generate(&env);
    let vault = Address::generate(&env);

    env.mock_all_auths();
    client.init(&admin, &vault);
    client.pause();

    env.mock_auths(&[]);
    client.unpause();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/calendar-contract/src/test.rs` around lines 17 - 105, Add tests
covering upgrade_contract auth and unpause edge cases: write a test that
verifies upgrade_contract requires admin auth by calling
client.try_upgrade_contract (or client.upgrade_contract with env.auth mocks)
with env.mock_all_auths() then env.mock_auths(&[]) and asserting an auth error;
add a test that unpause requires auth similar to the provided example using
client.unpause (or try_unpause) and env.mock_auths to assert a panic/error when
not authorized; and add a test that calling unpause before initialization
returns Err(Ok(CalendarError::NotInitialized)) by invoking client.try_unpause
(or client.unpause and asserting the expected failure) on a fresh client. Use
the existing client methods (upgrade_contract, try_upgrade_contract, unpause,
try_unpause) and CalendarError variants (NotInitialized, AlreadyInitialized,
ContractPaused) and the env mocking helpers (env.mock_all_auths, env.mock_auths)
to implement these tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/calendar-contract/src/events.rs`:
- Around line 4-13: Add a new contract_upgraded event function and emit it from
the upgrade path: implement pub fn contract_upgraded(env: &Env, old_code_hash:
&BytesN<32>, new_code_hash: &BytesN<32>) { let topics =
(symbol_short!("contract_upgraded"),); env.events().publish(topics,
(old_code_hash.clone(), new_code_hash.clone())); } (use the same pattern as
contract_paused and admin_transferred) and call contract_upgraded(...) from the
upgrade_contract function in contract.rs at the point where the upgrade is
applied, passing the previous and new code-hash values so the upgrade is
recorded.
- Line 1: Remove the unnecessary crate attribute by deleting the top-level
#![allow(deprecated)] in events.rs; this module contains no deprecated items
(symbol_short! from soroban-sdk v25.3.0 is not deprecated), so simply remove
that attribute line to avoid suppressing warnings incorrectly and keep the file
clean.

In `@contracts/calendar-contract/src/storage.rs`:
- Around line 13-45: Add a helper extend_instance_ttl(env: &Env) that calls
env.storage().instance().extend_ttl(...) and update storage accessors and admin
flows to extend TTL: call extend_instance_ttl() from initialize() after setting
Admin/Vault/IsPaused, and call it in transfer_admin(), pause(), unpause() and
any admin-sensitive paths (i.e., immediately after successful
get_admin()/set_admin(), set_vault_address(), set_paused() operations) so Admin,
VaultAddress and IsPaused instance keys do not expire; ensure references to
has_admin, set_admin, get_admin, set_vault_address, get_vault_address,
is_paused, set_paused are updated to invoke the TTL extension where those state
changes or admin reads occur.

---

Nitpick comments:
In `@contracts/calendar-contract/src/contract.rs`:
- Around line 35-44: In transfer_admin, add a defensive check after retrieving
the current admin (storage::get_admin) to ensure new_admin != admin; if equal,
return a distinct error (e.g., CalendarError::SameAdmin or
CalendarError::AlreadyAdmin) and skip calling storage::set_admin and
events::admin_transferred. If that CalendarError variant doesn't exist, add it
to the CalendarError enum and use it in transfer_admin to avoid emitting a
misleading admin_transferred event and wasting gas.

In `@contracts/calendar-contract/src/test.rs`:
- Around line 17-105: Add tests covering upgrade_contract auth and unpause edge
cases: write a test that verifies upgrade_contract requires admin auth by
calling client.try_upgrade_contract (or client.upgrade_contract with env.auth
mocks) with env.mock_all_auths() then env.mock_auths(&[]) and asserting an auth
error; add a test that unpause requires auth similar to the provided example
using client.unpause (or try_unpause) and env.mock_auths to assert a panic/error
when not authorized; and add a test that calling unpause before initialization
returns Err(Ok(CalendarError::NotInitialized)) by invoking client.try_unpause
(or client.unpause and asserting the expected failure) on a fresh client. Use
the existing client methods (upgrade_contract, try_upgrade_contract, unpause,
try_unpause) and CalendarError variants (NotInitialized, AlreadyInitialized,
ContractPaused) and the env mocking helpers (env.mock_all_auths, env.mock_auths)
to implement these tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9b79bd7-2399-4613-8ae6-3b5c0c9feebc

📥 Commits

Reviewing files that changed from the base of the PR and between 2482d34 and 7bf0bbf.

📒 Files selected for processing (8)
  • contracts/calendar-contract/Cargo.toml
  • contracts/calendar-contract/src/contract.rs
  • contracts/calendar-contract/src/error.rs
  • contracts/calendar-contract/src/events.rs
  • contracts/calendar-contract/src/lib.rs
  • contracts/calendar-contract/src/storage.rs
  • contracts/calendar-contract/src/test.rs
  • contracts/calendar-contract/src/types.rs

@@ -0,0 +1,13 @@
#![allow(deprecated)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

soroban-sdk symbol_short deprecated alternative 2025

💡 Result:

symbol_short! macro in soroban-sdk is not deprecated. It remains the recommended way to create short Symbol constants (up to 9 characters) at compile time for efficiency. For longer symbols (up to 32 characters), use Symbol::new(&env, "string"). Official docs.rs documentation for latest soroban-sdk v25.3.0 (published 2026-03-07) actively documents and exemplifies symbol_short! usage. Migration guides (v23->v25, v22->v23) list other breaking changes but do not mention deprecation of symbol_short!. Some older Symbol::short method may be deprecated in favor of the macro (per secondary docs), but the macro itself is current best practice. No 2025-specific deprecation found in changelogs, releases, or official Stellar sources.

Citations:


🏁 Script executed:

cat -n contracts/calendar-contract/src/events.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 591


Remove the unnecessary #![allow(deprecated)] attribute.

The symbol_short! macro is not deprecated in soroban-sdk v25.3.0 and remains the recommended way to create short Symbol constants. This module does not contain any deprecated code, so the attribute can be safely removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/calendar-contract/src/events.rs` at line 1, Remove the unnecessary
crate attribute by deleting the top-level #![allow(deprecated)] in events.rs;
this module contains no deprecated items (symbol_short! from soroban-sdk v25.3.0
is not deprecated), so simply remove that attribute line to avoid suppressing
warnings incorrectly and keep the file clean.

Comment on lines +4 to +13
pub fn contract_paused(env: &Env, paused: bool) {
let topics = (symbol_short!("paused"),);
env.events().publish(topics, paused);
}

pub fn admin_transferred(env: &Env, old_admin: &Address, new_admin: &Address) {
let topics = (symbol_short!("adm_xfer"),);
env.events()
.publish(topics, (old_admin.clone(), new_admin.clone()));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing contract_upgraded event per issue #56.

The linked issue #56 specifies three events: contract_paused, admin_transferred, and contract_upgraded. Only the first two are implemented here. The upgrade_contract function in contract.rs currently performs the upgrade without emitting an event.

Proposed addition for contract_upgraded event
+pub fn contract_upgraded(env: &Env, new_wasm_hash: &soroban_sdk::BytesN<32>) {
+    let topics = (symbol_short!("upgraded"),);
+    env.events().publish(topics, new_wasm_hash.clone());
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn contract_paused(env: &Env, paused: bool) {
let topics = (symbol_short!("paused"),);
env.events().publish(topics, paused);
}
pub fn admin_transferred(env: &Env, old_admin: &Address, new_admin: &Address) {
let topics = (symbol_short!("adm_xfer"),);
env.events()
.publish(topics, (old_admin.clone(), new_admin.clone()));
}
pub fn contract_paused(env: &Env, paused: bool) {
let topics = (symbol_short!("paused"),);
env.events().publish(topics, paused);
}
pub fn admin_transferred(env: &Env, old_admin: &Address, new_admin: &Address) {
let topics = (symbol_short!("adm_xfer"),);
env.events()
.publish(topics, (old_admin.clone(), new_admin.clone()));
}
pub fn contract_upgraded(env: &Env, new_wasm_hash: &soroban_sdk::BytesN<32>) {
let topics = (symbol_short!("upgraded"),);
env.events().publish(topics, new_wasm_hash.clone());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/calendar-contract/src/events.rs` around lines 4 - 13, Add a new
contract_upgraded event function and emit it from the upgrade path: implement
pub fn contract_upgraded(env: &Env, old_code_hash: &BytesN<32>, new_code_hash:
&BytesN<32>) { let topics = (symbol_short!("contract_upgraded"),);
env.events().publish(topics, (old_code_hash.clone(), new_code_hash.clone())); }
(use the same pattern as contract_paused and admin_transferred) and call
contract_upgraded(...) from the upgrade_contract function in contract.rs at the
point where the upgrade is applied, passing the previous and new code-hash
values so the upgrade is recorded.

Comment on lines +13 to +45
pub fn has_admin(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Admin)
}

pub fn set_admin(env: &Env, admin: &Address) {
env.storage().instance().set(&DataKey::Admin, admin);
}

pub fn get_admin(env: &Env) -> Option<Address> {
env.storage().instance().get(&DataKey::Admin)
}

// --- Vault ---

pub fn set_vault_address(env: &Env, vault: &Address) {
env.storage().instance().set(&DataKey::VaultAddress, vault);
}

pub fn get_vault_address(env: &Env) -> Option<Address> {
env.storage().instance().get(&DataKey::VaultAddress)
}

// --- Pause ---

pub fn is_paused(env: &Env) -> bool {
env.storage()
.instance()
.get(&DataKey::IsPaused)
.unwrap_or(false)
}

pub fn set_paused(env: &Env, paused: bool) {
env.storage().instance().set(&DataKey::IsPaused, &paused);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

soroban instance storage extend_ttl best practices 2025

💡 Result:

Best practices for extending TTL on Soroban instance storage, based on official Rust SDK documentation: Instance storage is for small, contract-level data like admin addresses, config, metadata (keep under ~100KB, ideally <10KB). It shares TTL with the contract instance and code; expiration makes the entire contract inaccessible. Use env.storage.instance.extend_ttl(threshold, extend_to): - threshold: Extend only if current TTL < this (in ledgers, e.g., 100_000 ~5.5 days, 518_400 ~30 days) - extend_to: New TTL (e.g., 5_250_000 ~1 year). Do not exceed env.storage.max_ttl. Key practices: 1. Extend regularly: On initialization, admin ops, every interaction for critical contracts. 2. Use threshold to avoid redundant extensions. 3. Guidelines: Short-lived: 100k ledgers; Medium: 1M; Long-term/critical: 5.25M+. 4. Example: pub fn bump_instance(env: Env) { env.storage.instance.extend_ttl(518_400, 5_250_000); } Extend proactively to avoid restoration costs. Unlike persistent/temp, no key param—extends whole instance/code. No 2025-specific changes noted; practices from current SDK (2026-relevant). Official Stellar docs pages were 404, but SDK sources authoritative.

Citations:


🏁 Script executed:

# First, find and read the full storage.rs file to see complete context
find . -name "storage.rs" -path "*/calendar-contract/*" | head -5

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 123


🏁 Script executed:

# Search for extend_ttl calls in the entire contract codebase
rg "extend_ttl" --type rust

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 411


🏁 Script executed:

# Check contract initialization and admin operations
rg -A 5 "pub fn set_admin|pub fn get_admin|pub fn new|fn init" --type rust contracts/

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 7125


🏁 Script executed:

# Read the full calendar-contract storage.rs to see complete context
cat -n contracts/calendar-contract/src/storage.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1389


🏁 Script executed:

# Check how identity-registry-contract implements TTL extension
rg -B 5 -A 10 "extend_ttl" contracts/identity-registry-contract/src/storage.rs | head -40

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1298


🏁 Script executed:

# Check calendar-contract initialization function for TTL calls
cat -n contracts/calendar-contract/src/contract.rs | head -100

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 2030


🏁 Script executed:

# Check if other calendar-contract contracts have extend_ttl for instance storage
cat -n contracts/payment-vault-contract/src/storage.rs | head -80

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 3217


🏁 Script executed:

# Check if reputation-scoring-contract has extend_ttl for instance storage
cat -n contracts/reputation-scoring-contract/src/storage.rs | head -80

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1148


Instance storage TTL extension is missing and critical for contract longevity.

Soroban instance storage entries expire after their TTL if not extended. Without calling extend_ttl(), the contract's admin address, vault address, and pause state will become inaccessible when the instance storage TTL expires, rendering the entire contract unusable. Extending TTL on initialization and during admin operations is a required Soroban best practice.

Add an extend_instance_ttl() function and call it during initialization and in critical admin paths (initialize, transfer_admin, pause, unpause):

Proposed TTL extension pattern
+const INSTANCE_LIFETIME_THRESHOLD: u32 = 17280; // ~1 day
+const INSTANCE_BUMP_AMOUNT: u32 = 518400;       // ~30 days
+
+pub fn extend_instance_ttl(env: &Env) {
+    env.storage()
+        .instance()
+        .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);
+}

Then call extend_instance_ttl(env) in initialize() and after get_admin() reads in contract operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/calendar-contract/src/storage.rs` around lines 13 - 45, Add a
helper extend_instance_ttl(env: &Env) that calls
env.storage().instance().extend_ttl(...) and update storage accessors and admin
flows to extend TTL: call extend_instance_ttl() from initialize() after setting
Admin/Vault/IsPaused, and call it in transfer_admin(), pause(), unpause() and
any admin-sensitive paths (i.e., immediately after successful
get_admin()/set_admin(), set_vault_address(), set_paused() operations) so Admin,
VaultAddress and IsPaused instance keys do not expire; ensure references to
has_admin, set_admin, get_admin, set_vault_address, get_vault_address,
is_paused, set_paused are updated to invoke the TTL extension where those state
changes or admin reads occur.

@Bosun-Josh121 Bosun-Josh121 merged commit b30fd9a into LightForgeHub:main Mar 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scaffold Calendar Contract & Admin Governance

3 participants