 Welcome to the workshop, which title is Testing Smart Contracts with Waffle. My name is Bartek Rutkowski. I work for TrueFi and I'm also one of the contributors to the Waffle open source library. And I'm going to take you through some of the key features of Waffle and we're going to code some stuff together. So this is what we're going to start with as the internet situation is not great. Some of that want to go through the tasks with us. I recommend cloning the repository right now and going so in the read me there is going to be a small section that's going to describe what you need to do basically is just installing the dependencies and doing some few basic steps. So yeah, I'm going to wait a second for everyone to do that. Okay, the URL is going to be visible also later on. So moving on, there are two basic approaches to testing smart contracts. One of which is testing smart contracts with other smart contracts, which has its very nice properties, but also the other approach is to test smart contracts with JavaScript or TypeScript code. And that's something that we're going to focus on. It has some nice things about it. So first of all, it's very easy and intuitive. So it is very fast, which is important if you want to test drive your code or if you want to make sure that the tests are being added to the code as it's being created all the time. It's also very, very flexible. I would say even more flexible than testing Solidity with other Solidity code. And it's also kind of de-app native in a sense that you interact with your smart contracts the same way your application is going to interact with your smart contracts later on. And then moving on to the WAFU itself, which is a library that utilizes JavaScript and TypeScript, it takes a very, very minimalistic approach. So it's not a very large framework that forces you to follow some certain rules, but it rather provides you with some set of utility functions that just help you with the largest pain points. Also it's blazing fast. As for the JavaScript or TypeScript library, because some of the Solidity testing can be a little faster, but as for a TypeScript framework, it is extremely fast. It also has a very nice and friendly syntax, and it obviously is open source. So anyone having any issues or any problems can reach out. And also if anyone has any ideas what we can improve, then they are welcome to do so. And also what is very important that WAFU works well with hard cut. So if you are already using your hard cut setup, then you can use some of the features of WAFU as a plug-in to your hard cut setup. So you can use either WAFU standalone as a complete testing solution or as a plug-in to hard cut, which is very nice as well. We're going to focus on using WAFU standalone, but recommend checking the documentation for the hard cut integration as well. And what WAFU actually does, so it takes you through all the steps of smart contract development. So first, it helps you compile your code, nevertheless, this is Viper of Solidity or Solidity. For most people, it's probably going to be Solidity. If you are team Vitalik and you prefer Viper, then yeah, you can use Viper as well. Then you can deploy your code, whether you want to actually prepare a deployment script for the main net or some other actually working network or just deploy for the testing purposes. And then provides with all the testing utilities like matchers, fixtures, and smart contract mocks. I'm going to dive deeper into that later on. And what are the WAFU components? It's TypeScript, obviously, with Typed Chain. It's Mock and Chai, which are JavaScript libraries for just JavaScript testing, but we utilize them here as well. And Ether's JS, which is a very nice library that wraps all the complexities of interacting with the Ethereum network. So now, what the whole process looks like? So first, we need our Solidity code. In this case, this is some Solidity file called exchange.sol, which is some smart contract. And then we need our configuration for WAFU. This configuration that we see here is not actually a needed configuration because these are all the default values. So you don't need to create that configuration. You can just keep completely this step, but mentioning this if you want to have something custom. And then we run a command, which is just invoking WAFU. And WAFU will produce byte code, the ABI, which is going to be helpful when interacting with smart contracts. And a flattened code, which is helpful if you, for example, want to verify your code on EtherScan or a similar site. And then a smart contract deployment itself, which is extremely simple. So basically, you only need to import the artifact from the Solidity, from the smart contract compilation. And then you need to use the deploy contract function. Super easy. First argument is who is deploying. Second argument is what is being deployed. And the third one are the arguments for the constructor of the function. Important thing to notice here is that the token object that we are creating here, which is the instantiation of the token, is tied to the wallet that deployed the contract. So whenever we're going to be interacting with the token, we're going to be interacting from the wallet. So the wallet is like the default center of the transaction. And we can change that by just connecting the contract to some other wallet. So by invoking contract.connect, we can have some other person, some other wallet interacting with the contract. Oh, thank you. I think I'm going to find some charger or someone will help me with that. Thank you. Thank you very much. Yeah, that the computer is on low battery. Moving on, moving on. So now interacting with the network. So there are basically three pieces of the interaction between the user and the network as a whole. So first we have our Dapp or a testing suite or a script. And that Dapp or whatever else uses a JSON RPC interface to talk to, to interact with any Ethereum node, which is like one of the nodes from the Ethereum network. And we're going to break down all these like three pieces and how sort of to wrap all that. So it's, so it makes sense from the developer perspective. So first of all, we have the node, the node itself. Usually instead of talking to the node or talking to the network, we use some API like infura alchemy, or a default provider injected into the browser by the metamask. But also we can use an emulated network like ganache or, or, or every VM or whatever else that is just localization that simulates the behavior of the Ethereum network. So before we can run our own Ethereum node at B, the hero of the community, which I obviously recommend, but I understand this is not the case for each solution. And then when we have the JSON RPC level, we need to wrap the JSON RPC complexities with nicer functions. So we have two libraries, two main, two major libraries that, that help with that. One of them is Ether's JS. Second one is web free JS. So basically web free JS is older. Ether's is like the preferred one by, by me myself and a lot of, a lot of my colleagues. But just to mention about that, that there are two of them at least. Yeah. And what Ether's JS does is that basically it wraps the whole management of the keys. It helps you construct the transaction. So as we see here, we have the private key. Then from the private key, we can create a wallet object. And then we can add a provider of the network. So sort of connect the, the wallet to a link to the particular node, to the particular API of the, of the extreme network. And then instead of just constructing some very bulky JSON and creating a HTTP request and sending that to the node, we can just use like, like a simple call like wallet dot get balance and we'll get our balance. And then there's JS also helps us override some, some additional properties of the transaction. So with it, there's JS, we can not only construct like a basic transactions, but also override gas limit and nonce value, chain ID, whatever else. And then if we construct that transaction, this is like the very bottom line. We can just add all these overrides and perform like a very custom transaction. And now we dive into the actual features of the, of the waffle. Right now we are sort of discovering the whole environment that's around, around the core of the presentation and what waffle can do on the testing field. So first of all, we can have like a basic testing so we can just invoke some function or we can have some value and we can demand the quality. So first equality, but necessarily doesn't necessarily need to be equality. We can just, we can also have, we can also expect the value to be within a range of some values or to be close to some value, which is kind of dangerous because obviously in the smart contracts, we want everything to be a super sharp and super precise, but in some cases, for example, if we are doing some calculations based on time, this is not going to be super precise and you can use something like this. Then we can test all the events and that's sort of corresponds to what I was talking about when it comes to the de-up nativeness of JavaScript testing because events are not crucial to the logic of the smart contracts themselves, but very often they are super important from the de-up standpoint because we need to be notified about some certain action that happened and we can test that very easily with waffle by just expecting some transaction to emit an event with some arguments or like emit multiple events or whatnot. We can also test external calls. So in this situation, this is a very simple thing because we just call a function and then expect the function to be called, but we can have a very advanced use cases where we have like a contract, calling other contracts, and then we can check whether some particular contract within the whole call stack was called by some other contract with some particular arguments, which can be quite powerful and allows us to check some of the complex cases of many smart contracts interactions. Then we have the reverts, which are also super cool to test, super, in my opinion, syntax is super cool and which are obviously super important to test. So first we can just test whether something is reverted, but we can also be very precise about the reverting reason, so what the error is. And also we can, which is one of the newest features, we can check the arguments of the reverts, that's one of the newest features of the Solidity and we have the reflection of that in WAFU as well. Then we have wrapper for the token balances, as this is something that we test very often. So we can check whether some transactions modifies balance of some tokens on some wallets, so we can do this for just one wallet that we expect some transaction to modify the balance of a token, of a wallet of particular value, or we can perform that for a pair of wallets or a triple of wallets or whatever, or whatever else. We can also have mock contracts, so mock contracts are just like artificial things that pretend to be smart contracts, so we can set up a mock contract, deploy a mock contract and then very easily define the behavior of the mock. So basically just say that mock when called, when like a particular method on the mock is called, is going to behave in a certain way. So this is going to return something or is going to revert. We can also specify the arguments and we can just basically model any behavior on the mock. And then the example of the usage of the mocks would be like this. So we have some setup that would return contract and a mock here C20 and then the mock, we sort of like program the mock to return some value and then we can, for example, check whether our contract correctly reacts to the value returned by the mock. So in the first case that our contract after examining the mock will return false or in the second case it will return true because the mock just returns some value that we expect or do not expect. And the last major feature are the fixtures. So this is an extremely powerful thing because normally when we have like a set of tests, basically like a good rule of testing is that all the tests should be, shouldn't depend on each other. So each test should have like a separated setup. We should be able to change the order of the test and everything should still work. So basically what fixtures allow us is sort of reverting the state of the blockchain to some other state from the past. So basically we can perform some setup transactions, deploy some contracts maybe perform some initial transactions and then save that as a fixture and then revoke the fixture in each test. So each test starts with the same state and we do not need to redo all the transactions and perform all these transactions. Again, we just sort of point to that state in the past that we want to sort of start from this particular moment and that actually is one of the things that make Waffle so fast. Because, yeah, and now this is going to be like a first full setup of the tests. So actually this is going to be shown also later on a sort of a cheat sheet when we start coding, but here we can go through the whole thing. So first we need to import the stuff from Chai that we need. So we need the expected utility. That's the most important thing. We need a type from itters that defines contract and then from Ethereum Waffle we want a deploy contract function, we want a mock provider function, which is the emulated network that we're going to be using instead of the actual Ethereum network in the tests and a solidity, which is just the object that contains all the custom matchers. And then we import the contract itself. So we inject the solidity matchers into the Chai itself and then we can start writing our tests. So here we can see that we created this provider. So we create this emulated virtual network. Then we create some wallets. In this case, only one wallet, which is called Alice. We create some contract and then in before each setup, this time we are not using fixtures. We just deploy the contract and then we can have like a, in this case, free tests that just assert some certain properties of the contract. Yeah, and now it's time to it's time to dive in. So we're going to have two difficulty tracks. So first is going to be like a beginner track. So you can use already done a smart contract code that we have in the repository that you downloaded. Or you can go through the advanced difficulty track. So basically start with an empty smart contract and test drive your code. So this is an interesting technique that instead of just writing the code and then testing whether the code is correct, you first write the test, see that the test is failing. So you are make sure that the contract that you are testing does not have this particular property or this particular function. And then after you have the failing test, you implement the smart contract logic and then see the test passing and then do a necessary factoring if you need and then repeat. So go on to the next property of the contract that you wanna add. And what we are gonna be working on. So the first task is gonna be to create a very simple splitter smart contract. So a contract that is gonna have a split function and when some if is gonna be sent to that function while the function is being called is just gonna split the ether in half and send to different addresses. And it's gonna revert when the value of the if is gonna be zero. And if the value of the if is not divided by two, then we're just gonna refund the remainder to the original sender. And we want to write the test for that. And task number two, which is they're gonna be sort of actually quite a few of them and you can do them in any order. So first, at the proper event when the split happens, so just to signalize that the split was called with a particular result. We also want to signalize whenever the non zero remainder was returned because we have that function as well. And we also want the owner to be the only person that is allowed to use the contract. So the split is gonna have a restriction that there's only one owner who can split the ether using the contract. And then we can also use a dynamic array of addresses. So maybe these are not gonna be set for good in the smart contract but they are gonna be arguments to the function. And maybe we're gonna have like more addresses than two, maybe three or four or just a dynamic number of them. And of course we want to create tests for that. So that's like a cheat sheet that I'm gonna stop on. So first QR code that you have is a waffle documentation that should help you with everything. The bottom QR code is the repository that we are gonna be working on. On the left you see the list of the tasks that are to be done. And on the right you have a sort of a basic template that shows you what's the general idea of the test and how they more or less should look like. Yeah, so if you have any questions or if you need help with something then feel free to reach out. I'm not the only person here to help. I have Daniel Przemegeniuszina from my team here as well. So if you have any problems or any questions we're here to help you. Okay, should I add something? Yeah, so are there any questions? Do I need to repeat anything? Is everything clear? By the way, question, where are you guys able to download the repository? Because the internal situation is not great so if anyone was downloading was, yeah, okay, I see some thumbs up. So I guess there was a success. Okay, cool. Oh, that's a good point. So actually I'm gonna switch from the presentation. Oh, nice. So here's the repository. This is what it looks like. So the first thing is the contracts folder. Here there is the ether splitter, the empty one. So if you go for the solid implementation yourself then you should start your work here. If you wanna use the already created contract it's gonna be here, the ether splitter ready with the split function. It doesn't have all the features from the task too. You can add them later on. And then the test section. So first there's some reference in the template file and the tests themselves should go into the ether splitter the tests where everything is set up. And here you can also see that we use ether splitter. So we import the empty ether splitter. If you want to use the already created one you should just swap the name here from ether splitter to ether splitter ready which basically would be just uncommenting this line and then commenting this one and that would make the change for you. Okay, yeah, read me, that's, that makes sense. So actually I'm gonna. Okay, so first of all you need to have the node installed and which I would guess that most of us you should have that installed already. Then you will need yarn to install the repository. If you don't have it then here's command for you to run, so we install yarn and then cloning the repository, entering the repository directory, installing the repository, and then the two commands that you're gonna be using more frequently, yarn build compiles the code and the yarn test runs proper tests. Yeah, and also some documentation sling if you need something. Okay, so I think we're gonna give you some time now and actually I think in a few minutes maybe we're gonna do some live coding if someone is gonna be lost and show you some of the things that are here. But for now, giving you some time. So, okay, so I'm Jen. I don't know, there's a kind of panel stuff like that. Apparently. Yeah, since I'm just waiting for an install in the dependencies. Anyhow, well, I really interested in writing some tests and when it comes to web two, there are tons of the methodologies and the tips for writing tests, right? So kind of, for example, is a writing kind of given when then pattern or BDD or the behavior driven test or that kind of things. So that kind of things in web two. So when it comes to web three, it seems like the security is also important. So I think that the mental model for testing in the web three world is really important. So I think, so I really wonder that you guys are thought in the testing especially in the web three world. Yeah, absolutely. So I think that one super important technique and sort of like mental model around like not only testing but developing smart content as a whole is that test driven development because like when you're like writing so I'm actually making people stand up. So if you're writing a solid decode, it's super important that the contract actually does what it needs to do. So that you don't have some unnecessary lines that you don't have any things that were put somewhere and you thought that they were gonna be useful but then at the end of the day, they ended up being not useful that all like cost gas. And this is all like a bloat that needs to be reduced. So a very nice technique is to actually test drive your contract. So actually write a very explicit expectations about your code and define what behavior you want to achieve from the contract and then implement only the code that sort of like performs the logic that is required to fulfill the sort of to make the test pass. Yeah, and then you need to go like through the whole stack. So there is sort of no differentiation like whether you should use unit tests or integration tests or some other type of tests you probably need to use all of them. So it's the easiest to just test drive your code using unit tests. So these are gonna be the tests that you're gonna be writing most of the time. And then if you test drive all the functions, test drive all the small bits of the logic that you need to put in place, then you probably want to move on to the integration tests and see how larger transactions, how like more complex cases resolve, how performing multiple transactions in a row affects the state and sort of like how this whole thing behaves. And that's sort of also not the end of the story because then you need to have like the whole or you should have the whole like testing deployment should play around with the whole thing. You need to, you also probably should develop like the app or the script that is meant to interact with the smart contracts in parallel to the smart contracts themselves. So all of like these two like remain in sync. Yeah. I don't know if there are any other tips that I can think of. Maybe someone can add something. Okay, actually maybe someone has any more questions. And yeah, oh yeah, the microphone is coming. Do we have a speaker? Is there a Viper support? Yes, so basically the only thing that's so on for Waffle, the only thing that's different for Waffle and the Viper is that you just need a different compiler and then you basically end up with like the very same compilation products, which one is like the bytecode that's just gonna go straight to the chain and the API. And all the abstraction of like wrapping the calls with like itters.js is also like the same for Viper and SolidV. So yes, definitely there is Viper support. You just need to use a different compiler, which is also like, I think you need to specify in the Waffle config that you are compiling not SolidV but you're compiling Viper. Well, that should be it. I'm pretty sure this is somewhere in the documentation. I don't use Viper personally, so I'm not very familiar with that setup, but yes, Viper is definitely supporting. So can you also expand the discussion about testing in Solidity and testing outside Solidity because that's kind of a hot topic with other frameworks like Foundry that they use Solidity and they have limitations or things that need to be work rounded. What is your view on this? Sure, so actually I think that the main advantage of using Solidity testing is the fact, so there are two things, okay. One is the pace, so the testing in Solidity is definitely faster and just having everything inside of the EVM just makes the test run faster, so which might be important for some people and it is sometimes actually important if you want to run the tests very frequently which makes sense and you should do that, so this is a nice property, but then if you have some continuous integration system on your repository then it's probably not that important because even the JavaScript tests are not gonna be that long, so that's one thing which is just the performance which is better on the Solidity side and the other is the fact that when using TypeScript or JavaScript tests, there is this JSON RPC middleman and there is also like casting of all of the types, so for example when you call a function and then something is being returned, this is like some JSON and the big number that's being returned from the Solidity or Viper code is just a very large number that's not able, that JavaScript is not able to process so you need to cast it, you need to have special types and there were times when it was actually a problem that we didn't have good implementations of big numbers and some people were just casting to strings then comparing strings and this wasn't great, but now I think that it's not that much of a problem anymore of course, like you have all the native types and native type compatibility within the Solidity itself and while using JavaScript or TypeScript, this is sort of external and some casting so on and so forth, but I think this is not that big of a problem. I think at the end of the day, the bigger difference is like sort of collapse to what you just prefer from the sort of developer experience standpoint because I truly believe that both of these approaches can be just very precise and it can really clean your code from any bugs or any errors and are just both very good, yeah? How long the same line is that I'll talk about is have you yourself or seen instances where you're actually testing the user interface so you're basically doing the same UI testing to do it with Web2 and Solanium and then actually kind of dropping down to Solidity by executing it, for example, through a browser? Yeah, actually, yes, I think I haven't been reading this kind of tests myself, but we definitely still do test our frontends with a typical like Web2 tools that even just like display the whole thing and then like click into like the particular, like virtually like click into like the particular sort of components. Yes, and actually perform transactions, though in this case we would need something that would just work very fast. So we, as a Ethereum network, we wouldn't use like a test net or a main net, of course, but just an emulated network, yeah? I might try to expand. So what we do in TrueFi, we run the whole blockchain with ganache or hardhat. We also run a local digraph and then we run the frontend and click through the frontend sending the transaction through MetaMask on this local network. So this is what we do, but it's separate from waffle, right? Okay. Hey guys, did you manage to download any Starian build, et cetera? Yes, you did? And how is it going? We failed, yeah, so my proposition is we are going to plug in different computer and live code with you. And if you didn't manage to download it, you can come here and we can do it together. So it's more interesting and some people don't have computers so you can do it with us here. How does it sound? Great, yeah, so let's start. Okay, so I'm swapping the HDMI to some other computer and we're gonna live code. Yeah, and let's start with this task one and Przemek will... Go on the stand. Yes. All right guys, so do we have volunteer to start the first task? We'll go the harder track. Yeah, so come here. And big round of applause for our volunteer. Come here. Yeah, so task one is we have an empty contract, empty test, and we're starting with writing test for one functionality of the contract. Then we write the function in the smart contract and then the test should pass. So, some technical problems, give us one second. So we're basically going to mob now. Yes, per programming for now, but here mob. Yes, so we want to write at our splitter. So just a simple exercise. We take an ether value to the function of a smart contract and divide it into two addresses and make sure to return the remainder if it's an odd value. So just something simple. Hey guys, can you see the text? Great. I'm trying to find another state of... I need another each state. And it won't test this leader function, right? Yeah, so we are going to start from the first test. Which contract is it? Ether splitter or ready one? We just want to see how the function looks like. Right now it's Ether splitter and empty. I guess we just have to call the... To what's this called? Ether splitter. We have an instance of splitter below. So take a look at line 30, 31. Yeah, there's splitter already deployed. There are addresses to be used. Yeah, we have an instance of splitter and we have some accounts like Alice, Bob, Charlie and David. We have to catch the value, right? Does it return something? No, it doesn't. So you want to test the balances of the accounts before and after. Something from there. Why? Yeah, because right now you use the Ether splitter, which is not implemented. Take a look at the contract right here. This one. Yeah, right now we use this, which is not implemented yet. Do you want to implement this or do you want to use the implemented one? Let's implement it. Okay. Yeah, so that's normal. That's when we do like TDD, like this is driven development. And we want to call some function and see the results of the function. Then we just have like error and we need to first create a signature of the function. Let's leave the message to the party. Let's, maybe let's start with just the function. Let's make the test pass maybe. Let's build. Let's keep the return, just make it public. Looks nice. We can save it and then build, right? And then run our tests, see that it works. Here, yarn build then. Yeah, so we need to run yarn build in terminal. So can you, this? And probably, yeah, here we use yarn, so yarn build. So now we are compiling our Spark contracts with Waffle and could generate types with type chain. And now we can run yarn test to see that our tests are passing. Let's see. Yeah, for now, great. It works. So now let's, maybe here we have like this split and now what we want to see? We want to see that as a result of split, some balances will change. So what should we put here? We have, what addresses we have? We can use Charlie and David as those two. Yeah, so we have Charlie and David. These are like random addresses. And as I understand, we want them to get, let's say, let's send one ether to split and splitter and then expect these two addresses to just get. Yeah, can I? Maybe let's expect what is before. So let's say that, so I test the Charlie and maybe David has zero before. So maybe let's start with just one address, just Charlie. Okay, so we can do like proper TDD, which is just start with simple example and then go to more advanced ones. So right now what we want to do is to first check that the initial balance is equal zero, then call this split, splitter and split and then let's expect, for example, this balance to be equal one. Or let's say 10 into five and five, right? Okay. We've just one, right? Yeah, I'm gonna instance a big number for that expresses one token. So one token, let's use this, okay. You will need to import big number. Oh, okay, it's, yeah, there it is. Actually, you don't need big number. You can do it differently. You can use properties of itters. You can call, no, no, no, not like this. You can leave it, can I show you? Okay, yeah, show like this. So we can, not like this format. I always format one. Let's import itters. And now one token should be one eater, right? But you probably want to move it higher so you can use it in this split function. Maybe. It's good. And second, because we want to pass this as a value. So these are those overwrites. All right. Yeah, and this will be this value. That function doesn't receive any. Yeah. So this will be this message value and, okay. And what do you expect next one? This is an eater's instance. Maybe we can just divide. Right, let's start with one, just one under it. Okay. Do it one. And it should pass. Okay, our terminal is broken. Our visual is to be called is broken. Yes. Okay, it doesn't expect that. What if I do try to do this? No, it's not format eater, it's parse eater. Parse eater. I always come up with this tool. Parset company. Yeah, second value. Or should I come to you? Second value. Why is it complaining? Oh, yeah, yeah, it's because it's not payable in the actual code, security code. Yeah. Payable. Good catch. Yeah. Oh, yeah. All right. Okay, you need to build. We're using actual TDP. Wait, so you need to build because we changed the smart contract. So we need to build it and then test it. No, sorry. I'm used to use it alongside hard hat. It's just one comment. Yeah, good. We're on a red test, so now we can implement it. Nice. Do you want to break or do you want to continue? Let's continue. Okay. Yeah. Let's go on. Oh, we should. Yeah, so we need those two addresses. So you can either take them in the split function or take them in the constructor. The two addresses that will be splitting. Oh, we're the hard code. Yeah, it's hard code. Let's keep it simple because we only have two addresses. Right. It was Charlie and Bob. Yeah. Bob and Charlie. Or Reciber one or Reciber two. We're gonna take the two addresses that we split in the constructor. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah. Yeah, so we have a constructor with two addresses and we'll be splitting the funds into those two. Maybe we can start implementing the split right away. We have the message value. So we'll do address one that's transfer or send. The message value is the amount that was transferred through the payable function. Gently test. So this would be half of it, right? Okay. What's going on? Ah, yeah. Let's run. Our code is hanging again. Not yet, but it's hanging. So we don't have a lot of time, but if anyone would like to continue, we'll hang around somewhere here. We can help. Okay guys. We need to finish. So we didn't manage to do a lot within a short period of time and not very perfect internet situation, but if any one of you wants to continue, then we are here like outside of the room and we have our booth. So you can drop by, we can talk and we can help you to continue with everything. So yeah, thanks a lot and see you around at DevCon.