Skip to content
Merged
Show file tree
Hide file tree
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
Binary file modified .DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ version = "0.1.0"
edition = "2021"

[dependencies]
soroban-sdk = "20.0.0"
soroban-sdk = "=21.7.7"

[dev-dependencies]
soroban-sdk = { version = "20.0.0", features = ["testutils"] }
soroban-sdk = { version = "=21.7.7", features = ["testutils"] }

[lib]
name = "grainlify_contracts"
Expand Down
2 changes: 1 addition & 1 deletion contracts/DELEGATE_PERMISSION_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Program:
- `revoke_program_delegate`
- `single_payout_by`
- `batch_payout_by`
- `create_program_release_schedule_by`
- `create_prog_release_schedule_by`
- `trigger_program_releases_by`
- `release_prog_schedule_manual_by`
- `update_program_metadata`
2 changes: 1 addition & 1 deletion contracts/bounty_escrow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [
]

[workspace.dependencies]
soroban-sdk = "21.7.7"
soroban-sdk = "=21.7.7"

[profile.release]
opt-level = "z"
Expand Down
1 change: 0 additions & 1 deletion contracts/bounty_escrow/contracts/escrow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ testutils = []
[dependencies]
soroban-sdk = { workspace = true }
grainlify-core = { path = "../../../grainlify-core", default-features = false }
grainlify-contracts = { path = "../../../" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["alloc", "testutils"] }
48 changes: 48 additions & 0 deletions contracts/bounty_escrow/contracts/escrow/RENEWAL_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Renewal And Rollover Policy

This policy defines how bounty lifetime extension (`renew_escrow`) and cycle rollover (`create_next_cycle`) behave in `bounty-escrow`.

## Renewal (`renew_escrow`)

`renew_escrow(bounty_id, new_deadline, additional_amount)` updates a currently active escrow without replacing its `bounty_id`.

Rules:
- Escrow must exist and be in `Locked` status.
- Renewal must happen before expiry (`now < current_deadline`).
- `new_deadline` must be strictly greater than the current deadline.
- `additional_amount` may be `0` (deadline-only extension) or positive (top-up).
- Negative `additional_amount` is rejected.
- The original depositor must authorize the renewal.

State effects:
- `deadline` is set to `new_deadline`.
- If `additional_amount > 0`, both `amount` and `remaining_amount` are increased exactly by `additional_amount`.
- Funds are transferred from depositor to contract for top-ups.
- A `RenewalRecord` is appended to immutable renewal history.

## Rollover (`create_next_cycle`)

`create_next_cycle(previous_bounty_id, new_bounty_id, amount, deadline)` starts a new cycle as a fresh escrow while preserving prior-cycle history.

Rules:
- Previous escrow must exist and be finalized (`Released` or `Refunded`).
- Previous cycle may have only one direct successor.
- `new_bounty_id` must not already exist and must differ from `previous_bounty_id`.
- `amount` must be strictly positive.
- `deadline` must be in the future.
- The original depositor authorizes funding for the new cycle.

State effects:
- New `Escrow` is created in `Locked` status.
- New funds are transferred from depositor to contract.
- Cycle links are updated:
- previous `next_id = new_bounty_id`
- new `previous_id = previous_bounty_id`
- new cycle depth increments by one.

## Security Notes

- No post-expiry resurrection: renewal after deadline is rejected.
- No hidden balance loss: renew without top-up does not change token balances.
- No double-successor forks: rollover chain enforces one successor per cycle.
- Renewal history is append-only and remains available after rollover.
30 changes: 11 additions & 19 deletions contracts/bounty_escrow/contracts/escrow/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,14 @@
use crate::CapabilityAction;
use soroban_sdk::{contracttype, symbol_short, Address, BytesN, Env, Symbol};

// Import storage key audit module for shared constants
use grainlify_contracts::storage_key_audit::{shared, bounty_escrow as be_keys};

// ── Version constant ─────────────────────────────────────────────────────────

/// Canonical event schema version included in **every** event payload.
///
/// Increment this value and update all emitter functions whenever the
/// payload schema changes in a breaking way. Non-breaking additions that is new
/// optional fields do not require a version bump.
pub const EVENT_VERSION_V2: u32 = shared::EVENT_VERSION_V2;
pub const EVENT_VERSION_V2: u32 = 2;

// ═══════════════════════════════════════════════════════════════════════════════
// INITIALIZATION EVENTS
Expand Down Expand Up @@ -91,7 +88,7 @@ pub struct BountyEscrowInitialized {
/// # Panics
/// Never panics; publishing is infallible in Soroban.
pub fn emit_bounty_initialized(env: &Env, event: BountyEscrowInitialized) {
let topics = (be_keys::BOUNTY_INITIALIZED,);
let topics = (symbol_short!("init"),);
env.events().publish(topics, event.clone());
}

Expand Down Expand Up @@ -853,11 +850,6 @@ pub fn emit_risk_flags_updated(env: &Env, event: RiskFlagsUpdated) {
/// - The `beneficiary` field allows off-chain indexers to build a
/// per-address ticket inbox without scanning all tickets.

pub fn emit_deprecation_state_changed(env: &Env, event: DeprecationStateChanged) {
let topics = (symbol_short!("deprec"),);
env.events().publish(topics, event);
}

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NotificationPreferencesUpdated {
Expand Down Expand Up @@ -991,7 +983,7 @@ pub fn emit_emergency_withdraw(env: &Env, event: EmergencyWithdrawEvent) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapabilityIssued {
/// Monotonic capability id (matches [`crate::DataKey::Capability`]).
pub capability_id: u64,
pub capability_id: BytesN<32>,
/// Address that created and vouches for this capability.
pub owner: Address,
/// Address authorised to exercise this capability.
Expand All @@ -1012,7 +1004,7 @@ pub struct CapabilityIssued {

/// Emit [`CapabilityIssued`]
pub fn emit_capability_issued(env: &Env, event: CapabilityIssued) {
let topics = (symbol_short!("cap_new"), event.capability_id);
let topics = (symbol_short!("cap_new"), event.capability_id.clone());
env.events().publish(topics, event);
}

Expand All @@ -1036,7 +1028,7 @@ pub fn emit_capability_issued(env: &Env, event: CapabilityIssued) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapabilityUsed {
/// Capability that was exercised.
pub capability_id: u64,
pub capability_id: BytesN<32>,
/// Address that exercised the capability.
pub holder: Address,
/// Action that was performed.
Expand All @@ -1055,7 +1047,7 @@ pub struct CapabilityUsed {

/// Emit [`CapabilityUsed`]
pub fn emit_capability_used(env: &Env, event: CapabilityUsed) {
let topics = (symbol_short!("cap_use"), event.capability_id);
let topics = (symbol_short!("cap_use"), event.capability_id.clone());
env.events().publish(topics, event);
}

Expand All @@ -1078,14 +1070,14 @@ pub fn emit_capability_used(env: &Env, event: CapabilityUsed) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapabilityRevoked {
/// Capability that was revoked
pub capability_id: u64,
pub capability_id: BytesN<32>,
pub owner: Address,
pub revoked_at: u64,
}

/// Emit [`CapabilityRevoked`]
pub fn emit_capability_revoked(env: &Env, event: CapabilityRevoked) {
let topics = (symbol_short!("cap_rev"), event.capability_id);
let topics = (symbol_short!("cap_rev"), event.capability_id.clone());
env.events().publish(topics, event);
}

Expand Down Expand Up @@ -1224,7 +1216,7 @@ pub fn emit_timelock_configured(env: &Env, event: TimelockConfigured) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AdminActionProposed {
pub version: u32,
pub action_type: crate::ActionType,
pub action_type: CapabilityAction,
pub execute_after: u64,
pub proposed_by: Address,
pub timestamp: u64,
Expand Down Expand Up @@ -1257,7 +1249,7 @@ pub fn emit_admin_action_proposed(env: &Env, event: AdminActionProposed) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AdminActionExecuted {
pub version: u32,
pub action_type: crate::ActionType,
pub action_type: CapabilityAction,
pub executed_by: Address,
pub executed_at: u64,
}
Expand Down Expand Up @@ -1289,7 +1281,7 @@ pub fn emit_admin_action_executed(env: &Env, event: AdminActionExecuted) {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AdminActionCancelled {
pub version: u32,
pub action_type: crate::ActionType,
pub action_type: CapabilityAction,
pub cancelled_by: Address,
pub cancelled_at: u64,
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/bounty_escrow/contracts/escrow/src/invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ pub(crate) fn set_disabled_for_test(env: &Env, disabled: bool) {
#[cfg(test)]
pub(crate) fn call_count_for_test(env: &Env) -> u32 {
env.storage().instance().get(&INV_CALLS).unwrap_or(0)
}
}
Loading
Loading