Skip to content

Add revocation event emission with actor, reason, and timestamp #567

Description

@Oluwaseyi89

Summary

Add revocation event emission with actor, reason, and timestamp — tax_attribute's revoke_attribute silently detaches a tag from a token with no event at all, unlike every other mutating function in the contract.

Social Media Link

Let's collaborate on Discord. And ensure to star our repo.

Problem Statement

Confirmed in stellar-core/compliance-engine/contracts/tax_attribute/src/lib.rs and stellar-core/compliance-engine/contracts/tax_attribute/src/events.rs:

  1. revoke_attribute emits nothing on success: lib.rs:220-265 authorizes the caller, loads the TaxAttributeTag, checks caller == admin || caller == attribute.issuing_authority, removes the tag_id from TokenAttributes(token_id), and returns Ok(()) — there is no env.events().publish(...) call anywhere in this function, unlike add_issuer and remove_issuer which both publish Symbol::short("iss_add")/("iss_rem") events (lib.rs:88-119, 121-155).

  2. No actor is recorded anywhere: the function receives caller: Address but discards it after the authorization check — there is no persistent or event-logged trace of who revoked the attribute.

  3. No reason field exists on the revocation path at all: revoke_attribute's signature (lib.rs:220-225) is (env, caller, token_id, tag_id) — there is no parameter to capture why the attribute was revoked (e.g. regulatory change, issuer error, fraud finding).

  4. No timestamp is captured for the revocation itself: TaxAttributeTag (lib.rs:14-25) has attached_at: u64 for creation but nothing equivalent for revocation — once removed from TokenAttributes, the original Attribute(tag_id) entry in persistent storage is left untouched (not deleted), so there is stale data with no marker of when or why it stopped applying to the token.

  5. events.rs only defines initialization-related events: Initialized and ReinitializationAttempted (events.rs:3-13) — there is no AttributeRevokedEvent type at all, despite Event::IssuerAdded/Event::IssuerRemoved being declared as an enum in lib.rs:36-40 that is itself never actually published either (dead code — add_issuer/remove_issuer publish raw tuples via env.events().publish((Symbol::short(...),), ...) instead of using the Event enum).

  6. get_attributes_for_token cannot distinguish "never attached" from "revoked": since revoke_attribute only removes the tag_id from the token's index (lib.rs:255-260) without marking the underlying Attribute(tag_id) record as revoked, any code path that still holds a reference to the old tag_id and calls get_attribute directly (if such a function existed) would see no indication of revocation.

  7. Off-chain auditors and the carbon_asset/regulatory_checks contracts have no signal to react to a revocation: since no event fires, downstream indexers or compliance dashboards cannot detect a tax attribute being pulled from a token without polling get_attributes_for_token before and after and diffing results.

Required Changes

  1. Add AttributeRevokedEvent { token_id: u32, tag_id: String, revoked_by: Address, reason: String, timestamp: u64 } to events.rs, following the existing contractevent pattern used by Initialized/ReinitializationAttempted.

  2. Add a reason: String parameter to revoke_attribute's signature: revoke_attribute(env, caller: Address, token_id: u32, tag_id: String, reason: String) -> Result<(), ContractError>.

  3. Inside revoke_attribute, after successfully removing the tag_id from TokenAttributes(token_id), publish AttributeRevokedEvent with caller as revoked_by, the passed reason, and env.ledger().timestamp().

  4. Add a corresponding emit_attribute_revoked_event(env, token_id, tag_id, revoked_by, reason) helper function to events.rs, matching the style of emit_initialized_event/emit_reinitialization_attempted_event.

  5. Remove the unused Event enum (IssuerAdded/IssuerRemoved) from lib.rs or actually wire add_issuer/remove_issuer to publish it via .publish(&env) instead of raw Symbol::short tuples, for internal consistency — flag as a related but separate cleanup if out of scope for this issue.

  6. Add unit tests: revoking an attribute emits AttributeRevokedEvent with the correct token_id, tag_id, revoked_by, reason, and timestamp; revocation by the admin vs. the original issuing authority both emit the event correctly with the respective caller as revoked_by.

Acceptance Criteria

  1. revoke_attribute accepts a reason: String parameter.
  2. A successful revocation emits AttributeRevokedEvent containing the token ID, tag ID, caller address, reason, and ledger timestamp.
  3. Both admin-initiated and issuer-initiated revocations correctly attribute revoked_by to the actual caller.
  4. Existing revocation authorization logic (admin or original issuer only) is unchanged.
  5. Existing AttributeNotFound/AttributeNotAttached/NotAuthorized error paths continue to behave as before.
  6. Test coverage includes event emission verification for both authorized-caller paths.

Directory to Work on:

stellar-core/compliance-engine/

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    RustThis issue is to be implemented with Rust programming language.SorobanThis issue is to be implemented with Soroban SDK

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions