Skip to main content

Creating an Event

Recall that contract calls cannot directly return a value to the outside world when submitting a transaction. However, often we will want to indicate to the outside world that something has taken place (e.g. a transaction has occurred or a certain state has been reached). We can alert others that this has occurred using an event.

Declaring Events

An event can communicate an arbitrary amount of data, defined in a similar manner as a struct. Events should be declared using the #[ink(event)] attribute.

For example,

#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
value: Balance,
}

This Transfer event will contain three pieces of data - a value of type Balance and two Option-wrapped AccountId variables indicating the from and to accounts. For faster access to the event data they can have indexed fields. We can do this by using the #[ink(topic)] attribute tag on that field.

One way of retrieving data from an Option variable is using the .unwrap_or() function. You may recall using this in the my_value_or_zero() and balance_of_or_zero() functions in this project and the Incrementer project.

Emitting Events

Now that we have defined what data will be contained within the event and how to declare it, it's time to actually emit some events. We do this by calling self.env().emit_event and include an event as the sole argument to the method call.

Remember that since the from and to fields are Option, we can't just set them to particular values. Let's assume we want to set an value of 100 for the initial deployer. This value does not come from any other account, and so the from value should be None.

self.env()
.emit_event(
Transfer {
from: None,
to: Some(self.env().caller()),
value: 100,
});
info

Note: value does not need a Some(), as the value is not stored in an Option.

We want to emit a Transfer event every time that a transfer takes place. In the ERC-20 template that we have been working on, this occurs in two places: first, during the new call, and second, every time that transfer_from_to is called.

Your Turn!

Follow the ACTIONs in the template code to emit a Transfer event every time a token transfer occurs.

Remember to run cargo +nightly test to test your work.

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod erc20 {
#[cfg(not(feature = "ink-as-dependency"))]
#[ink(storage)]
pub struct Erc20 {
/// The total supply.
total_supply: Balance,
/// The balance of each user.
balances: ink_storage::collections::HashMap<AccountId, Balance>,
}

#[ink(event)]
pub struct Transfer {
// ACTION: Create a `Transfer` event with:
// * from: Option<AccountId>
// * to: Option<AccountId>
// * value: Balance
}

impl Erc20 {
#[ink(constructor)]
pub fn new(initial_supply: Balance) -> Self {
let mut balances = ink_storage::collections::HashMap::new();
balances.insert(Self::env().caller(), initial_supply);

// ACTION: Call `self.env().emit_event` with the `Transfer` event
// HINT: Since we use `Option<AccountId>`, you need to wrap accounts in `Some()`

Self {
total_supply: initial_supply,
balances
}
}

#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}

#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}

#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
self.transfer_from_to(self.env().caller(), to, value)
}

fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
let from_balance = self.balance_of_or_zero(&from);
if from_balance < value {
return false
}

// Update the sender's balance.
#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod erc20 {
#[cfg(not(feature = "ink-as-dependency"))]
#[ink(storage)]
pub struct Erc20 {
/// The total supply.
total_supply: Balance,
/// The balance of each user.
balances: ink_storage::collections::HashMap<AccountId, Balance>,
}

#[ink(event)]
pub struct Transfer {
#[ink(topic)]
from: Option<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
#[ink(topic)]
value: Balance,
}

impl Erc20 {
#[ink(constructor)]
pub fn new(initial_supply: Balance) -> Self {
let caller = Self::env().caller();
let mut balances = ink_storage::collections::HashMap::new();
balances.insert(caller, initial_supply);

Self::env().emit_event(Transfer {
from: None,
to: Some(caller),
value: initial_supply,
});

Self {
total_supply: initial_supply,
balances
}
}

#[ink(message)]
pub fn total_supply(&self) -> Balance {
self.total_supply
}

#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balance_of_or_zero(&owner)
}

#[ink(message)]
pub fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
self.transfer_from_to(self.env().caller(), to, value)
}

fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
let from_balance = self.balance_of_or_zero(&from);
if from_balance < value {
return false
}

// Update the sender's balance.
self.balances.insert(from, from_balance - value);

// Update the receiver's balance.
let to_balance = self.balance_of_or_zero(&to);
self.balances.insert(to, to_balance + value);

self.env().emit_event(Transfer {
from: Some(from),
to: Some(to),
value,
});

true
}

fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
*self.balances.get(owner).unwrap_or(&0)
}
}

#[cfg(test)]
mod tests {
use super::*;

use ink_lang as ink;

#[ink::test]
fn new_works() {
let contract = Erc20::new(777);
assert_eq!(contract.total_supply(), 777);
}

#[ink::test]
fn balance_works() {
let contract = Erc20::new(100);
assert_eq!(contract.total_supply(), 100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 0);
}

#[ink::test]
fn transfer_works() {
let mut contract = Erc20::new(100);
assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
assert!(contract.transfer(AccountId::from([0x0; 32]), 10));
assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
assert!(!contract.transfer(AccountId::from([0x0; 32]), 100));
}
}
}