How Events Work on a Deeper Level

The course is part of this learning path

Start course
1h 1m

This course covers Ethereum denominations, the ABI array, Ethereum networks, private/public key cryptography, and much more!


In the previous lectures, we have talked a little bit briefly about events, but in this lecture, I really want to dive into what they are and how they work. And you can see I've already opened a couple of tabs here and I'm going to go through them in a second. But first of all, I want to talk about the three major use cases of events, and the first one would be return values and that might surprise you. I'm going to give you an example in a second. The second one is asynchronous triggers, and the third one is simply a form of a cheaper storage. Now, let me start with the first one, return values. One of the problems on the Ethereum blockchain, especially with the EVM and especially with writing functions, so called transaction changing functions, is that they cannot return any values. And that might surprise you because you've seen before when we run the JavaScript VM, when we deploy the smart contract, let me deploy the smart contract. Then you see here the start game function, I've added a new return value here. It's a public payable function. It's not a view function, it's not a constant function, and it returns an address now. And I've added this return's address and return address(threeInARow). Before, we were emitting this event, and you remember we were opening this and then copying the address of the game and then going to the game and then saying the game is running under this address. Why can't we just add a return value here?

That's somehow really stupid, isn't it? Now, let's just give this a try and you will be surprised. So, I'm going to start a new game 0.1 Ether. And I'm opening this and we have a decoded output there as an address. So, why can't we use this? The problem is as long as we're working with the Javascript VM right here in Javascript, the return values outside of the blockchains are working. The problem starts when you are working with a real blockchain. When you are sending off a transaction on a real blockchain, then you are actually getting a transaction hash back and you have to wait for this transaction to be mined. And I'm going to run this now on a real blockchain. I'm going to use my Injector Web3 provider and MetaMask and we're installing MetaMask later in this course. So, just stay tuned and watch this video now. If you don't know what is MetaMask, it doesn't matter. What is important though is that you understand that we are now going away from the in memory blockchain simulation here to a real blockchain through this so called Ropsten test network, which is exactly working the same way as the main network. Just as a side blockchain, a copy of the original blockchain where ethers cost no money, but the way it's working with transactions and the smart contract is exactly the same. So, just to give you a little example, I'm here on the Ropsten test network and I have an account in my MetaMask, and I'm going via MetaMask. So, I'm going from Remix to MetaMask, from MetaMask to the Ropsten blockchain via a service called Infura. If you don't understand anything for now just roll with me for this simple example and keep in mind, in our Javascript VM, we have this decoded output, but as soon as I start deploying or writing a transaction to the network, this pop up appears and then I hit to confirm and then when I scroll down, what I get actually is a link. Etherscan is a block explorer where I can look into the publicly available data of the blockchain. The blockchain itself is a public ledger so all the data is always available and I have to wait until the transaction is actually mined. Now, you see it took some time that my game instance popped up here and this time between the actual transaction where  I  sent  off the transaction when I get back a hash and when the transaction is mined, this time is where my return values are not available, but why are they still not available afterwards? And that is more technical problem of the Ethereum blockchain with return values. When the transaction is mined, then we get back a transaction receipt and that transaction receipt should have always a specific size. And if we add return values of unlimited size, then we are maybe, bloating up this transaction receipt. And they are thinking about changing this further down the road with so called EIPs, Ethereum improvement proposals, but for now there is no return value. Now, obviously I just deployed the smart contract. I'm going to show you what happens if I am starting a new game. So, I start a new game. I confirm this and then again we get back a transaction hash, and I click on this and it opens up etherscan, a block explorer. You can go to and you see what currently happens on the Ethereum blockchain. And we have to wait for the transaction to be mined. In order to do anything further, we have to wait until our transaction is baked into a block, and that happened now. And if I open my transaction details now, still my decoded output is empty but my logs are there. So, I can use my logs, so called events in order to give return values from my transactions that ascent towards the blockchain. Why do I still want to have return values if I can't read them out anyway? Now, that has something to do with how Solidity works. In Solidity, within the same transaction, you very well can get return values from writing functions. Just if this function is calling that function, then inside this function you can work with the return values, and there is an example here on Stack Exchange that I stumbled upon, and this, it's an example out of a real world, it's not a full example. But the idea was here that you have a smart contract which creates a new bounty, it's like a bug bounty, and it creates a new entry and that entry is bytes32 entry and this will return this bytes32 entry. Now, when you have another smart contract that has a function create bounty and you call this function from outside, then this smart contract will call in turn the other smart contract and this is a writing function. So, there is no view or pure here. So, it's really a writing function but it will get these bytes32 index from this storage mapping probably,  and save it himself somewhere into a storage variable. So, there is a reason why you want to have return values from writing functions, even though from outside meaning from your front-end, from your view layer, you can't really access those and the areas where the events are coming in. Events are also asynchronous triggers and we have used them. So, if we open up our ThreeInARow game, then what we can see here in our joinGame() function is we emitted an event PlayerJoint. Now, we haven't really used that free JS yet, but in the free JS, in Javascript, in html javascript, you can deploy an event listener into a blockchain node, and that event listener will then fire the callback to your frontend when a certain event has fired. And the idea here is very simple. Instead of creating the game and then another person or constantly calling a function in your smart contract if a player has joined, you basically just wait for this player event, this PlayerJoined event to fire. And as soon as the PlayerJoined event has fired, then you're ready to start the game. And then, on the next player event you just have to see who is the active player. And once you know the active player, once it is the same that you have in your wallet in your account, once the address is the same, you can start playing the game. And that is exactly how asynchronous triggers work. Another example would be, if you want to wait maybe for a purchase bidding item to go through or purchase to go through. So, if something is purchased, you get an event and then you dispatch a parcel. So, there's these events where they come in. And the last example is a cheap data storage and the idea is here to use events to emit data that you don't need inside smart contracts. Why you don't need them inside smart contracts? The problem with events are they can only be caught from outside solidity, so you need to use them in your JavaScript part of your code. You cannot see any events inside Solidity. You can emit them and we emit them on a lot of different places in our game, but you see there is never actually an event listener in Solidity. And the reason for this is really that events live on a so called side chain inside the blockchain data structure. They have the same cryptographic validation, so there is no reason to believe that events can be emitted outside of smart contracts. So, you have all the security that the smart contract offers and the blockchain is offering, but you can only react to events outside of your Solidity code, and there you can actually do really cool things. And one of the cool things is something that I want to show you now that I stumbled upon as well. Now, that's a game actually, and it's called Adventureum. And if you've watched Bandersnatch, there is this Black Mirror from Black Mirror: Bandersnatch where you have several options where you can go, then you might know where I'm going to, but even if you don't know, then let me just read out the text. Adventureum is a text-based, choose-your-own game where each point in the story is defined by other players. In order to minimize storage costs, All this text is kept in logs, so-called events, rather than written to a contract storage using a dumb contract pattern, which I wrote. So, let me start this game. And I have to switch to the mainnet because everything is on the mainnet. So, all these texts that you see here popping up in a second hopefully is written into an event that is emitted during creation of this game or adding another path. So, the paths are, I'll read this text and then you can say, I go to the forest, I head for the town, or I look around the field. And if I go to the forest, I will walk another path in my game and then another text comes, and then I go for a swim and so on until you reach a point in the game where there is no path or there's no edge set yet. And then you can add your own text, but that text is not stored in a smart contract. All that is stored in a smart contract is the index to a specific path and the options to the path, and the text itself is stored in an event. So, I save a lot of gas by storing this in an event rather than in a storage variable. And the reason for that is that when you store text, when you store strings in storage variables, it costs you a lot of gas versus storing them inside events, it costs you much less gas, almost negligible amount of gas in order to store strings. So, events can really be used as a cheap way of storing data. And I think this game very, very well demonstrates this. Now back to our game and we are going to add the rest of the events that are missing now. And in this case, we want to have events where the player won, events where we have a payout, and events where we want to add a high score to our highscoremanager. And I'm going to define the events and then it's up to you in this current coding challenge for this lecture to emit the events on the right place, and in the next sections, we're going to work with these events from JavaScript. So, this is going to be the foundation that we're doing right now for the later sections where we are working outside of Solidity. We have a couple of events which we want to integrate and one is an event GameOverWithWin(address _winner); and we have an event GameOverWithDraw(); and an event PayoutSuccess(address _receiver, uint _amountInWei); and in the highscoremanager, we have one event where we just basically say NewWinAdded(address _for, uint _totalWins) after winning. If you want to do the challenge, then add the events to the appropriate places in the code. Emit them where they should be emitted and then in a second, I will just do it and comment a little bit. First, we have to have the setWinner and we're going to emit GameOverWithWin and then we have the player who won. But we also have to payout success, which is a little bit different than you might have expected because if our player.send is true, meaning if the sending of the money succeeded,  then we are going to emit PayoutSuccess to the player, and the amount in weight is the balanceToPayOut  and setDraw. Here, we have emit GameOverWithDraw(). This doesn't take any arguments because it's just game over and if our player.send is true, then we have a PayoutSuccess(player1, balanceToPayOut). And emit PayoutSuccess(player2, balanceToPayOut). And the withdraw with win, here we also have to emit PayoutSuccess(_to, balanceToWithdraw). There is this part in the highscoremanager, we are adding the NewWinAdded to the addWin function and here we emit NewWinAdded and there is for, and then we have the amount and numberOfWins is going to be over here. And we're done. In the next lecture, we're going to do a little summary, and then it gets really interesting with local development with Truffle and Web3. We are talking about how we can connect actually to our smart contracts from outside. Right now, we're just working in Remix and that is pretty boring. So, let's add some more tools to our stack and get things done from a JavaScript and HTML perspective. And I'll see you in the next lecture.


About the Author

Tom is a CTO, senior back-end developer, and systems architect with over twenty years of hands-on development experience in a variety of languages and systems. He has a CS master's degree and has been working with Ethereum and blockchain technologies since 2016.

Covered Topics