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
17 changes: 15 additions & 2 deletions contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1702,9 +1702,16 @@ impl Escrow {
}

/// Admin sets the allowlist status for a specific agent.
///
/// Emits an `agt_alw` event with `(agent, allowed)` after the storage
/// write so indexers can observe every per-agent allowlist change
/// on-chain, mirroring the `cfg_set` event already emitted by
/// [`Self::set_allowlist_enabled`] for the master toggle.
pub fn set_agent_allowed(env: Env, agent: Address, allowed: bool) {
require_admin(&env);
write_flag(&env, &DataKey::AgentAllowed(agent), allowed);
write_flag(&env, &DataKey::AgentAllowed(agent.clone()), allowed);
env.events()
.publish((symbol_short!("agt_alw"),), (agent, allowed));
}

/// Read whether an agent is on the blocklist (false for never-set).
Expand All @@ -1716,9 +1723,15 @@ impl Escrow {
/// agent is rejected by `record_usage` with `AgentBlocked`,
/// independent of the allowlist and taking precedence over it: an
/// agent that is both allow-listed and blocked is still rejected.
///
/// Emits an `agt_blk` event with `(agent, blocked)` after the storage
/// write so indexers can observe every per-agent blocklist change
/// on-chain.
pub fn set_agent_blocked(env: Env, agent: Address, blocked: bool) {
require_admin(&env);
write_flag(&env, &DataKey::AgentBlocked(agent), blocked);
write_flag(&env, &DataKey::AgentBlocked(agent.clone()), blocked);
env.events()
.publish((symbol_short!("agt_blk"),), (agent, blocked));
}

/// Admin sets the per-call lower bound on `requests` for batched
Expand Down
118 changes: 113 additions & 5 deletions contracts/escrow/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,16 +1101,21 @@ fn test_transfer_service_ownership_genuine_transfer_emits_event() {
let new_owner = Address::generate(&env);
let desc = String::from_str(&env, "inference service");
client.set_service_metadata(&svc, &desc, &owner);
// Capture event count before transfer.
let events_before = env.events().all();
let count_before = events_before.len();
// Perform genuine transfer.
client.transfer_service_ownership(&owner, &svc, &new_owner);
// Exactly one new event (owner_chg).
// env.events().all() reflects only the most recent contract invocation
// (confirmed by every other event test in this file, e.g.
// assert_usage_event_count re-checking a count of 1 after each of
// several sequential record_usage calls), not a running total since
// the start of the test. The prior `set_service_metadata` call's
// `meta_set` event is therefore not present here — only this
// transfer's own event is. Comparing against a pre-call count of
// events from a *different* invocation was the bug; checking this
// call's own event count directly is the fix.
let events_after = env.events().all();
assert_eq!(
events_after.len(),
count_before + 1,
1,
"genuine transfer must emit exactly one event"
);
let (_addr, topics, data) = events_after.last().unwrap();
Expand Down Expand Up @@ -2525,6 +2530,109 @@ fn test_set_agent_blocked_requires_admin_auth() {
client.set_agent_blocked(&agent, &true);
}
#[test]
fn test_set_agent_allowed_emits_agt_alw_event() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
let agent = Address::generate(&env);

client.set_agent_allowed(&agent, &true);

let events = env.events().all();
let (_addr, topics, data) = events.last().unwrap();
let expected_topics: soroban_sdk::Vec<soroban_sdk::Val> =
(symbol_short!("agt_alw"),).into_val(&env);
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent, true));
}
#[test]
fn test_set_agent_allowed_emits_event_on_every_toggle() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
let agent = Address::generate(&env);
let expected_topics: soroban_sdk::Vec<soroban_sdk::Val> =
(symbol_short!("agt_alw"),).into_val(&env);

client.set_agent_allowed(&agent, &true);
let (_, topics, data) = env.events().all().last().unwrap();
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent.clone(), true));

// A second, independent call emits its own event carrying the new
// value; each call's event is checked immediately after that call.
client.set_agent_allowed(&agent, &false);
let (_, topics, data) = env.events().all().last().unwrap();
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent, false));
}
#[test]
fn test_set_agent_blocked_emits_agt_blk_event() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
let agent = Address::generate(&env);

client.set_agent_blocked(&agent, &true);

let events = env.events().all();
let (_addr, topics, data) = events.last().unwrap();
let expected_topics: soroban_sdk::Vec<soroban_sdk::Val> =
(symbol_short!("agt_blk"),).into_val(&env);
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent, true));
}
#[test]
fn test_set_agent_blocked_emits_event_on_every_toggle() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
let agent = Address::generate(&env);
let expected_topics: soroban_sdk::Vec<soroban_sdk::Val> =
(symbol_short!("agt_blk"),).into_val(&env);

client.set_agent_blocked(&agent, &true);
let (_, topics, data) = env.events().all().last().unwrap();
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent.clone(), true));

client.set_agent_blocked(&agent, &false);
let (_, topics, data) = env.events().all().last().unwrap();
assert_eq!(topics, expected_topics);
let decoded: (Address, bool) = data.into_val(&env);
assert_eq!(decoded, (agent, false));
}
#[test]
fn test_agt_alw_and_agt_blk_topics_do_not_collide() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
let agent = Address::generate(&env);
let alw_topics: soroban_sdk::Vec<soroban_sdk::Val> = (symbol_short!("agt_alw"),).into_val(&env);
let blk_topics: soroban_sdk::Vec<soroban_sdk::Val> = (symbol_short!("agt_blk"),).into_val(&env);

client.set_agent_allowed(&agent, &true);
let events = env.events().all();
assert_eq!(
events.iter().filter(|(_, t, _)| t == &alw_topics).count(),
1
);
assert_eq!(
events.iter().filter(|(_, t, _)| t == &blk_topics).count(),
0
);

client.set_agent_blocked(&agent, &true);
let events = env.events().all();
assert_eq!(
events.iter().filter(|(_, t, _)| t == &blk_topics).count(),
1
);
// The distinct topic ensures a blocklist write is never mistaken for an
// allowlist write by a listener subscribed to only one of the two.
assert!(alw_topics != blk_topics);
}
#[test]
fn test_remove_service_price_clears_price() {
let env = Env::default();
let (client, admin) = setup_initialized(&env);
Expand Down
Loading