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
296 lines
6.7 KiB
Rust
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);
|
|
}
|
|
}
|