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
26 changes: 26 additions & 0 deletions EVENT_SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,30 @@ Emitted when existing offering metadata is replaced.

---

### `metadata_removed`

Emitted when the owner deletes a stale offering's metadata from instance storage.

| Index | Location | Type | Description |
|---------|----------|---------|---------------------|
| topic 0 | topics | Symbol | `"metadata_removed"` |
| topic 1 | topics | String | offering_id |
| topic 2 | topics | Address | caller (owner) |
| data | data | () | empty |

```json
{
"topics": ["metadata_removed", "offering-001", "GOWNER..."],
"data": null
}
```

**Indexer note:** After this event, `get_metadata(offering_id)` returns `None`.
The call is idempotent — removing a key that was never set (or was already removed)
emits the event and returns `Ok(())` without error.

---

### `set_authorized_caller`

Emitted when the owner updates the authorized caller address.
Expand Down Expand Up @@ -808,6 +832,7 @@ Emitted by `set_vault()` when the admin updates the registered vault address.
| `set_authorized_caller` | vault | `set_authorized_caller()` |
| `metadata_set` | vault | `set_metadata()` |
| `metadata_updated` | vault | `update_metadata()` |
| `metadata_removed` | vault | `remove_metadata()` |
| `distribute` | vault | `distribute()` |
| `init` | revenue-pool | `init()` |
| `admin_changed` | revenue-pool | `set_admin()` |
Expand All @@ -829,6 +854,7 @@ Emitted by `set_vault()` when the admin updates the registered vault address.
|---------|---------------|--------------------------------------------------------------|
| 0.0.1 | vault | Initial vault events |
| 0.0.1 | vault | Added `set_authorized_caller` event with old/new value payload (Issue #256) |
| 0.0.1 | vault | Added `metadata_removed` event on `remove_metadata()` for stale-entry cleanup |
| 0.0.1 | revenue-pool | Full revenue pool event suite with JSON examples |
| 0.0.1 | revenue-pool | Added `admin_changed` event on `set_admin` for explicit old/new admin intent |
| 0.1.0 | settlement | `payment_received`, `balance_credited` |
28 changes: 28 additions & 0 deletions contracts/vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,34 @@ impl CalloraVault {
Ok(metadata)
}

/// Remove stored offering metadata (owner only).
///
/// Deletes the `Metadata(offering_id)` storage key from instance storage.
/// Silently succeeds if the key does not exist (idempotent).
///
/// # Errors
/// - `VaultError::Unauthorized` — caller is not the vault owner.
/// - `VaultError::OfferingIdTooLong` — `offering_id` exceeds `MAX_OFFERING_ID_LEN`.
pub fn remove_metadata(
env: Env,
caller: Address,
offering_id: String,
) -> Result<(), VaultError> {
caller.require_auth();
Self::require_owner(env.clone(), caller.clone())?;
if offering_id.len() > MAX_OFFERING_ID_LEN {
return Err(VaultError::OfferingIdTooLong);
}
env.storage()
.instance()
.remove(&StorageKey::Metadata(offering_id.clone()));
env.events().publish(
(Symbol::new(&env, "metadata_removed"), offering_id, caller),
(),
);
Ok(())
}

/// Admin-gated contract upgrade.
///
/// Only the current admin may call. This will instruct the host to update
Expand Down
87 changes: 87 additions & 0 deletions contracts/vault/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,93 @@ fn metadata_remains_after_ownership_transfer() {
);
}

// ---------------------------------------------------------------------------
// remove_metadata tests
// ---------------------------------------------------------------------------

#[test]
fn remove_metadata_clears_entry() {
let env = Env::default();
let owner = Address::generate(&env);
let (_, client) = create_vault(&env);
let (usdc, _, _) = create_usdc(&env, &owner);

env.mock_all_auths();
client.init(&owner, &usdc, &None, &None, &None, &None, &None);

let offering_id = String::from_str(&env, "offering-rm-001");
let metadata = String::from_str(&env, "ipfs://bafybeig");

client.set_metadata(&owner, &offering_id, &metadata);
assert_eq!(client.get_metadata(&offering_id), Some(metadata));

client.remove_metadata(&owner, &offering_id);
assert_eq!(client.get_metadata(&offering_id), None);
}

#[test]
fn remove_metadata_emits_event() {
let env = Env::default();
let owner = Address::generate(&env);
let (vault_address, client) = create_vault(&env);
let (usdc, _, _) = create_usdc(&env, &owner);

env.mock_all_auths();
client.init(&owner, &usdc, &None, &None, &None, &None, &None);

let offering_id = String::from_str(&env, "offering-rm-002");
client.set_metadata(&owner, &offering_id, &String::from_str(&env, "ipfs://cid"));
client.remove_metadata(&owner, &offering_id);

let events = env.events().all();
let ev = events.last().expect("expected metadata_removed event");

assert_eq!(ev.0, vault_address);
let topics = &ev.1;
assert_eq!(topics.len(), 3);

let topic0: Symbol = topics.get(0).unwrap().into_val(&env);
let topic1: String = topics.get(1).unwrap().into_val(&env);
let topic2: Address = topics.get(2).unwrap().into_val(&env);

assert_eq!(topic0, Symbol::new(&env, "metadata_removed"));
assert_eq!(topic1, offering_id);
assert_eq!(topic2, owner);
}

#[test]
#[should_panic(expected = "unauthorized: owner only")]
fn unauthorized_cannot_remove_metadata() {
let env = Env::default();
let owner = Address::generate(&env);
let unauthorized = Address::generate(&env);
let (_, client) = create_vault(&env);
let (usdc, _, _) = create_usdc(&env, &owner);

env.mock_all_auths();
client.init(&owner, &usdc, &None, &None, &None, &None, &None);

let offering_id = String::from_str(&env, "offering-rm-003");
client.set_metadata(&owner, &offering_id, &String::from_str(&env, "ipfs://cid"));
client.remove_metadata(&unauthorized, &offering_id);
}

#[test]
fn remove_metadata_nonexistent_is_noop() {
let env = Env::default();
let owner = Address::generate(&env);
let (_, client) = create_vault(&env);
let (usdc, _, _) = create_usdc(&env, &owner);

env.mock_all_auths();
client.init(&owner, &usdc, &None, &None, &None, &None, &None);

let offering_id = String::from_str(&env, "offering-rm-never-set");
// Should not panic even when the key was never written
client.remove_metadata(&owner, &offering_id);
assert_eq!(client.get_metadata(&offering_id), None);
}

// ---------------------------------------------------------------------------
// Full lifecycle test
// ---------------------------------------------------------------------------
Expand Down
Loading