Triggering Events
Smart contracts do not live in a vacuum. Even though they run in a very limited sandbox, from a larger perspective there will have to be a way for users to interact with them. Since smart contracts are essentially event-driven, and requests run asynchronously from the user's perspective, there is a need for triggering events by the smart contracts themselves. Of course, it would be possible for users to periodically call a view function to retrieve the latest state of the smart contract, but this burdens the nodes unnecessarily. A better way is to have the smart contracts trigger events that the user can subscribe to and that convey changes to its state.
To support events the ISCP sandbox provides a very rudimentary interface. The function
call context exposes this interface through its event()
function, which is passed a
completely arbitrary text string. It is up to the smart contract creator to format
this text string and it's up to the user to interpret this text string correctly. This
is error-prone, inconsistent, and means that a lot of code needs to be written both on
the smart contract side that generates these events, and on the client side that handles
these events. And with any change to the formatting of these events both ends need to be
modified to stay in sync.
This is why the schema tool allows you to define your own structured events.
The schema tool will generate a structure that will become part of all func call contexts.
Events can only be triggered from within a func. They will become part of the state of the
smart contract because every event is logged in the core eventlog
contract.
Therefore, they cannot be triggered from a view.
For each event defined in the events
section of the schema definition file, this
events structure will contain a member function that takes the defined types of parameters
and will automatically encode the event as a consistently formatted string and pass it
to the ISCP context's event()
function. The string consists of the name of the event,
a timestamp, and string representations of each field, all separated by vertical bars.
Here is the events
section that can be found in the demo fairroulette
smart contract:
- schema.yaml
- schema.json
"events": {
"bet": {
"address": "Address // address of better",
"amount": "Int64 // amount of iotas to bet",
"number": "Int64 // number to bet on",
},
"payout": {
"address": "Address // address of winner",
"amount": "Int64 // amount of iotas won",
},
"round": {
"number": "Int64 // current betting round number"
},
"start": {
},
"stop": {
},
"winner": {
"number": "Int64 // the winning number"
}
}
events:
bet:
address: Address // address of better
amount: Int64 // amount of iotas to bet
number: Int64 // number to bet on
payout:
address: Address // address of winner
amount: Int64 // amount of iotas won
round:
number: Int64 // current betting round number
start:
stop:
winner:
number: Int64 // the winning number
The schema tool will generate events.xx
which contains the following code for the
FairRouletteEvents struct:
- Go
- Rust
- TypeScript
package fairroulette
import "github.com/iotaledger/wasp/packages/wasmvm/wasmlib/go/wasmlib"
type FairRouletteEvents struct{}
func (e FairRouletteEvents) Bet(address wasmlib.ScAddress, amount int64, number int64) {
wasmlib.NewEventEncoder("fairroulette.bet").
Address(address).
Int64(amount).
Int64(number).
Emit()
}
func (e FairRouletteEvents) Payout(address wasmlib.ScAddress, amount int64) {
wasmlib.NewEventEncoder("fairroulette.payout").
Address(address).
Int64(amount).
Emit()
}
func (e FairRouletteEvents) Round(number int64) {
wasmlib.NewEventEncoder("fairroulette.round").
Int64(number).
Emit()
}
func (e FairRouletteEvents) Start() {
wasmlib.NewEventEncoder("fairroulette.start").
Emit()
}
func (e FairRouletteEvents) Stop() {
wasmlib.NewEventEncoder("fairroulette.stop").
Emit()
}
func (e FairRouletteEvents) Winner(number int64) {
wasmlib.NewEventEncoder("fairroulette.winner").
Int64(number).
Emit()
}
use wasmlib::*;
pub struct FairRouletteEvents {
}
impl FairRouletteEvents {
pub fn bet(&self, address: &ScAddress, amount: i64, number: i64) {
let mut encoder = EventEncoder::new("fairroulette.bet");
encoder.address(&address);
encoder.int64(amount);
encoder.int64(number);
encoder.emit();
}
pub fn payout(&self, address: &ScAddress, amount: i64) {
let mut encoder = EventEncoder::new("fairroulette.payout");
encoder.address(&address);
encoder.int64(amount);
encoder.emit();
}
pub fn round(&self, number: i64) {
let mut encoder = EventEncoder::new("fairroulette.round");
encoder.int64(number);
encoder.emit();
}
pub fn start(&self) {
EventEncoder::new("fairroulette.start").emit();
}
pub fn stop(&self) {
EventEncoder::new("fairroulette.stop").emit();
}
pub fn winner(&self, number: i64) {
let mut encoder = EventEncoder::new("fairroulette.winner");
encoder.int64(number);
encoder.emit();
}
}
import * as wasmlib from "wasmlib";
export class FairRouletteEvents {
bet(address: wasmlib.ScAddress, amount: i64, number: i64): void {
new wasmlib.EventEncoder("fairroulette.bet").
address(address).
int64(amount).
int64(number).
emit();
}
payout(address: wasmlib.ScAddress, amount: i64): void {
new wasmlib.EventEncoder("fairroulette.payout").
address(address).
int64(amount).
emit();
}
round(number: i64): void {
new wasmlib.EventEncoder("fairroulette.round").
int64(number).
emit();
}
start(): void {
new wasmlib.EventEncoder("fairroulette.start").
emit();
}
stop(): void {
new wasmlib.EventEncoder("fairroulette.stop").
emit();
}
winner(number: i64): void {
new wasmlib.EventEncoder("fairroulette.winner").
int64(number).
emit();
}
}
Notice how the generated functions use the WasmLib EventEncoder to encode the
parameters into a single string before emitting it. Here is the way in which
fairroulette
emits the bet
event in its smart contract code:
- Go
- Rust
- TypeScript
f.Events.Bet(bet.Better.Address(), bet.Amount, bet.Number)
f.events.bet(&bet.better.address(), bet.amount, bet.number);
f.events.bet(bet.better.address(), bet.amount, bet.number);
The smart contract client code can listen in to the event stream and respond to the events it deems noteworthy. The schema tool will shortly also be generating the client side code that properly parses these events and passes a type-safe structure to the client code.
In the next section we will explore how the schema tool helps to simplify function definitions.