#protocol

By Tezos Protocol amendments team

In this blog post, we will discuss a new feature to the existing Michelson language for the Tezos smart contracts. This new feature will allow smart contracts to deliver event messages to off-chain applications by writing event-like data into the transaction receipt in the form of internal operation results. The prominent feature of this is that we define an event emitting instruction, and an event log format that is versatile and expressive.

In addition, having event support in Michelson will avoid manual, duplicating and competing definition of critical parts of event logging systems, so that smart contracts and consumers of events can integrate with each other seamlessly. Historically there has been efforts to emulate this feature using tickets or sending transactions to other smart contracts. This new event logging capability, however, promises to provide a direct, unified and intuitive interface to deliver events with competitive gas prices.

Event messages

First let us define events in smart contracts on Tezos. Events are data written by smart contracts onto the blockchain metadata that are purely for consumption by off-chain applications. It is different from contract storage in the sense that events can be written by a smart contract, but not read by any smart contract including the one composing it. However, off-chain applications such as indexers like Tzkt or your DApp services can observe block header metadata for those event receipts containing data written by a smart contract.

Motivating example

Let us see one example. A voting smart contract allows addition and removal of quorum members. This contract can use this utility to "push" notification in changes to voters. The DApp off-chain service can process the notification events and actually send notifications to voters' contacts, via email or mobile application push notification service. Event messages in this case will be very likely of the following type expressed in CameLIGO.


type event = VoterAdded of address | VoterRemoved of address

How to emit events

Emitting events in a Tezos contract now is intuitive. A new instruction EMIT is introduced for this purpose. Here are its semantics.


    EMIT %tag 'ty :: 'ty : 'C -> operation : 'C
    > EMIT %tag 'ty / ty : S  =>  operation : S
    
    EMIT %tag :: ty : 'C -> operation : 'C
    > EMIT %tag / ty : S => operation : 'C

The EMIT %tag 'ty is an instruction taking an optional data type 'ty and composes an event operation under an optional tag tag and the supplied data as the event attachment. Like other operations from TRANSFER_TOKENS, you will need to include the events in a list of operations in the return value of your contract code to effect them. If the data type ascription is absent, EMIT will take the type on the stack top as is. A very important difference between type-ascribed EMITs and type-elided EMIT is that type-ascribed EMITs allows Michelson types to be annotated, which helps indexers to identify and correctly extract the values for their own indexes. If the tag is absent, the default tag is filled in.

Anatomy of events

Events are included in the list of internal operation results in the transaction metadata.

Here is an example of an event emission. After a successful smart contract operation, you can get a block with the successful result by sending a GET HTTP request to your node at the path /chains/main/blocks/<block-id>/operations/3/<operation-index>, where block-id is an integer indicating the block height at which your transaction is included and operation-index is the index of that transaction into the block. That REST call will return a JSON in the shape as shown below. All the key information pertaining to this event is enclosed in pairs of angled brackets which is originated from a call to EMIT.


{
  "protocol": "ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK",
  // ... fields elided for brevity
  "contents": [
    {
      "kind": "transaction",
      // ... fields elided for brevity
      "metadata": {
        // ... fields elided for brevity
        "internal_operation_results": [
          {
            "kind": "event",
            "source": "",
            "nonce": 0,
            "type": ,
            "tag": "",
            "payload": ,
            "result": {
              "status": "applied",
              "consumed_milligas": ""
            }
          }
        ]
      }
    }
  ]
}

The tag will show up in the tag field. In addition, type ascription of the event data attachment is also included in this receipt under the field type. In this receipt, you will find the data attachment in the payload field.

Example code

So allow us to present you how to emit events with Tezos contracts. The following code, executed once, will emit two events.


    parameter unit ;
    storage unit ;
    code { DROP ;
           UNIT ;
           PUSH nat 10 ;
           LEFT string ;
           EMIT %event (or (nat %number) (string %words)) ;
           PUSH string "lorem ipsum" ;
           RIGHT nat ;
           EMIT ;
           NIL operation ;
           SWAP ;
           CONS ;
           SWAP ;
           CONS ;
           PAIR }

The two events will be one with tag event, type or (nat %number) (string %words) and data Left 10; and another with no tag, type or nat string and data Right "lorem ipsum". For instance, you may find them in the following JSON responses from the node.



{
  "protocol": "ProtoALphaALphaALphaALphaALphaALphaALphaALphaDdp3zK",
  // ... fields elided for brevity
  "contents": [
    {
      "kind": "transaction",
      // ... fields elided for brevity
      "metadata": {
        // ... fields elided for brevity
        "internal_operation_results": [
          {
            "kind": "event",
            "source": "",
            "nonce": 0,
            "type": {
              "prim": "or",
              "args": [
                {
                  "prim": "nat",
                  "annots": [
                    "number"
                  ]
                },
                {
                  "prim": "string",
                  "annots": [
                    "words"
                  ]
                },
              ]
            },
            "tag": "event",
            "payload": {
              "prim": "Left",
              "args": [
                "nat": 10
              ]
            },
            "result": {
              "status": "applied",
              "consumed_milligas": "1000"
            }
          },
          {
            "kind": "event",
            "source": "",
            "nonce": 1,
            "type": {
              "prim": "or",
              "args": [
                {
                  "prim": "nat"
                },
                {
                  "prim": "string"
                },
              ]
            },
            "payload": {
              "prim": "Right",
              "args": [
                "string": "lorem ipsum"
              ]
            },
            "result": {
              "status": "applied",
              "consumed_milligas": "1000"
            }
          }
        ]
      }
    }
  ]
}

LIGO example

Today, the current LIGO compilers do not recognise the EMIT instruction. For demonstration purpose, we present a functionally equivalent CameLIGO code with embedded Michelson notation.


type storage = unit
type parameter = unit
type event = Number of nat | Words of string

(* Here we are using embedded Michelson notation *)
let emit_tagged_event (data : event) : operation =
  [%Michelson ({| {
    EMIT
      %event
      (or (nat %number) (string %words))
  } |} : (event -> operation))] data

let emit_untagged_event_with_inferred_type (data : event) : operation =
  [%Michelson ({| {EMIT} |} : (event -> operation))] data

let main (storage, _parameter: storage * parameter)
  : operation list * storage
= (
    [
      emit_tagged_event (Number 10n) ;
      emit_untagged_event_with_inferred_type (Words "lorem ipsum")
    ],
    storage
)

Demonstration

We will use this toy DApp as our demonstration here. To launch this demonstration, you need to get Rust nightly via Rustup followed by rustup toolchain install nightly. You will additionally need to run cargo install wasm-pack and NodeJS for the graphing tool to work in your browser. The architecture of this DApp consists of three parts. Firstly we need a working Tezos node running Kathmanthu, which is most easy to achieve with Flextesa for local development. Secondly we need an indexer service to serve the events in a recognisable format intended for DApps' consumption. In this repository, this indexer is located at indexer. Thirdly we need an actual DApp that consumes the events served by the indexer once the subscription starts. This service in this case is located at visualizer.The on-chain smart contract is in fact a simple token evxtz. It has dynamic exchange rate governed by the underlying smart contract located at contract/src.To launch this demonstration, you may compile the contract in contract/src/liquid.mligo and deploy it with your testing node. After it is deployed you may now mint some evxtzs with Left <amount> or burn a few with Right <amount> arguments. You may now start the indexer in the indexer folder.


cargo run <node-ip-address>:<node-port> <indexer-port>

 

On another terminal, start the visualizer in the visualizer folder that displays the exchange rate from the time of deployment of the token contract.



npm install && npm run serve

 

This will prompt you to open the graphing tool mostly likely accessible from http://localhost:8080. In there, you may fill in the contract address as you have obtained from the contract origination receipt and the indexer address in the form of localhost:<indexer-port>. The tool will subscribe to the indexer and filter events for this contract after you click on the *TRACK* button. As you mint and burn tokens, you may start to see a candle chart displaying the changes in the evxtz price against xtz as a function of block heights. For instance, you may see this chart as you keep minting evxtz.


https://hackmd-prod-images.s3-ap-northeast-1.amazonaws.com/uploads/upload_cf3d79a1798e24d76d94fefc780ed5c9.png?AWSAccessKeyId=AKIA3XSAAW6AWSKNINWO&Expires=1658844967&Signature=rx4Fd6Wwy08kSNq64FCZCVMCBpE%3D

Final words

We are happy to roll out event logs in Tezos and we believe that with this feature we can greatly improve the Tezos ecosystem by enabling a variety of applications and modes of interaction between various systems both on-chain and off-chain. We hope that you are enjoying this post and get inspired for new ideas powered by this new feature.

Happy hacking!

If you want to know more about Marigold, please follow us onsocial media (Twitter, Reddit, Linkedin)!

Scroll to top