Smart Contract Events & Logging
Implementing events and logging in smart contracts for transparency and off-chain monitoring.
Advanced⏱️ 50 min📚 Prerequisites: 1
Smart Contract Events & Logging
Events allow smart contracts to communicate with off-chain applications. They're essential for transparency and monitoring.
Why Events?
- Transparency: Log all important actions
- Off-chain Integration: DApps can listen to events
- Indexing: Easy to query historical data
- Debugging: Track contract execution
- Analytics: Monitor contract usage
Event Structure
RUST#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Event { pub event_type: String, pub contract_address: String, pub data: EventData, pub block_height: u64, pub tx_hash: String, pub timestamp: u64, } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum EventData { Transfer { from: String, to: String, amount: u64, }, Approval { owner: String, spender: String, amount: u64, }, Mint { to: String, amount: u64, }, Burn { from: String, amount: u64, }, }
Event Emitter
RUSTstruct EventEmitter { events: Vec<Event>, } impl EventEmitter { fn new() -> Self { EventEmitter { events: Vec::new(), } } fn emit( &mut self, event_type: String, contract_address: String, data: EventData, block_height: u64, tx_hash: String, ) { let event = Event { event_type, contract_address, data, block_height, tx_hash, timestamp: current_timestamp(), }; self.events.push(event); } fn get_events(&self, event_type: Option<&str>) -> Vec<&Event> { if let Some(et) = event_type { self.events.iter() .filter(|e| e.event_type == et) .collect() } else { self.events.iter().collect() } } }
Token Contract with Events
RUSTstruct TokenContract { name: String, symbol: String, total_supply: u64, balances: HashMap<String, u64>, event_emitter: EventEmitter, contract_address: String, } impl TokenContract { fn transfer( &mut self, from: String, to: String, amount: u64, tx_hash: String, block_height: u64, ) -> Result<(), String> { // Validate and transfer let balance = self.balances.get(&from).copied().unwrap_or(0); if balance < amount { return Err(String::from("Insufficient balance")); } *self.balances.entry(from.clone()).or_insert(0) -= amount; *self.balances.entry(to.clone()).or_insert(0) += amount; // Emit Transfer event self.event_emitter.emit( String::from("Transfer"), self.contract_address.clone(), EventData::Transfer { from, to, amount, }, block_height, tx_hash, ); Ok(()) } fn mint( &mut self, to: String, amount: u64, tx_hash: String, block_height: u64, ) { *self.balances.entry(to.clone()).or_insert(0) += amount; self.total_supply += amount; // Emit Mint event self.event_emitter.emit( String::from("Mint"), self.contract_address.clone(), EventData::Mint { to, amount }, block_height, tx_hash, ); } }
Event Indexing
RUSTstruct EventIndex { by_type: HashMap<String, Vec<usize>>, by_contract: HashMap<String, Vec<usize>>, by_address: HashMap<String, Vec<usize>>, } impl EventIndex { fn new() -> Self { EventIndex { by_type: HashMap::new(), by_contract: HashMap::new(), by_address: HashMap::new(), } } fn index_event(&mut self, event: &Event, index: usize) { // Index by type self.by_type .entry(event.event_type.clone()) .or_insert_with(Vec::new) .push(index); // Index by contract self.by_contract .entry(event.contract_address.clone()) .or_insert_with(Vec::new) .push(index); // Index by address (from Transfer events) if let EventData::Transfer { from, to, .. } = &event.data { self.by_address .entry(from.clone()) .or_insert_with(Vec::new) .push(index); self.by_address .entry(to.clone()) .or_insert_with(Vec::new) .push(index); } } fn query_by_type(&self, event_type: &str) -> Vec<usize> { self.by_type.get(event_type).cloned().unwrap_or_default() } fn query_by_contract(&self, contract: &str) -> Vec<usize> { self.by_contract.get(contract).cloned().unwrap_or_default() } fn query_by_address(&self, address: &str) -> Vec<usize> { self.by_address.get(address).cloned().unwrap_or_default() } }
Event Query Interface
RUSTstruct EventQuery { events: Vec<Event>, index: EventIndex, } impl EventQuery { fn new() -> Self { EventQuery { events: Vec::new(), index: EventIndex::new(), } } fn add_event(&mut self, event: Event) { let index = self.events.len(); self.events.push(event.clone()); self.index.index_event(&event, index); } fn get_transfers(&self, address: &str) -> Vec<&Event> { let indices = self.index.query_by_address(address); indices.iter() .filter_map(|&i| self.events.get(i)) .filter(|e| matches!(e.data, EventData::Transfer { .. })) .collect() } fn get_contract_events(&self, contract: &str, event_type: Option<&str>) -> Vec<&Event> { let indices = self.index.query_by_contract(contract); indices.iter() .filter_map(|&i| self.events.get(i)) .filter(|e| { if let Some(et) = event_type { e.event_type == et } else { true } }) .collect() } }
Best Practices
- Emit for All State Changes: Log important actions
- Include Relevant Data: All data needed for off-chain processing
- Use Consistent Naming: Standard event names
- Index Events: For efficient querying
- Gas Consideration: Events cost gas, but are worth it
- Event Standards: Follow platform-specific standards
Event Standards
ERC-20 Events
RUST// Transfer event EventData::Transfer { from: String, to: String, amount: u64, } // Approval event EventData::Approval { owner: String, spender: String, amount: u64, }
ERC-721 Events
RUST// Transfer event EventData::Transfer { from: String, to: String, token_id: u64, } // Approval event EventData::Approval { owner: String, approved: String, token_id: u64, }
Event Monitoring
RUSTstruct EventMonitor { subscribers: HashMap<String, Vec<Box<dyn Fn(&Event)>>>, } impl EventMonitor { fn new() -> Self { EventMonitor { subscribers: HashMap::new(), } } fn subscribe(&mut self, event_type: String, callback: Box<dyn Fn(&Event)>) { self.subscribers .entry(event_type) .or_insert_with(Vec::new) .push(callback); } fn notify(&self, event: &Event) { if let Some(callbacks) = self.subscribers.get(&event.event_type) { for callback in callbacks { callback(event); } } } }
Use Cases
- DApp Integration: Frontend listens to events
- Analytics: Track contract usage
- Notifications: Alert users of important events
- Indexing: Build searchable event databases
- Compliance: Audit trail of all actions
Code Examples
Event Emission
Emitting events from a contract
RUST
use std::collections::HashMap;
#[derive(Clone, Debug)]
struct Event {
event_type: String,
data: String,
block_height: u64,
}
struct EventEmitter {
events: Vec<Event>,
}
impl EventEmitter {
fn new() -> Self {
EventEmitter { events: Vec::new() }
}
fn emit(&mut self, event_type: String, data: String, block_height: u64) {
self.events.push(Event {
event_type,
data,
block_height,
});
}
fn get_events(&self, event_type: Option<&str>) -> Vec<&Event> {
if let Some(et) = event_type {
self.events.iter()
.filter(|e| e.event_type == et)
.collect()
} else {
self.events.iter().collect()
}
}
}
struct TokenContract {
balances: HashMap<String, u64>,
emitter: EventEmitter,
}
impl TokenContract {
fn new() -> Self {
TokenContract {
balances: HashMap::new(),
emitter: EventEmitter::new(),
}
}
fn transfer(
&mut self,
from: String,
to: String,
amount: u64,
block_height: u64,
) -> Result<(), String> {
let balance = self.balances.get(&from).copied().unwrap_or(0);
if balance < amount {
return Err(String::from("Insufficient balance"));
}
*self.balances.entry(from.clone()).or_insert(0) -= amount;
*self.balances.entry(to.clone()).or_insert(0) += amount;
// Emit Transfer event
let event_data = format!("from: {}, to: {}, amount: {}", from, to, amount);
self.emitter.emit(
String::from("Transfer"),
event_data,
block_height,
);
Ok(())
}
fn get_transfer_events(&self) -> Vec<&Event> {
self.emitter.get_events(Some("Transfer"))
}
}
fn main() {
let mut contract = TokenContract::new();
contract.balances.insert(String::from("alice"), 1000);
contract.transfer(
String::from("alice"),
String::from("bob"),
500,
100,
).unwrap();
for event in contract.get_transfer_events() {
println!("Event: {} - {}", event.event_type, event.data);
}
}Explanation:
Events are emitted when important actions occur. They provide a log of all contract activities that can be queried off-chain.
Exercises
Add Events to Contract
Add event emission to a contract function!
Starter Code:
RUST
struct Contract {
value: u64,
events: Vec<String>,
}
impl Contract {
fn set_value(&mut self, new_value: u64) {
self.value = new_value;
// Emit event here
}
}
fn main() {
let mut contract = Contract {
value: 0,
events: Vec::new(),
};
contract.set_value(100);
println!("Events: {:?}", contract.events);
}