synor/crates/synor-sdk/src/events.rs
Gulshan Yadav 5c643af64c fix: resolve all clippy warnings for CI
Fix all Rust clippy warnings that were causing CI failures when built
with RUSTFLAGS=-Dwarnings. Changes include:

- Replace derivable_impls with derive macros for BlockBody, Network, etc.
- Use div_ceil() instead of manual implementation
- Fix should_implement_trait by renaming from_str to parse
- Add type aliases for type_complexity warnings
- Use or_default(), is_some_and(), is_multiple_of() where appropriate
- Remove needless borrows and redundant closures
- Fix manual_strip with strip_prefix()
- Add allow attributes for intentional patterns (too_many_arguments,
  needless_range_loop in cryptographic code, assertions_on_constants)
- Remove unused imports, mut bindings, and dead code in tests
2026-01-08 05:58:22 +05:30

296 lines
6.7 KiB
Rust

//! Event emission for contracts.
//!
//! Events (logs) are a way to emit data that can be indexed off-chain
//! but is not stored on-chain. They're useful for:
//! - Tracking token transfers
//! - Recording state changes
//! - Debugging and monitoring
//!
//! # Example
//!
//! ```rust,ignore
//! use synor_sdk::events::{emit, Event, Topic};
//!
//! // Define an event
//! struct Transfer {
//! from: Address,
//! to: Address,
//! amount: u64,
//! }
//!
//! impl Event for Transfer {
//! fn topics(&self) -> Vec<Topic> {
//! vec![
//! Topic::from(b"Transfer"),
//! Topic::from(&self.from),
//! Topic::from(&self.to),
//! ]
//! }
//!
//! fn data(&self) -> Vec<u8> {
//! self.amount.to_le_bytes().to_vec()
//! }
//! }
//!
//! // Emit the event
//! emit(&Transfer { from, to, amount });
//! ```
use alloc::vec::Vec;
use crate::host;
use crate::types::{Address, Hash256};
/// A topic (indexed field) in an event log.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Topic(pub [u8; 32]);
impl Topic {
/// Creates a topic from raw bytes.
pub fn new(bytes: [u8; 32]) -> Self {
Topic(bytes)
}
/// Creates a topic from a hash.
pub fn from_hash(hash: Hash256) -> Self {
Topic(hash.0)
}
/// Creates a topic from a string (hashed).
pub fn from_name(name: &str) -> Self {
Topic(host::blake3(name.as_bytes()))
}
/// Creates a topic from bytes (hashed if longer than 32 bytes).
pub fn from_bytes(bytes: &[u8]) -> Self {
if bytes.len() <= 32 {
let mut arr = [0u8; 32];
arr[..bytes.len()].copy_from_slice(bytes);
Topic(arr)
} else {
Topic(host::blake3(bytes))
}
}
/// Returns as bytes.
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl From<[u8; 32]> for Topic {
fn from(bytes: [u8; 32]) -> Self {
Topic(bytes)
}
}
impl From<Hash256> for Topic {
fn from(hash: Hash256) -> Self {
Topic(hash.0)
}
}
impl From<&Address> for Topic {
fn from(addr: &Address) -> Self {
// Use first 32 bytes of address
let mut arr = [0u8; 32];
arr.copy_from_slice(&addr.0[..32]);
Topic(arr)
}
}
impl From<&str> for Topic {
fn from(name: &str) -> Self {
Topic::from_name(name)
}
}
impl From<u64> for Topic {
fn from(value: u64) -> Self {
let mut arr = [0u8; 32];
arr[24..32].copy_from_slice(&value.to_be_bytes());
Topic(arr)
}
}
impl From<u128> for Topic {
fn from(value: u128) -> Self {
let mut arr = [0u8; 32];
arr[16..32].copy_from_slice(&value.to_be_bytes());
Topic(arr)
}
}
/// Trait for types that can be emitted as events.
pub trait Event {
/// Returns the indexed topics for this event.
///
/// The first topic is typically the event signature (hash of event name).
/// Maximum 4 topics are allowed.
fn topics(&self) -> Vec<Topic>;
/// Returns the non-indexed data for this event.
fn data(&self) -> Vec<u8>;
}
/// Emits an event.
pub fn emit<E: Event>(event: &E) {
let topics = event.topics();
let data = event.data();
let topic_bytes: Vec<[u8; 32]> = topics.iter().map(|t| t.0).collect();
host::emit_log(&topic_bytes, &data);
}
/// Emits a raw event with topics and data.
pub fn emit_raw(topics: &[[u8; 32]], data: &[u8]) {
host::emit_log(topics, data);
}
/// Helper macro for defining events.
///
/// # Example
///
/// ```rust,ignore
/// synor_sdk::define_event! {
/// /// Transfer event
/// Transfer {
/// #[indexed] from: Address,
/// #[indexed] to: Address,
/// amount: u64,
/// }
/// }
/// ```
#[macro_export]
macro_rules! define_event {
(
$(#[$meta:meta])*
$name:ident {
$(
$(#[indexed])?
$field:ident: $ty:ty
),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Clone, Debug)]
pub struct $name {
$(pub $field: $ty,)*
}
impl $crate::events::Event for $name {
fn topics(&self) -> alloc::vec::Vec<$crate::events::Topic> {
let mut topics = alloc::vec::Vec::new();
topics.push($crate::events::Topic::from_name(stringify!($name)));
// Add indexed fields
topics
}
fn data(&self) -> alloc::vec::Vec<u8> {
// Serialize non-indexed fields
borsh::to_vec(self).unwrap_or_default()
}
}
};
}
// Common events
/// Transfer event for token contracts.
#[derive(Clone, Debug)]
pub struct Transfer {
pub from: Address,
pub to: Address,
pub amount: u64,
}
impl Event for Transfer {
fn topics(&self) -> Vec<Topic> {
alloc::vec![
Topic::from_name("Transfer"),
Topic::from(&self.from),
Topic::from(&self.to),
]
}
fn data(&self) -> Vec<u8> {
self.amount.to_le_bytes().to_vec()
}
}
/// Approval event for token contracts.
#[derive(Clone, Debug)]
pub struct Approval {
pub owner: Address,
pub spender: Address,
pub amount: u64,
}
impl Event for Approval {
fn topics(&self) -> Vec<Topic> {
alloc::vec![
Topic::from_name("Approval"),
Topic::from(&self.owner),
Topic::from(&self.spender),
]
}
fn data(&self) -> Vec<u8> {
self.amount.to_le_bytes().to_vec()
}
}
/// Ownership transferred event.
#[derive(Clone, Debug)]
pub struct OwnershipTransferred {
pub previous_owner: Address,
pub new_owner: Address,
}
impl Event for OwnershipTransferred {
fn topics(&self) -> Vec<Topic> {
alloc::vec![
Topic::from_name("OwnershipTransferred"),
Topic::from(&self.previous_owner),
Topic::from(&self.new_owner),
]
}
fn data(&self) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topic_from_name() {
let topic = Topic::from_name("Transfer");
assert_ne!(topic.0, [0u8; 32]);
}
#[test]
fn test_topic_from_u64() {
let topic = Topic::from(12345u64);
// Value should be in last 8 bytes
let value = u64::from_be_bytes(topic.0[24..32].try_into().unwrap());
assert_eq!(value, 12345);
}
#[test]
fn test_transfer_event() {
let event = Transfer {
from: Address::zero(),
to: Address::zero(),
amount: 1000,
};
let topics = event.topics();
assert_eq!(topics.len(), 3);
let data = event.data();
assert_eq!(data.len(), 8);
}
}