Skip to content

feat: implement on-chain contract version tracking for split-escrow#252

Open
fredericklamar342-prog wants to merge 2 commits intoOlufunbiIK:mainfrom
omenprotocol:main
Open

feat: implement on-chain contract version tracking for split-escrow#252
fredericklamar342-prog wants to merge 2 commits intoOlufunbiIK:mainfrom
omenprotocol:main

Conversation

@fredericklamar342-prog
Copy link
Copy Markdown
Contributor

@fredericklamar342-prog fredericklamar342-prog commented Mar 24, 2026

done

Summary by CodeRabbit

  • New Features

    • Contract now supports semantic version tracking, querying, and upgrades with event notifications.
  • Bug Fixes / Behavior Changes

    • Added an explicit error for invalid version inputs.
    • Fee handling adjusted so treasury lookup and fee-collected events occur only when a positive fee is due.
  • Tests

    • Added tests for version init, retrieval, successful and invalid upgrades, admin authorization, and fee/release scenarios.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 24, 2026

Someone is attempting to deploy a commit to the olufunbiik's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd06e650-3156-48d2-ad3e-a5f6d486f801

📥 Commits

Reviewing files that changed from the base of the PR and between 74e8e2f and a2340ce.

📒 Files selected for processing (2)
  • contracts/split-escrow/src/fees.rs
  • contracts/split-escrow/src/test.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • contracts/split-escrow/src/test.rs

📝 Walkthrough

Walkthrough

Adds on-chain contract versioning: store a semver at initialization, expose a getter, allow admin-only upgrades with semver validation, emit a ContractUpgraded event, add an InvalidVersion error, and update tests and fee collection behavior.

Changes

Cohort / File(s) Summary
Error Handling
contracts/split-escrow/src/errors.rs
Added InvalidVersion = 10 Error variant for semver/upgrade failures.
Event Emission
contracts/split-escrow/src/events.rs
Added emit_contract_upgraded(env, old_version, new_version) to publish ContractUpgraded events.
Storage Layer
contracts/split-escrow/src/storage.rs
Added DataKey::Version and set_version / get_version for persistent version storage.
Core Contract Logic
contracts/split-escrow/src/lib.rs
initialize now accepts version: String; added get_version and admin-only upgrade_version with is_valid_semver validation and event emission.
Fees Behavior
contracts/split-escrow/src/fees.rs
collect_fee defers treasury lookup and only fetches/emits when fee_amount > 0, avoiding unnecessary TreasuryNotSet errors and events for zero fees.
Tests
contracts/split-escrow/src/test.rs
Updated setup to pass version; added tests for get_version, successful/invalid/non-admin upgrades, semver validation at init, plus a release-funds-zero-fee test.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin/Client
    participant Contract as SplitEscrow Contract
    participant Storage as Storage Layer
    participant Events as Event System

    Admin->>Contract: initialize(admin, token, version:"1.0.0")
    Contract->>Contract: validate semver (is_valid_semver)
    Contract->>Storage: set_version("1.0.0")
    Storage-->>Contract: ok

    Admin->>Contract: upgrade_version("1.1.0")
    Contract->>Contract: verify admin auth
    Contract->>Contract: validate semver("1.1.0")
    Contract->>Storage: get_version() -> "1.0.0"
    Storage-->>Contract: "1.0.0"
    Contract->>Storage: set_version("1.1.0")
    Storage-->>Contract: ok
    Contract->>Events: emit_contract_upgraded("1.0.0","1.1.0")
    Events-->>Admin: ContractUpgraded event
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
I store the version in a burrowed key,
Admin hops by, upgrades safely with glee,
Semver dots glitter, checked and true,
An event flutters: old to new,
A rabbit cheers — contracts hopped anew!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main feature: adding on-chain version tracking capabilities to the split-escrow contract through new get_version() and upgrade_version() methods.

✏️ 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.

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: 2

🧹 Nitpick comments (4)
contracts/split-escrow/src/storage.rs (1)

84-86: Consider returning Option<String> for migration safety.

The unwrap() will panic if called on a contract that was initialized before this version tracking feature was added. Unlike get_admin which is guaranteed to exist after initialization, Version won't exist for pre-upgrade contracts.

Consider following the pattern used by get_treasury (line 76-78) which returns Option<Address>:

♻️ Proposed change
-pub fn get_version(env: &Env) -> soroban_sdk::String {
-    env.storage().instance().get(&DataKey::Version).unwrap()
+pub fn get_version(env: &Env) -> Option<soroban_sdk::String> {
+    env.storage().instance().get(&DataKey::Version)
 }

Then callers can provide a default or handle the missing case gracefully.

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

In `@contracts/split-escrow/src/storage.rs` around lines 84 - 86, Change
get_version to return Option<soroban_sdk::String> instead of unwrapping; read
DataKey::Version from env.storage().instance().get(...) and return the Option so
callers can handle missing versions (follow the get_treasury/get_admin pattern).
Update any callers of get_version to handle None or provide a default version
during migration/initialization flows.
contracts/split-escrow/src/test.rs (1)

127-137: Unused variable admin in test.

The variable admin is captured from setup() but never used in this test.

♻️ Proposed fix
 #[test]
 fn test_upgrade_version_success() {
-    let (env, client, admin, _, _, _, _) = setup();
+    let (env, client, _, _, _, _, _) = setup();
     
     // Auth is mocked in setup, but we can explicitly test it if needed.
     // Here we just test the functionality.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/split-escrow/src/test.rs` around lines 127 - 137, The test
test_upgrade_version_success binds an unused admin variable from setup(); fix by
dropping the unused binding—call setup() as let (env, client, _, _, _, _, _) =
setup(); or rename admin to a prefixed underscore (e.g., _admin) so the compiler
won’t warn; keep the rest of the test (client.upgrade_version and
assert_eq!(client.get_version(), new_version)) unchanged.
contracts/split-escrow/src/lib.rs (2)

143-158: Consider validating that the new version is greater than the old version.

The current implementation allows "downgrades" (e.g., upgrading from 2.0.0 to 1.0.0). If version tracking is intended to reflect actual contract evolution, you may want to enforce monotonically increasing versions to prevent accidental or malicious rollbacks.

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

In `@contracts/split-escrow/src/lib.rs` around lines 143 - 158, The
upgrade_version function currently only checks semver validity but allows
downgrades; modify upgrade_version to retrieve the current version via
storage::get_version(&env), parse both old and new using the existing
is_valid_semver or a semver parser, compare them to ensure new_version >
old_version (reject equal or lesser versions with Error::InvalidVersion or a new
Error variant), and only call storage::set_version(&env, &new_version) and
events::emit_contract_upgraded(&env, old_version, new_version) when the
comparison passes; reference upgrade_version, is_valid_semver,
storage::get_version, storage::set_version, and events::emit_contract_upgraded.

161-189: Leading zeros are not rejected, which violates semver spec.

The current validation accepts versions like "01.0.0" or "1.00.0", which are invalid per the semver specification (numeric identifiers must not include leading zeros).

If strict semver compliance is needed, add a check to reject segments with leading zeros:

♻️ Proposed enhancement
         if char == b'.' {
             if !segment_has_digit {
                 return false;
             }
             dot_count += 1;
             segment_has_digit = false;
+            segment_start = true;
         } else if char >= b'0' && char <= b'9' {
+            // Reject leading zeros (except "0" alone, handled by next dot or end)
+            if segment_start && char == b'0' && i + 1 < len && buf[i + 1] != b'.' {
+                return false;
+            }
+            segment_start = false;
             segment_has_digit = true;
         } else {
             return false;
         }

Alternatively, if lightweight validation is intentional for gas efficiency, document this deviation from strict semver.

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

In `@contracts/split-escrow/src/lib.rs` around lines 161 - 189, The
is_valid_semver function currently accepts numeric identifiers with leading
zeros; modify is_valid_semver to track each segment's start index/length (e.g.,
add segment_start or segment_len) while iterating over buf so that when you hit
a '.' or the end you can reject the segment if segment_len > 1 and the first
byte of that segment is b'0'; keep the existing checks (digit-only, three dots
check) but add this leading-zero check before resetting
segment_len/segment_has_digit and returning the final result.
🤖 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/split-escrow/src/test.rs`:
- Around line 158-168: The test test_semver_validation_at_init is missing a
mocked auth context so initialize (which calls admin.require_auth()) will fail
before semver checks; fix by calling env.mock_all_auths() at the start of the
test before invoking SplitEscrowContractClient::initialize (or before any
admin.require_auth() path) so the auth check is satisfied and the semver
validation/assertion can run.
- Around line 147-156: The test test_upgrade_version_non_admin is invalid
because it runs env.as_contract(&client.address, ...) (executing as the
contract) and setup() calls mock_all_auths(), and the declared non_admin is
never used; change the test to invoke client.upgrade_version as the non_admin
address (not as the contract) and ensure you do not mock all auths for this
call—either remove or restrict mock_all_auths() in setup() or reconfigure
mock_auths to exclude the non_admin principal, then call the contract using the
non_admin principal (instead of env.as_contract) so the require_auth check in
client.upgrade_version actually fails for non-admin.

---

Nitpick comments:
In `@contracts/split-escrow/src/lib.rs`:
- Around line 143-158: The upgrade_version function currently only checks semver
validity but allows downgrades; modify upgrade_version to retrieve the current
version via storage::get_version(&env), parse both old and new using the
existing is_valid_semver or a semver parser, compare them to ensure new_version
> old_version (reject equal or lesser versions with Error::InvalidVersion or a
new Error variant), and only call storage::set_version(&env, &new_version) and
events::emit_contract_upgraded(&env, old_version, new_version) when the
comparison passes; reference upgrade_version, is_valid_semver,
storage::get_version, storage::set_version, and events::emit_contract_upgraded.
- Around line 161-189: The is_valid_semver function currently accepts numeric
identifiers with leading zeros; modify is_valid_semver to track each segment's
start index/length (e.g., add segment_start or segment_len) while iterating over
buf so that when you hit a '.' or the end you can reject the segment if
segment_len > 1 and the first byte of that segment is b'0'; keep the existing
checks (digit-only, three dots check) but add this leading-zero check before
resetting segment_len/segment_has_digit and returning the final result.

In `@contracts/split-escrow/src/storage.rs`:
- Around line 84-86: Change get_version to return Option<soroban_sdk::String>
instead of unwrapping; read DataKey::Version from
env.storage().instance().get(...) and return the Option so callers can handle
missing versions (follow the get_treasury/get_admin pattern). Update any callers
of get_version to handle None or provide a default version during
migration/initialization flows.

In `@contracts/split-escrow/src/test.rs`:
- Around line 127-137: The test test_upgrade_version_success binds an unused
admin variable from setup(); fix by dropping the unused binding—call setup() as
let (env, client, _, _, _, _, _) = setup(); or rename admin to a prefixed
underscore (e.g., _admin) so the compiler won’t warn; keep the rest of the test
(client.upgrade_version and assert_eq!(client.get_version(), new_version))
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2dd95170-1472-4bc5-adfc-9f7eb0f2279e

📥 Commits

Reviewing files that changed from the base of the PR and between be836a8 and 74e8e2f.

📒 Files selected for processing (5)
  • contracts/split-escrow/src/errors.rs
  • contracts/split-escrow/src/events.rs
  • contracts/split-escrow/src/lib.rs
  • contracts/split-escrow/src/storage.rs
  • contracts/split-escrow/src/test.rs

Comment on lines +158 to +168
#[test]
fn test_semver_validation_at_init() {
let env = Env::default();
let admin = Address::generate(&env);
let token = Address::generate(&env);
let contract_id = env.register_contract(None, SplitEscrowContract);
let client = SplitEscrowContractClient::new(&env, &contract_id);

// Valid
client.initialize(&admin, &token, &String::from_str(&env, "0.1.0"));
assert_eq!(client.get_version(), String::from_str(&env, "0.1.0"));
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 env.mock_all_auths() before initialize.

The initialize function calls admin.require_auth(). Without mocking authentication, this test should fail on the auth check before reaching the semver validation. Add env.mock_all_auths() to ensure the test validates what it claims.

🐛 Proposed fix
 #[test]
 fn test_semver_validation_at_init() {
     let env = Env::default();
+    env.mock_all_auths();
     let admin = Address::generate(&env);
     let token = Address::generate(&env);
     let contract_id = env.register_contract(None, SplitEscrowContract);
     let client = SplitEscrowContractClient::new(&env, &contract_id);
     
     // Valid
     client.initialize(&admin, &token, &String::from_str(&env, "0.1.0"));
     assert_eq!(client.get_version(), String::from_str(&env, "0.1.0"));
 }
📝 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
#[test]
fn test_semver_validation_at_init() {
let env = Env::default();
let admin = Address::generate(&env);
let token = Address::generate(&env);
let contract_id = env.register_contract(None, SplitEscrowContract);
let client = SplitEscrowContractClient::new(&env, &contract_id);
// Valid
client.initialize(&admin, &token, &String::from_str(&env, "0.1.0"));
assert_eq!(client.get_version(), String::from_str(&env, "0.1.0"));
#[test]
fn test_semver_validation_at_init() {
let env = Env::default();
env.mock_all_auths();
let admin = Address::generate(&env);
let token = Address::generate(&env);
let contract_id = env.register_contract(None, SplitEscrowContract);
let client = SplitEscrowContractClient::new(&env, &contract_id);
// Valid
client.initialize(&admin, &token, &String::from_str(&env, "0.1.0"));
assert_eq!(client.get_version(), String::from_str(&env, "0.1.0"));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/split-escrow/src/test.rs` around lines 158 - 168, The test
test_semver_validation_at_init is missing a mocked auth context so initialize
(which calls admin.require_auth()) will fail before semver checks; fix by
calling env.mock_all_auths() at the start of the test before invoking
SplitEscrowContractClient::initialize (or before any admin.require_auth() path)
so the auth check is satisfied and the semver validation/assertion can run.

@OlufunbiIK
Copy link
Copy Markdown
Owner

Hi @fredericklamar342-prog

You did a clean job but could you resolve the conflicts?

@OlufunbiIK
Copy link
Copy Markdown
Owner

@fredericklamar342-prog ??

@fredericklamar342-prog
Copy link
Copy Markdown
Contributor Author

@fredericklamar342-prog ??

i am so sorry i thought my pull request was successful i will get it done in a giffy

@OlufunbiIK
Copy link
Copy Markdown
Owner

Hi again @fredericklamar342-prog

Can you resolve it so that I can merge your PR and you can get your points?

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.

2 participants