feat: add Context Groups on-chain support (storage, CRUD, membership, proxy governance)#44
feat: add Context Groups on-chain support (storage, CRUD, membership, proxy governance)#44
Conversation
- Added support for context groups in the contract state, including new fields for managing groups and their references. - Introduced a migration script to handle the transition to the new context group structure. - Updated the `mutate` function to accommodate group-related requests, with a placeholder for future implementation. - Enhanced the test suite to include migration tests for context groups, ensuring data integrity during transitions. This update enhances the contract's functionality and prepares it for future group management features.
- Added methods for creating, deleting, and managing group memberships within the context configuration. - Enhanced the `mutate` function to handle group-related requests, including error handling for group operations. - Introduced a new `GroupInfoResponse` struct for querying group details. - Implemented tests for group creation, querying, and membership management to ensure functionality and integrity. This update significantly expands the contract's capabilities for managing context groups.
…ion and querying - Implemented methods for registering and unregistering contexts within groups, ensuring only group admins can perform these actions. - Updated the `mutate` function to handle new context-related requests, including logging for successful registrations and unregistrations. - Added new query functions to retrieve contexts associated with a group and to find the group of a specific context. - Expanded the test suite to include integration tests for context registration and validation of group-context relationships. This update significantly improves the contract's capabilities for managing context associations within groups.
…text groups - Added functionality to set the target application for context groups, allowing group admins to update the target application associated with a group. - Enhanced the `mutate` function to handle `SetTargetApplication` requests, including logging for successful updates. - Implemented tests to verify the correct behavior of target application management, including checks for admin permissions and handling of non-existent groups. This update significantly improves the management capabilities of context groups by allowing dynamic updates to their target applications.
…egistration - Implemented `proxy_register_in_group` and `proxy_unregister_from_group` methods in the context configuration contract, allowing context proxies to manage group memberships. - Updated the `ProposalAction` enum to include actions for registering and unregistering contexts from groups. - Enhanced the `mutate` function to handle these new actions, ensuring proper interaction with the context configuration contract. This update improves the functionality of context proxies by enabling them to manage group associations directly.
… tag - Changed the dependency for `calimero-context-config` from a local path to a specific git tag (`0.10.0-rc.1`), ensuring consistent versioning and easier dependency management.
…ability - Simplified the formatting of function signatures and require statements in `mutate.rs` and `query.rs` for better readability. - Enhanced test assertions in `groups.rs` to improve clarity and maintainability. - These changes do not alter functionality but improve the overall code structure and consistency.
…ions - Updated function signatures in `groups.rs` and `sandbox.rs` to use lifetime parameters for better memory management. - Enhanced test assertions to reflect updated expected values, ensuring accuracy in test outcomes. - These changes improve code clarity and maintainability without altering existing functionality.
…d member handling - Introduced `admin_nonces` and `members` fields in `OnChainGroupMeta` to track admin nonces and group members. - Updated the `mutate` function to include nonce checks for group operations, ensuring proper admin authorization. - Modified member management logic to utilize a set for members, improving efficiency and clarity. - Enhanced tests in `groups.rs` to validate new member handling and nonce functionality, ensuring robustness in group operations. This update significantly improves the integrity and functionality of group management within the context configuration.
- Introduced `approved_registrations` field in `OnChainGroupMeta` to track approved context registrations. - Implemented `approve_context_registration` method in the `mutate` function, allowing group admins to approve context registrations. - Updated the `mutate` function to handle new `ApproveContextRegistration` request type. - Enhanced tests in `groups.rs` to validate the approval process for context registrations, ensuring only admins can approve. This update significantly enhances the context management capabilities within groups by allowing controlled registration approvals.
- Introduced `context_ids` field in `OnChainGroupMeta` to maintain a forward index of contexts belonging to a group, enabling efficient pagination. - Updated the `mutate` function to initialize and manage `context_ids`, including insertion and removal of context IDs during group operations. - Enhanced the query functionality to retrieve context IDs associated with a group, improving the overall context management capabilities. This update significantly enhances the ability to track and manage contexts within groups, optimizing performance for context-related queries.
| calimero-context-config-near = { path = "./contracts/near/context-config" } | ||
|
|
||
| [patch."https://github.com/calimero-network/core"] | ||
| calimero-context-config = { path = "../core/crates/context/config" } |
There was a problem hiding this comment.
Local filesystem path patch committed in Cargo.toml
High Severity
The [patch] section overrides calimero-context-config with a local filesystem path (../core/crates/context/config). This breaks builds for anyone who doesn't have the core repo checked out at that exact relative path, including CI pipelines. The corresponding Cargo.lock also lost its git source line, confirming the override is active.
- Updated the `drain` method in `ContextConfigs` to clear all relevant fields in the group, including `admin_nonces`, `members`, `approved_registrations`, and `context_ids`. - This change ensures that all group-related data is properly reset, improving data integrity during context operations.
…oup_id - Modified the `proxy_unregister_from_group` method to accept a `group_id` parameter, enhancing the validation of context-group associations. - Updated the `ProposalAction` enum to include the `group_id` in the unregistration action, ensuring proper handling during group management operations. - These changes improve the robustness of group membership management within the context proxy.
Adds a public query method to retrieve the current nonce for a group admin, mirroring the existing fetch_nonce method for context members. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add on-chain commit/reveal flow for group invitations, mirroring the existing context invitation pattern. This allows joiners to claim admin-signed invitations without needing admin action at join time. - Add GroupInvitationCommitments/GroupUsedInvitations storage prefixes - Add invitation_commitments and used_invitations fields to OnChainGroupMeta - Create group_invitation.rs with commit_group_invitation and reveal_group_invitation - Skip nonce check for invitation variants (joiner has no admin nonce) - Dispatch new GroupRequestKind variants in handle_group_request - Initialize/clear new fields in create_group, delete_group, and erase - Add 04_group_invitations migration for existing groups Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
contracts/near/context-config/src/sys/migrations/04_group_invitations.rs
Show resolved
Hide resolved
Add group_members paginated view method returning admin/member entries with role tags, enabling cross-node member sync. Add JoinContextViaGroup mutation allowing verified group members to join contexts within their group without the full invitation ceremony. Uses privileged signer bypass to add the new member to the context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track per-group signer-to-context identities and use authorized guard mutation to remove group members from all registered contexts when membership is revoked. Made-with: Cursor
Store `migration_method` in `OnChainGroupMeta` so peer nodes can recover it during group sync. Without this, `maybe_lazy_upgrade` on peer nodes short-circuits because `migration: None` after sync. - Add `migration_method: Option<String>` to `OnChainGroupMeta` - Add contract storage migration (05_group_migration_method) - Update `set_group_target` to accept and store migration method - Return `migration_method` in `GroupInfoResponse` query - Update `GroupRequestKind::SetTargetApplication` with new field - Add sandbox tests for migration method propagation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nd visibility Implement on-chain group permission system: - MemberCapabilities bitfield (CAN_CREATE_CONTEXT, CAN_INVITE_MEMBERS, CAN_JOIN_OPEN_CONTEXTS) - VisibilityMode (Open/Restricted) per context with allowlists - Migration 06: existing members get CAN_JOIN_OPEN_CONTEXTS - Capability checks on register_context, join_context_via_group, reveal_group_invitation - New mutations: set_member_capabilities, set_context_visibility, manage_context_allowlist, set_default_capabilities, set_default_visibility - New queries: context_visibility, context_allowlist - AdminContextJoinEvent (NEP-297) for admin force-joins on restricted contexts - Auto-add creator to allowlist on restricted context registration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sions Add 13 new tests covering: - CAN_CREATE_CONTEXT capability gate on register_context_in_group - Admin bypass of capability checks - Creator auto-added to allowlist on Restricted context - CAN_JOIN_OPEN_CONTEXTS gate on join_context_via_group - Restricted context blocked for non-allowlist members - Allowlist member can join restricted context - New members inherit default_member_capabilities - Non-admin rejected from set_member_capabilities - Lockdown mode (default_capabilities=0) - Default visibility inheritance (Restricted) - Non-creator/non-admin rejected from set_context_visibility - Allowlist add/remove lifecycle Also fix existing tests to pass visibility_mode field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Erase function doesn't clear all group storage collections
- Added clearing of member_contexts, member_capabilities, context_visibility, and context_allowlists collections to match delete_group behavior.
- ✅ Fixed: Removed member privileges not revoked from
used_open_invitationsguard- Added privilege revocation from used_open_invitations guard during cascade removal of group members.
Or push these changes by commenting:
@cursor push a78ec72d3b
Preview (a78ec72d3b)
diff --git a/contracts/near/context-config/src/mutate.rs b/contracts/near/context-config/src/mutate.rs
--- a/contracts/near/context-config/src/mutate.rs
+++ b/contracts/near/context-config/src/mutate.rs
@@ -578,9 +578,7 @@
} => {
self.set_default_capabilities(signer_id, group_id, default_capabilities);
}
- GroupRequestKind::SetDefaultVisibility {
- default_visibility,
- } => {
+ GroupRequestKind::SetDefaultVisibility { default_visibility } => {
self.set_default_visibility(signer_id, group_id, default_visibility.into());
}
}
@@ -611,12 +609,9 @@
let used_invitations = IterableSet::new(Prefix::GroupUsedInvitations(*group_id));
let member_contexts = IterableMap::new(Prefix::GroupMemberContexts(*group_id));
- let member_capabilities =
- IterableMap::new(Prefix::GroupMemberCapabilities(*group_id));
- let context_visibility =
- IterableMap::new(Prefix::GroupContextVisibility(*group_id));
- let context_allowlists =
- IterableMap::new(Prefix::GroupContextAllowlists(*group_id));
+ let member_capabilities = IterableMap::new(Prefix::GroupMemberCapabilities(*group_id));
+ let context_visibility = IterableMap::new(Prefix::GroupContextVisibility(*group_id));
+ let context_allowlists = IterableMap::new(Prefix::GroupContextAllowlists(*group_id));
let meta = OnChainGroupMeta {
app_key,
@@ -755,6 +750,10 @@
let identity = context_identity.rt().expect("infallible conversion");
context.members.priviledges().revoke(&identity);
context.application.priviledges().revoke(&identity);
+ context
+ .used_open_invitations
+ .priviledges()
+ .revoke(&identity);
env::log_str(&format!(
"Cascade-removed `{}` from context `{}`",
@@ -784,10 +783,7 @@
.map_or(false, |caps| {
caps & MemberCapabilities::CAN_CREATE_CONTEXT != 0
});
- require!(
- can_create,
- "insufficient capabilities to create context"
- );
+ require!(can_create, "insufficient capabilities to create context");
let mode = visibility_mode.unwrap_or(group.default_context_visibility);
@@ -974,10 +970,7 @@
let on_allowlist = group
.context_allowlists
.contains_key(&(*context_id, signer_id.clone()));
- require!(
- on_allowlist,
- "not on allowlist for this restricted context"
- );
+ require!(on_allowlist, "not on allowlist for this restricted context");
} else {
// Open context: requires CAN_JOIN_OPEN_CONTEXTS
let can_join = group
@@ -986,10 +979,7 @@
.map_or(false, |caps| {
caps & MemberCapabilities::CAN_JOIN_OPEN_CONTEXTS != 0
});
- require!(
- can_join,
- "insufficient capabilities to join open context"
- );
+ require!(can_join, "insufficient capabilities to join open context");
}
} else if is_restricted {
// Admin force-joining a restricted context they're not on the allowlist for
@@ -1059,10 +1049,7 @@
"only group admins can set member capabilities"
);
- require!(
- group.members.contains(&member),
- "member not in group"
- );
+ require!(group.members.contains(&member), "member not in group");
let _ignored = group.member_capabilities.insert(*member, capabilities);
@@ -1142,20 +1129,19 @@
);
for member in &add {
- let _ignored = group
- .context_allowlists
- .insert((*context_id, **member), ());
+ let _ignored = group.context_allowlists.insert((*context_id, **member), ());
}
for member in &remove {
- let _ignored = group
- .context_allowlists
- .remove(&(*context_id, **member));
+ let _ignored = group.context_allowlists.remove(&(*context_id, **member));
}
env::log_str(&format!(
"Updated allowlist for context `{}` in group `{}`: added {}, removed {}",
- context_id, group_id, add.len(), remove.len()
+ context_id,
+ group_id,
+ add.len(),
+ remove.len()
));
}
diff --git a/contracts/near/context-config/src/sys.rs b/contracts/near/context-config/src/sys.rs
--- a/contracts/near/context-config/src/sys.rs
+++ b/contracts/near/context-config/src/sys.rs
@@ -57,6 +57,10 @@
group.context_ids.clear();
group.invitation_commitments.clear();
group.used_invitations.clear();
+ group.member_contexts.clear();
+ group.member_capabilities.clear();
+ group.context_visibility.clear();
+ group.context_allowlists.clear();
}
self.context_group_refs.clear();- Remove redundant `context_count` field from `OnChainGroupMeta`; derive count from `context_ids.len()` at query time to prevent drift - Add missing `clear()` calls for `member_contexts`, `member_capabilities`, `context_visibility`, and `context_allowlists` in `erase` - Add `member_contexts` cleanup to `proxy_unregister_from_group` to match the cleanup already done in `unregister_context_from_group` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
contracts/near/context-config/src/sys/migrations/04_group_invitations.rs
Show resolved
Hide resolved
…fix migration schemas - Add context_visibility and context_allowlists cleanup to both unregister_context_from_group and proxy_unregister_from_group to prevent storage leaks and stale data on re-registration - Remove nonexistent context_count field from OldOnChainGroupMeta in migrations 04-06 to match the actual on-chain schema and prevent borsh deserialization corruption Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| .expect("group does not exist"); | ||
| let _ignored = group | ||
| .member_contexts | ||
| .insert((signer_id.clone(), *context_id), *new_member); |
There was a problem hiding this comment.
Repeated group join overwrites tracked context identity
Medium Severity
In join_context_via_group, the member_contexts map uses (SignerId, ContextId) as the key. If the same signer joins the same context multiple times with different ContextIdentity values, insert silently overwrites the previous entry. When remove_group_members later cascade-removes members, it only finds the last recorded identity — earlier identities become orphaned context members that can never be cascade-removed.
| err_str.contains("only group admins can register contexts"), | ||
| "Expected 'only group admins can register contexts', got: {}", | ||
| err_str | ||
| ); |
There was a problem hiding this comment.
Test expects wrong error message for register rejection
Low Severity
test_non_admin_register_rejected asserts the error contains "only group admins can register contexts", but register_context_in_group produces "insufficient capabilities to create context" when a non-member signer lacks the CAN_CREATE_CONTEXT capability. The assertion string never matches, so this test would always fail.



Summary
Implements the Context Groups feature across the context-config and context-proxy NEAR contracts. Context Groups introduce a hierarchical relationship between contexts, enabling:
Changes done
OnChainGroupMetaand extendedContextConfigswithgroupsandcontext_group_refs; addedContext.group_id, newPrefixvariants (9–11), and03_context_groupsmigration with feature gate.handle_group_request()withcreate_group(),delete_group(),add_group_members(),remove_group_members().group()andis_group_admin().register_context_in_group()andunregister_context_from_group()mutations.group_contexts()(paginated scan) andcontext_group()(O(1) reverse lookup) queries.set_group_target()for admin-controlled application version updates with audit logging.proxy_register_in_group()andproxy_unregister_from_group()on context-config (callable only by the context’s proxy).ProposalAction::RegisterInGroupandProposalAction::UnregisterFromGroupin proxy’sexecute_proposal()via cross-contract calls to context-config.proxy_register_in_groupandproxy_unregister_from_group.03_context_groupsmigration and migration roundtrip tests.context-config/tests/groups.rs.calimero-context-configdependency andCargo.lock.Dependency on
corerepoThis PR depends on the shared types crate changes in the
corerepo (feat/context-management-proposalbranch) which addsContextGroupId,AppKey,GroupRequest,GroupRequestKind,RequestKind::Group,ProposalAction::RegisterInGroup, andProposalAction::UnregisterFromGroup.Before merging: Once the core PR is merged and a new version is released, update the
calimero-context-configtag inCargo.toml(currently0.10.0-rc.1) to the new release tag containing the context groups types.Test plan
cd contracts/near/context-config && ./build.sh && cd ../context-proxy && ./build.shcargo test --package calimero-context-config-nearcargo test --package calimero-context-proxy-nearcargo check --features "migrations,03_context_groups"calimero-context-configtag inCargo.tomlto new core release version after core PR mergesNote
High Risk
High risk because it introduces new on-chain state (groups, permissions, allowlists, invitation commitments) and new authorization paths/cross-contract calls, plus multiple storage migrations that must preserve existing contract state.
Overview
Adds on-chain Context Groups to the
context-configNEAR contract, introducing new persistent group state (groups,context_group_refs,Context.group_id) and a newRequestKind::Groupmutation flow for group CRUD, membership management, context registration, and target-application updates (including storedmigration_method).Implements group-level permissions and visibility controls (capability bitfields, per-context
Open/Restrictedvisibility with allowlists) and a commit/reveal group invitation mechanism with signature verification, replay protection, and nonce handling; also emits a structured NEP-297 event when an admin force-joins a restricted context.Extends proxy governance:
context-proxyproposals can nowRegisterInGroup/UnregisterFromGroupvia new cross-contract calls, andcontext-configadds corresponding proxy-only entrypoints; adds storage wipe support for groups, plus new feature-gated migrations (03–06) and substantial integration/migration test coverage.Written by Cursor Bugbot for commit 370394b. This will update automatically on new commits. Configure here.