Welcome to the course for arbitrum stylus. Stylus is a tool that will allow you to write smart contracts using the Rust programming language and then deploy on arbitrum. In this course, you'll learn how to develop smart contracts on arbitum using stylus and writing your smart contract in the Rust programming language. Why should you take this course? Maybe you're tired of writing your smart contracts in Solidity and want to try out something new. Then stylus might be something that you would want to try out. Another reason to try out Stylus is to practice your Rust programming skills. Perhaps you know the basics of the Rust programming language, but you don't have any good ideas for what to build with the Rust programming language. To take this course, you'll need to be familiar with Solidity and Rust. In this course, I'll be showing you the code using a code editor called BS Code. Now, you don't have to use VS Code to write STS contract. However, if you are, then I recommend that you install a plugin called Rust Analyzer. The Rust Analyzer plug-in will give you live feedbacks as you write your code so that it will make your deb experience fun and easy. In this course, you'll be developing a lot of smart contracts. And of course, you're going to write some code that do not compile. When your code doesn't compile, you can fix the error and then try to compile your code again. If the code compiles, great. Then you can move on. Otherwise, you'll encounter another error. So, you'll fix the error and then try compiling again. To automate this workflow, I recommend that you install a tool called Cargo Watch. Cargo Watch will monitor any changes that you make to files and you can specify what command to execute when the files change. For example, you can specify to build the contracts or to run the tests. Another tool that I'll be using in this course is called the Remix IDE. It's a browser-based Solidity code editor. When I show you a demo of stylus contracts that are deployed on the test net, I'll be interacting with the stylus contract using the Remix IDE. Next, I'm going to explain where the exercises are located and how to do them. The course is under a GitHub repo called Arbitum Stylus. Once you download the project, there's a folder called topics. Inside here are list of topics that I explained in this course. Now, if there's exercise for that particular topic, then inside that folder, for example, constructor, you'll see two folders called exercises and solutions. Exercises will contain the starter code for you to write your code. To do the exercises, you'll open the readme file. Inside here, there'll be instructions on what to do for the exercise. Now, if you're getting stuck or maybe you just want to see the solutions, then you can look inside the folder called solutions. Here, you'll find the solutions for the exercises. And finally, if you simply want to look at the examples, you can also look inside the code under the folder examples. I'm going to first begin this course by explaining what Stylus is. Stylus is a tool to write smart contracts on arbitum using languages that compile to web assembly. There are many But in this course, we'll be using the Rust programming language. So what is the benefit of writing your smart contracts using stylus? One benefit is faster execution and lower gas fees compared to EBM. The reason for this is EBM is a virtual machine. So there is the overhead of running your smart contracts on this virtual machine. Now let's compare this with smart contracts developed using stylus. Stylus compiles your contract into web assembly. Web assemblies are codes that operate at a lower level than the Ethereum virtual machine. Therefore, there's less overhead for executing code on web assembly which results in faster execution and lower gas fees. Another benefit of using Stylus is that you can leverage existing libraries and tooling from the chosen programming language. For example, using the Rust programming language, we have all of the great tools that we use in the Rust programming language to write our smart contract. We can also import some of the Rust libraries and use it inside our smart contracts. Next, I'm going to explain about the features of the Stylus Rust SDK. The Stylist Rust SDK is the tool that we're going to be using to write our smart contract in Rust and then compile into web assembly. The stylus rust SDK offers some Rust macros that generate Rust code from your solidity code. For example, these macros can convert Solidity events, errors, strrus, state variables, and interfaces into Rust code. Another feature of the Stylus Rust SDK is storage caching. In most cases, when you access state variables using the stylus rust SDK, it will automatically cache your state variable so that it won't have to touch the state variable multiple times if it is not needed. And why is it important to cache state variables? The reason is gas cost. It's expensive to read and write two state variables. For example, here is a simple solidity code. It has one state variable called val. Inside a function called write, it reads the state variable val three times and then updates it once. In total, it touches the state variable five times. Three over here and then two over here. One to
Segment 2 (05:00 - 10:00)
get the current value and then one to update the value. Five in total. Now, let's compare this with a stylus contract. For now, don't worry about the syntax. I'll simply explain what's going on here. Again, we have a state variable called bell. inside the function write it reads this state variable three times and then increments it by one. The behavior is the same as the solidity contract. However, since stylus SDK caches state variables, the number of times that it touches the state variable is different. Remember in solidity it touched the state variable five times. But how about here? Well, the first one will the state variable. So that's one. And then the other two times that it reads into the state variable, it returns the cache. it doesn't touch the state variable. So in total over here it touches the state variable once. Compare this with solidity. In solidity code, it touched the state variable every time we referenced it. But over here it caches it. So it only touches it once. Next, we increment this state variable one. Let's count the number of times that it touches the state variable. First, it needs to get the current value. B. get. However, since this is going to get the cached value, this will not touch the state variable. And then to update it, it will need to touch the state variable. So in total, it's only touching the state variable once. So that is an example of how stylus contract caches state variables. Another unique feature of styus contracts is that public functions in a styus contracts automatically have re-entrance guards. In Solidity, you'll have to manually put re-entry guards on the public functions. However, in Stylus, all of your public functions will be protected by re-entry guards that are automatically put by the Stylus Rust SDK. So, these are some features of the Stylus Rust SDK. And finally, I'll highlight some of the difference between Stylus Rust SDK and Solidity. One difference is that there is no solidity immutable variables. This comes from the fact that immutable variables is a feature of the Solidity programming language. In Rust, there's also a concept of immutable variables. However, the immutable variable in Rust and Solidity are totally different things and they behave differently. In solidity, immutable variable means that variables that can be set inside your constructor and afterwards they cannot change. When the contract is deployed, those immutable variables will be replaced by hardcoded values. However, this behavior is not available in Rust and it's difficult to replicate it using macros. Another difference between Solidity and Stylus Rust SDK is that there's no inheritance similar to Solidity. Again, this comes from the fact that there's no inheritance in Rust. However, using the Rust Stylus SDK, you'll be able to inherit other Styus contracts. I'll mention one more difference between Solidity and Stylus Rust SDK. Currently, there's no easy way to deploy Styus contracts from within a Styus contract. In solidity, this is trivial. You simply call the function new. So these are some differences between the stylus rust SDK and solidity. In this video, I'll show you the steps that are needed to set up your first stylus contract. I'll be following the instructions from this readme file, which you can find at arbitr stylus topics. Hello. And the name of the file is called readme. md. Okay, so let's get started. First step is to install some tools that are used by stylus. These are Rust, Docker, and Foundrycast. Throughout this course, I'll be using Rust version 1. 87. To install Rust, navigate to the Rust official web page, and I'm going to copy this command and then execute it inside my terminal. Here I have my code editor open. and my terminal open. I'm going to paste the command and then execute it. This will install all of the tools that you need to work with Rust. We can check that Rust is installed by typing rust c- version. And you can see here that I've installed Rust 1. 87. The next step is to install Docker. Installing Docker will be different for Windows and for Linux. So I'll just put the link over here. You can click on it and then install Docker. Okay. And then moving on, let's install Foundrycast. Clicking on the Foundry link. This will take you to the GitHub page for the Foundry. The easiest way to install Foundry is to click on their website. For now, this is get foundry. sh. Clicking on the link. This will take you to the documentation for foundry. I'm running Linux and these two commands are the commands that I'll need to execute inside my terminal. The second command is easy to remember, foundry up. So I'll just copy the first command. Back inside my terminal, I'll execute the command. Next, we'll install Foundry by typing foundry up. Okay, now Foundry is installed. Let's move on to the next step. So now that we install Rust, Docker, and Foundry, the next step is to install Nitro Dev Node. This is a simplified version of the arbitrum node used for development and for testing. To install this, all we have to do is get clone this repo. Now when we write our
Segment 3 (10:00 - 15:00)
status contract, we'll also have to run this node inside our computer. And we do this by cding into nitro dev node and then running this script. So for now, I'll just get clone this repo. Back inside my terminal, I'll execute the command. The command above will get clone this repo. So now if I look into this folder, you can see that there's a folder called nitro dev node. Okay, moving on. The next thing that we need to install is cargo stylus. This is a command line tool that we're going to need to initialize stylus projects, deploy the contract, and export the AI of the stylus contract. To install this CLI, all you have to do is copy and paste this command, paste it inside our terminal, execute the code. Okay, that installed cargo stylus. Let's move on. Now going back to the readme file, you might encounter a problem that you need to install OpenSSL into your system. Here I've listed the command that you'll need to install this OpenSSL package for Ubuntu and Fedora. For other system, you'll have to Google this. So now that we've installed cargo stylus, let's move on to the next step. The next step is to set up the build target for Rust. Rust can compile code for different targets. We need to target web assembly. So to do that, we're going to execute these two commands. The first command sets the Rust version to 1. 87. The second command adds a build target for web assembly. Okay, back inside my terminal, I'm going to execute both of the commands. The smart contracts that are written with stylus rust SDK are written in Rust. These contracts will be compiled into web assembly. So that is why we're adding the web assembly to the build target of Rust. Okay. And we're now ready to initialize our first stylus contract. All you have to do is execute this command replacing your project name. I'll copy this and then back inside the terminal. I'll paste the command. Let's replace this project name with let's say hello stylus. Let's look at some of the files that I created inside my folder temp. I see that it created a folder called hello stylus. And inside here it created a bunch of files. The important files that you'll need to remember are cargo. tml. This defines the Rust packages also called crates that are needed to build this project. And then we also have a file called rust tool chain. tl. This defines the rust version that was used to compile the stylus contract. Okay. And then there are two other folders that are important and that I want to mention here. The first one is source. This will contain the contract and examples. Inside examples, you can put any Rust script. Let's take a look at the example Rust code. Scrolling down, you can see that it imports the private key and the RPC URL along with the Stylus contract address. So, this means that before we can execute this Rust code, we'll first need to deploy our Stylus contract. Okay. And then scrolling down, it sets up a wallet and then it gets the number from the counter contract and then sends a transaction to increment the count and then gets the count again. Okay, so this script is a simple example of reading and writing to a contract. In this course, I'll be modifying this script to write a integration test and then execute it. Next, let's take a look at the contract itself. There are two files, main rs and lib. RS. Let's first take a look at main. r RS. What's interesting to note here is that after you compile a contract, we can export the AI of the contract. The code that is responsible for exporting the AI is this code over here. That's all you need to know for the main RS. Okay. And then contract is inside lib. RS. So here is the counter contract. There are some functions to get the number, set the number, and then increment the number. We'll look into the details of how to create a contract later in the course. For now, let's focus on how to use the cargo styus CLI, how to execute basic commands like compile a contract, test the contract, and deploy the contract. Okay, going back to our readme file. So, this is a step that we completed. We initialize our stylus contract project. The next command that I will explain is called cargo stylus check. This will compile a contract and also make sure that your contract can be deployed. Now before we run this command, we first need to run the nitro dev node. So let's first run this dev node. Back inside the code editor, I'm going to open my terminal and then cd into the folder where I get clone the nitro deb node. Now before you run the nitro dev node, you also need to make sure that docker is running. The simplest way to check that docker is running is to type docker ps- a. This will list out all of the containers that are currently running here. These are some containers that I am currently running. It's not important for this course. Okay. So now that we know that Docker is running, let's run the Nitro Dev node. We run the Nitro Dev node by
Segment 4 (15:00 - 20:00)
executing a script inside this folder called run deb node. Okay, now that nitro dev node is running, let's execute the command that will check that our status contract can be deployed. So I'm going to open another terminal and then navigate to the folder of stylus. We've created a project folder called hello stylus. Inside here I'll type cargo stylus check. Execute the command. This will build a contract and also check that the contract can be deployed onto arbitrum. Okay. And you can see that the code executed without any errors. The check pass. Moving on. Let's look at some other commands that we can execute with the cargo stylus CLI. So we run the command cargo stylus check. We can also test. Let's run this command cargo test and this will execute the unit test inside divs. Here you can see that there are some unit test for this contract. Okay. And the test passed. The next command I'll explain is called cargo stylus export AI. This will create a solidity API from your stylus contract and then print it out to your terminal. Remember earlier that I mentioned that inside a file called main rs, the main function will print out the AI of this contract. Our contract is the counter contract. So it's going to export AI with the functions number, set, num, mon, num, add number, increment and so on. So to create a solidity AI from our contract, we type cargo stylus export dashabi. Okay. And here's a solidity AI that was exported. Notice that the function name has been modified to be camelc case. Here we have a function called set number in camel mall number in camel case. But inside our stylus contract we name the functions with a underscore set number and mall number. So that's one thing that you need to remember when you export the AI from a stylus contract. It transforms your functions from snake case into camel cases. Okay, for the last part of this video, let's deploy the contract onto our local node. The local node being the nitro dev node. To deploy the contract, we'll execute this command. I'll copy this and explain what each of these arguments mean inside the terminal. The first argument endpoint means that we're going to be using this endpoint as the RPC URL. This RPC URL points to the Nitro Dev node. So this means that we're going to be deploying our contract to the Nitro Dev node. And then the next argument private key is the private key that we're going to be using to deploy this contract. Notice that here I'm revealing my private key to you. But this is not a problem. This is a default private key that is used by Nitro Demno to fund this wallet. Okay. And the next argument estimate guess. This will simulate a deployment of this contract. If we remove this, we're actually going to be deploying the contract. If you remove this no verify argument before it deploys the contract, it would try to check your contract using Docker containers. For some weird reason, I couldn't get this to work. So that is why I'm putting no verify. I'm going to skip the verification with the docker container to show you the error that I'm getting. Let's first remove this and then execute the code. Okay, so this time it worked. But if you're having problem with the docker containing check your contract, then you can skip this check by docker by putting in no verify. Now the command above did not deploy the contract. It only estimated the gas that is needed to deploy the contract. To actually deploy the contract onto our nitro dev node, we'll need to remove this estimate gas argument. Execute the code again. And we successfully deployed our contract onto the nitro dev node. Here's the contract address. And here are some transactions that were involved in deploying the contract. Now you'll notice that there are two transactions deployment tx and contract activated and ready onchain with tx over here. So why is there two transaction involved for deploying a contract? This I will explain in the next video. In the previous video, we deployed a status contract onto our local dev node. There are two transactions that are involved for deploying this contract. The transaction that deploys the contract and the transaction that activates the contract. So why is there two transactions? When we deploy a solidity or a Viper contract onto a EVM, there's only a single transaction that we need to send to deploy the contract. However, for stylus contracts, there are two transactions. So, what's the purpose of these two transactions? The answer is the first transaction is to upload the Wom code. When we write our contract in Rust and then compile into WASM web assembly code, the first transaction will deploy this WOM code. The next transaction will check that this was wom is safe to
Segment 5 (20:00 - 25:00)
execute and load it into arbitrum nodes. At this point the contract is live and we can finally interact with the contract. So these are the two transactions that are involved for deploying a status contract. Going back to our transaction log when we deploy the contract onto our local dev node. Here I will mention two more things. The first is gas and the other concept unique to stylus is caching the contract. In this video, I'll talk about the difference in gas between Stylus contract and other contracts such as Solidity and Viper. And in the next video, I'll explain about caching a contract. So with Stylus contracts, their code compiles down to web assembly. These web assembly op codes are cheaper than EBM op codes. This is because web assembly operates at a lower level, closer to the machine code than EBM op codes. In other words, it's more efficient and faster to execute WASM up codes than EBM up codes. This idea also translates to the gas. Since W was up codes are cheaper to execute than EBM up codes, we need to introduce a new concept, a fraction of a gas. Sty this fractional gas is called ink. Currently, one gas is set to 10,000 ink. This number can be changed by the arbitrum node, but for now, this is the default. One gas is equal to 10,000 ink. When you're writing stylus contracts, you won't have to think about this ink, but it's nice to know when you want to go deeper into stylus contracts. Okay. And the final concept that I'll explain here is caching a contract. At the end, if you look at the logs for deploying a contract, it says cache contracts benefit from cheaper calls. So, what's this thing about caching a contract? It turns out that there's a overhead for loading and calling stylus wasn't contracts. So when you call into a stylus contract, there's some extra work that the node has to do to be able to call the wasen contracts. To remove this overhead, arbitrum introduces a concept of caching the contract. The way you cache a contract is you bid on an onchain contract. You're bidding to cach your contract into memory of arbitum nodes. In other words, this cache manager determines which stylus contracts are to be loaded into arbitum's memory so that it doesn't have to go through this extra step of loading and then calling into stylus wom contracts. For more information about caching contracts, check out the official documentation for Stylus. You can also check out the cache manager contract. I'll put the links for both of these pages inside the readme file of topics/hello. In this video, I'll walk you through an example of deploying a simple stylus contract onto arbitrum. The first step is to add a configuration for the network into your wallet. These are the configurations that you'll have to put inside your wallet. In this course, I'll be using the MetaMask wallet to interact with contracts on arbitum. The next step is to get arbitrum sepoleia e. Now, there are two ways to get this E on arbitrum. One way is to use a faucet and another way is to bridge your sepoleia e from over to arbitumia. Let me show you both ways to get this e. Let's start with faucet. Currently, there are several options to get arbitrum from a faucet. For example, let's look at alchemy. I'll click on this link. Here's the arbitumia faucet page for alchemy. I'll paste my wallet address inside here and then click on send me e. Okay, I just got some arbitrum inside my wallet. So this is one way of getting arbitrum sepoleia inside your wallet which you're going to need to be able to deploy the status contracts. The other way to fund your wallet on arbitrum with ETH is to bridge your ETH from Sapoleia. Here I'm going to use the official bridge of Arbitum. Inside my wallet I have 0. 1299 E. I'm going to send some of it over to Arbitum 1. Let's say I'm going to put 0. 01. Then I'm going to scroll down and select arbitrum bridge and then click on move funds to arbitrum one. After you submit your transaction on Solia, you can check the status by clicking on transaction history. If the transfer of EH to Arbitum Sapoleia is still pending, you will see it under the pending transactions. Okay, now that we have Arbitum Sapoleia E, let's deploy a stylus contract onto Arbitum Sapoleia. The command that we're going to execute is basically the same command that we executed when we deploy the contract onto the natural dev node. The only difference is that we'll need to change the endpoint and the private key. Here I'm going to paste my private key from my wallet and for the endpoint I'm going to be using this endpoint. So first I'm going to set up the environment variables, the private key and the RPC URL. I'm going to copy the RPC URL and then inside my terminal paste it. The next step is to get my private key, which I've already done inside my terminal, but I won't be showing you my private key. And the last step is to execute this command. Paste the command inside the terminal and then execute it. Actually, that was a transaction that simulated the deployment since we have the estimate gas argument. To actually deploy the contract, I'm going to remove this and then execute the command again.
Segment 6 (25:00 - 30:00)
This time, it's going to deploy the contract. Okay, here it is. Deploy contract at this address. The next step is to interact with this contract. We'll need to remember the address of the contract and we're going to use remix IDE to interact with this contract. But before we do that, let's export the AI. cargo styus export AI. Okay. And this is the interface that we're going to be copying and pasting inside Remix. I'm going to first copy this and we're going to be using the remix ID. The links for this remix ID will be somewhere inside the readme file. First, I'm going to create a new solidity file. Let's call this styus and then paste the interface that we copied. Let's try to compile a contract. Make sure that the contract compiles. Okay, the contract compiles. Next, let's try to interact with this contract. Click on the deployment tab and then click on injected provider. This will connect your MetaMask to the Remix IDE. This is the account that I'm going to be using to interact with the contract on arbitrum. And then we need to paste in the address of our contract. Going back to our terminal, I'm going to scroll back up and then copy this contract address. Back inside remix, I'll paste the address and then click on add address. Okay, so here's our contract. Here's our first stylus contract deployed on arbitrum. Before sending a transaction, let's first call the function number. Let's try to see what data is stored inside this contract. Clicking on number, it returns a zero. So we know that something is working. How about we send a transaction? Let's call the function increment. Hey, the transaction went through. So let's try calling the function number again. Since we call the function increment, we expect that this number to increment by one. And now it is equal to one. So that was an example of deploying a stylus contract onto arbitum. Here's the quick rundown of the steps that are involved. Make sure that you have enough e. You can get this by either faucet or bridging to arbitrum sepoleia from sepoleia. Before deploying, you should also call the function cargo styles check to make sure that your contract is valid to be deployed. And the final step is to deploy the contract by executing this code. Before you do that, you'll need to prepare the parameters for the RPC URL and the private key. Once you deploy the contract, you can export the AI by calling the command cargo stylus export AI. This will print out the Solidity interface that you can interact with. In this video, we copied this interface, paste it into Remix, and then interact it with this contract. We've initialized our first stylus contract. When we did that, it created a bunch of files. In this video, I'm going to explain the purpose of some of these files. Let's start with cargo. tl. Inside my code editor, I've initialized a stylus project called hello stylus. Under here, let's take a look at cargo. tl. Inside this file, it defines the dependencies. It defines the Rust crates that are needed to build this project. Now, there are other bunch of informations, but here I want to focus on the name. Currently, the name of this project is called Stylus Hello World. And let's say that we wanted to change the name of this project. For this example, I'll keep it simple and say I want to rename my project to stylus hello. So, this is the first thing that you'll do. Change the name under cargo. l promo to whatever name that you want to change to. But we're not done yet. You'll also need to change the main rs. Notice here that this name refers to the name that we had previously, stylus hello world. So we also need to change this name. Since inside cargo. tl I renamed it as stylus hello, the name that I need to reference here will be stylus_hello. If you didn't, so let's revert this change back to stylus hello world. The code will compile. However, if you try to execute one of the styus commands, for example, cargo stylus export abi, then you'll notice that the code does not compile. And the reason is it says that I need to change the name from the previous name to the current name stylus hello. So, let's do that. Change this to stylus hello. Execute the code again. And now the code executes correctly. So, those are two things that you need to change if you change cargo. Next, let's take a look at main rs. When you write a rust program, the file called main. rs is the file that is executed and the function that is called must be named main. However, inside this file, you'll notice that there are a bunch of macros. Here we have cfg and so on. Here I'm going to briefly explain what some of these macros mean. And what is this code actually doing? The first two macros are related to each other. Let's start with this part. If the feature flag is not set to test and if it export AI then it is saying that this function main rs will not have a main function. And the second line is saying the same thing and it's saying that the
Segment 7 (30:00 - 35:00)
main function use this as a main function. This is needed to call web assembly code. So it's replacing the rust main function with something that web assembly can call. Otherwise, if the feature flag is set to export AI, remember when we call cargle stylus export ABI, this is the code that is actually being executed. So, if there's a feature flag for export ABI, then this is the main function to be executed which prints out the interface and the AI. So, that is a quick explanation of the main. rs. Let's move on. Let's look at lib. rs. This is a file that will contain the logic for your contract. Now, inside here, there are also a bunch of macros. To simplify the code, I'm going to remove the comments and also remove some of the functions. Two functions, number and set number, are enough to explain what's going inside. Okay, let's start from the top. Again, we start with some macros that are configured when the code is compiled. The first line says that if it is not a test and if the feature flag export AI is not set, then this code will not contain any main function. The next line says that it's not going to import the Rust standard library. The reason is the standard library contains a lot of utility functions that are not needed for smart contracts. For example, functions that operate with the file system and functions that make network requests. Smart contracts are not allowed to do any of those. So, we're not going to need it to keep the contract size small. It's saying that it's not going to import the standard library. However, there are some stuff that the contract needs. One of them is how data is managed on the heap. For example, vectors and strings must be allocated on the heap. So the next line explicitly says we're not going to import the std, but however, we need the default allocator. Okay. The next line imports the stylus rust SDK. Importing prelude will import most of the stuff that you'll need to write your contract. Now data is encoded differently between Rust and the EBM. To encode and decode these data, we need to import a library called alloy primitives. Here it is importing the U256. This is UN256. So by importing this module, we'll be able to encode and decode a UN 256. There are other types from this alloy primitives such as address bytes 32 bytes and so on. The next part of the code defines the state variables of this contract. This is a simple counter contract and it defines that this contract is going to store a single number y in 256 called number. Now this syntax is not rust syntax. It is solidity syntax. Here we're using the storage macro to convert solidity syntax into rust code. Also there's another macro called entry point. Inside this file we can define multiple strus but we need to have a entry point. By defining a entry point this is our contract. Okay. So where are the functions for this contract called counter? Well, it will be inside this code block. Inside here, we have a import counter. This is just rust syntax to define functions on this counter struck. What makes this a public function for a smart contract is by putting the macro public. So after we deploy this contract, we'll be able to call the function number and set number. In solidity, when we have public functions, we need to declare what kind of function it is. Does it write to the blockchain or not? If it does not write to the blockchain, then the function must be declared as or pure. But you'll notice here that those keywords are missing from these two functions. The keywords that indicate whether the function is read or write is actually implied by the reference that is being passed. Here you see a reference to self. However, over a mutable self. This is what tells whether the function is read or write. A immutable reference will be read only. a mutable reference will be right. To see this, note that the number function passes a immutable reference and the function set number passes a mutable reference. Now let's run the command cargo stylus export API and see what interface we get. Okay, here is the interface has a function called number which is external view and then we also have a function called set number which is a function that will write something onto the blockchain. The stylist Rust SDK is able to figure out that this is a view function. And this is a function that might potentially write onto the blockchain and it can figure this out by looking at what kind of reference is being passed. Is it a immutable reference or a mutable reference? Okay, so this is the contract. We looked at how the state variables are declared and we also looked at how to declare public functions. Next, let's take a look at the unit test. The unit test can either be inside the same file or it can be in a separate file. For simplicity, it's kept inside the same file. The test framework that is being used for unit testing is called stylus SDK testing. Unlike the test that you will write in Foundry or hard hat, currently there's a limitation for what you can test using this testing framework. The current
Segment 8 (35:00 - 40:00)
limitation that I found out while writing stylus contracts is that you won't be able to call other contracts. The code will compile, but when you try to run the test, you'll get some weird errors. So that is why in this course for integration test we're going to modify another file inside examples counter rs. Inside here we'll write scripts that will call into multiple contracts and then we'll run it on nitro dev node. To execute this script you'll need to fill in the environment variable for private key path RPC URL and stylus contract address. All of these environment variables are defined over here inside. example. So to execute this examples/counter. rs we'll first copy this and then we'll rename it tomb. Once you do that you'll get a file and this is where you will put the values for the RPC URL stylus contract address and the private key path. Once you do that you can execute this rust code by typing cargo run dash example and the name of the file counter. Now before you execute this, you'll need to somehow load the environment variable. There are two ways to load this environment variable. If you're working with Linux, you can type the command embed the code. Or another way is before you execute this command, you'll have to export all of these environment variables. For example, you'll say export RPC URL followed by the actual value. Export all of these variables and afterwards you can execute this command. Okay, in summary, in this video, I explained some of the files inside a stylus contract. The files that will be useful to you from the beginning are cargo. tl, the code itself inside main. rs and lib. RS, counter rs if you want to execute some Rust script, and the environment files. In this video, I'll explain how to define a constructor in a stylus contract and some of the things that you'll have to watch out when you're writing constructor in it. Here I have an example contract that has two state variables owner and number. Let's define a constructor that will set the owner to the account that sent the transaction to deploy this contract and also set the number to the parameter that is passed into the constructor. We define the constructor inside the input block that has the public macro attached to it. To define a constructor, we first use the constructor macro. We'll name this function constructor as well. Let's also make this constructor taking an input of the type U256. This number will be the number that will be stored in the state variable num. To set the state variable num, we saw in the previous video that all we had to do was call the function set and then pass in the new value. Okay, but how about the owner? How do we set this to the account that sent the transaction to deploy this contract? Now, if you're coming from solidity, all you have to do is set owner to message sender. However, this code is incorrect. In stylus, stylus contracts are deployed by another contract. If you set the owner to message sender, message sender will be the stylus contract that deployed this contract. But if you wanted to set this to account that submitted the transaction to deploy this contract, we have to change this to TX origin. TX origin as the name implies is the account that originated the transaction. So if Alice sends a transaction to deploy this contract, TX origin will be Alice. But however in stylus contracts are deployed by another contract. So message sender will be this factory contract that deployed this contract. So that is one thing that you need to remember about constructor. If you want to set the owner to the account that sent the transaction, make sure to use TX origin. Here I will show you how to create public and internal functions in your stylus contract. Public functions are functions that can be called by other contracts and accounts. Internal functions are functions that can only be called from within this contract. In this video, we'll be using a simple contract, a contract with a single state variable called num of the type in 256. Accounts and other contracts will be able to call this function to increment the state variable num. Let's turn this part of the code into an internal function. To declare internal function, we first need to write the code imple. This is simply rust syntax to define methods on a strct or enum. In this example, we're using the strct called examples. So, we're simply attaching methods to this examples strct. Now, the difference between the functions that we'll declare inside this input block and this one is that the one on the top has a public macro. So whatever function that we declare inside here will be public functions that other accounts and contracts will be able to call. However, if we define a function inside here, notice that it doesn't have a public macro. So this will be internal function simply functions that are attached to this strct. Okay, let's declare internal function. I'll start with keyword pub
Segment 9 (40:00 - 45:00)
fn. I'll name this internal function underscore ink. Since we're going to be moving this code inside to this internal function, it's also going to need the mutable reference mute self. And then I'll simply remove this code and then paste it here. Okay, so this is our internal function. How do we call this internal function inside here? Well, we can simply say self ink. And if you had other parameters that you need to pass into this internal function, for example, let's say that the internal function takes two other inputs. X of the type U64 and Y as well. Then here we'll pass those inputs. Let's say X and Y. But for this example, we're not going to pass any inputs. So I'll remove them. So this is an example of declaring and calling a internal function inside your stylus contract. Let's go one more step. What if you wanted to call a pure rust function? So far we defined all of our functions inside this input block, but we didn't have to do this. We could have simply called a rust function. For example, let's say that we have a function called that. It's going to take two inputs. X of the type U256 and Y of the type also U256. Since we're adding, we're simply going to return another U256 and then say X + Y. This is a simple Rust function. We can also use this function inside our contract. So, let's replace this part of the code. Here we're getting the current value of num and then adding a one. We can call a function add and then replace this code. Okay, there we go. Let's try to compile this contract. Make sure that it compiles. Inside my terminal, I'll type cargo build. Okay, and the code compiles. So in this video, I showed you some examples of how to create public functions, how to call this internal functions inside your contract, and lastly, how you can declare normal Rust functions and then use it inside the contract. Stylus has a macro that will convert the error that you declare in solidity into Rust code. So in this video, I'll show you how to declare errors in stylus contract and also show you how to do error handling. Let's start by looking at how to convert an error that is declared in solidity into Rust code. Here I have two errors that are declared using the solidity syntax. Error called unauthorized and an error called index out of bounds. To convert these two solidity errors into rust code, we first need to wrap these errors in a macro called so which is imported from alloy soul types. And for convenience, I'm also going to attach the attributes debug and partial equal. And that is it. It's pretty easy to convert your solidity errors into rust code. Next, how do we use these errors inside our stylus contract? To contract, we need to wrap them in rust code. So I'll declare enum called my air. This enum will have two types. Unauthorized wrapping the unauthorized air above and index out of bounds wrapping the index out-of-bounds air that is also declared above. To declare that this is going to be a solidity error, we also need to add the attribute solidity error. And we're now ready to use this error inside our stylus contract. How do you throw an error from a function inside a styus contract? Let's start with a simple example. Let's throw an error unauthorized inside the enum my air. I will say my air and then the value will be authorized. Now this unauthorized needs to store the value unauthorized which is a solidity error. So inside here I need to put a parenthesis and then say unauthorized. This is a strct an empty strct. So here I need to put curly braces without any values. Now if I was to throw a index out of bounds error then I would do something similar but change this to index out of bounds and also this as well index out of bounds. However the error for index out of bounds takes in a single input aint 256 and the name is called index. This is important to remember. Inside the curly braces we need to say index and then the data type is a U256. For example, let's say U256 from zero. Create a zero of the type U256. So this is the way to create the error inside your stylus contract. But how do we return an error? If you need to throw an error, then we need to return the result type. The result type, we need to specify two other types. The type that is returned for okay and when there is error. For example, if we're going to return a U32 for the okay type and let's say string for the error type, then we put U32 and string. If this function returns without any errors, then we need to wrap this in the okay. And inside here, we need to put a value of the type U32. For example, 1 U32. And if it is an error, then we need to say error with some string. Let's say hello. Okay, so
Segment 10 (45:00 - 50:00)
that is how results work. Now let's apply this to my air. In the case that there is no air, we'll simply return a empty tpple inside our code. This will look like okay and inside here we'll put a empty tuple. However, in this example, we're simply going to return air. So let's start with the declaration of the output. Air type will be my air. And to actually return air, we need to wrap this in a air. Okay. So this is a simple example of declaring a solidity error. and then using it inside your stylus contract. Let's look at two more examples of error handling. Let's say that you want to be lazy, so you don't want to define these error types. One way you can make the function return an error without defining these error types is to simply call the panic macro. This will help the execution of your code. Now note that when you call a function on wrap on a none or air, it also panics. So if you want to develop a stylus contract quick and dirty, then this is one way you can do error handling. Simply call a panic macro. And for the final example, let's do error handling more thoroughly. Let's say that we have two function that returns a result. For the okay type, the types are different, but for the error type, they are the same. Both of them return a my error. In the case of a air for simplicity, we're simply going to return the error types. Now inside this styus contract, let's say that there's a public function. And when this function is called, it's going to call these two function f and g. f and g both return the result type where the error type is my error. One way to handle these errors is to pattern match on the error. A shorter way to handle error is to use the question operator inside your stylus contract. A function will probably call into other functions which will return a result. If the error types are all the same, then you can use the question operator to make your code shorter. In the previous video, we saw that Stylus is able to convert your Solidity errors into Rust code. The same works with events. We can have Solidity events declared inside our code and convert it into Rust. For example, let's say that we have a solidity error here. I name it log and it takes in two inputs. To convert this into stylus code, we wrap this in a salt macro. Now that we have an event declared inside our stylus contract, let's look at how we will emit this event. Here I have a simple contract without any state variables and without any public functions. I'll create a public function called log and inside here we'll emit the event. To emit a event, we'll need to call function called log from the stylus SDK. We'll need to pass two inputs, the VM and the event that we want to emit. This log event takes in two inputs. address named sender and un 256 named bell. So inside this log strct which was created by this soul macro we'll need to put two datas sender and bell. For example for the sender let's put message sender and for the bell let's put u2561 and that is it. One way to call the contracts from your status contract is to first define an interface. To define an interface, you would define the interface in solidity syntax and then wrap it in a macro provided by the stylus SDK. For example, here I have an interface for a counter contract. We have two functions count to get the current count and in to increment the count. Note that this is solidity syntax. To convert this into Rust code so that we can use this inside our stylus contract, we need to wrap this in a macro. This macro is called sole interface. And now we can use this interface inside our stylus contract. So let's look at example. There are several ways to initialize the interface that we declared over here. The first way is to pass it as an input. Notice that we declared the interface called I counter. And we can directly use it as an input to this function. And if you wanted to call the function ink on this interface, then we simply call the function ink. The function ink is already provided by this macro. So this is one way of calling a contract that is declared by an interface. Let's look at another way. In solidity, we can also pass in an address and then instantiate an interface. The same goes for stylus contracts. For example, let's have a public function that takes in the address of the counter contract as input. Notice here the type of this variable target address is address. Okay. So from the address, how do we instantiate the interface I counter? Let's call this variable target that target. To instantiate the counter interface, we will call I counter new and then pass in the address of the target. Target address. This syntax is similar to solidity syntax. We simply pass in the address into the interface. Okay. But how do we call function? For example, let's call the function count
Segment 11 (50:00 - 55:00)
on this interface. We can simply call target. count count. And notice like in the previous example, we also need to pass in self. So this is one difference from solidity contracts and styus contracts. In solidity contract, there's no concept of self. So you don't need to pass it into functions. However, with stylus, you'll have to pass this around into different functions. Now when we call other contracts in the solidity definition, it says it's going to return a 256, but of course calling a contract may fail. So the type that is being returned here is not U256. It's actually a result of the type U256 and the error type the generic error type is vector of U8. It represents the error as bytes. So this is what is being returned when we call the function count. The same goes for ink. So let's say that we wanted to return the count of the counter contract. Let's first start by defining the output. What should be our output? Since here we're returning a result. If this call fails, we also want to fail this function. So let's also return a result. In the successful case of calling this contract, it's going to return a U256. So let's also return a U256. And for the error case, we'll return this error simply bytes vector of U8. Okay. So now let's return the result of calling this function count. One way is to pattern match. But the shorter way is to just use a question operator and then wrap this in a okay. If the function count returns a okay with the inner value of U256, then this question mark will unwrap this okay result U256 and then rewrap it in a okay. However, if this function call to count fails, then it's going to return a vector of U8 which will be propagated up to this function. So that this function will also return an error with the inner value of vector w8. So these are two examples of working with interfaces in your stylus contract. In solidity you can make low-level calls by calling the function called call on an address. We can do something similar in stylus contracts as well. To make low-level calls in stylus contracts, we'll need to prepare a strruct called raw call. Imagine that from our stylus contract we want to call a counter contract. Now, one way to call this contract is to simply define an interface and then call these functions. Another way is to use the low-level call. And to do this, we'll need to prepare the strct raw call. Let's look at example. Let's call the function count on the counter contract. And instead of using a interface, we'll use the raw call strct. To make a low-level call, we first need to wrap our code inside the unsafe block. And then we create the raw call struck by calling raw call new. Now we can make a low-level call by calling the method call. This function takes in two inputs. The address of the contract that we want to call in this case this will come from the input and then the data that we want to pass over to this target. This data will encode the function selector and the function input. The type that we need to pass in here is a slice of the type U8. For this example, let's call the function count. So here we'll need to pass in a slice of U8. To call the function count, we'll need to prepare the function selector for the function count. Now, there's a handy macro that is available from the Stylus SDK called function selector. So, we can call this by saying function vector and then putting in a string lit. We're going to call the function count. This is going to produce the correct function signature. And now, to convert this into a slice of U8, we'll need to call the function concat. And finally, let's return the result as a vector of U8. So to do this, we'll declare the output type as result of the type vector of U8. And for the air type is also vector of U8. Since the function selector is a macro, I forgot to put a exclamation mark. Okay. So this is a simple example of calling other contracts using the low-level method called call. Inside this, this is called raw call. Once you create the strct, you'll call a method called call and then inside it pass in two inputs. The address of the contract that you want to call followed by a slice of U8 which encodes the function to call and the function inputs to pass. The output will be bytes which is encoded as vector of U8. Therefore, here we're returning a result and in the okay case it's returning a vector of U8. Just like in solidity, there are three ways to send E if from a contract. The first way is to call the function called transfer e. The second way is to use the low-level call. And the third way is to instantiate an interface and then call the function which must be payable to be able to
Segment 12 (55:00 - 60:00)
receive beef. For the functions to be able to receive beef, they must be declared as payable. This is done by attaching a payable macro on the functions. Let's look at example of sending if using the function called transfer. You'll first get the value of E that was sent to this function by calling BM message value. This is the amount of E that was sent when this function test transfer is called. Now to transfer E to a target address, we simply call the function transfer E on the BM passing in the address that's going to receive the E and the amount to send from this contract. Since this function may fail, it's going to return a result here. For simplicity, we'll unwrap the result. If it fails to send, unwrap will panic. So, this function call will also fail. Now, let's look at an example of sending E using the low-level call. Again, we'll start by getting the amount of E that was sent to this function call. Next, we need to prepare the strct called call. Next, we specify the amount to be sent. In this example, we'll forward all of the E that was sent to this function. This value is stored inside the variable message bell. And finally, to send E using the low-level call, we'll use a macro called. It's going to take three inputs. This configuration, which specifies the amount of E to send, the address that's going to receive the E, followed by any data we want to pass. Since we're simply sending E, this data is going to be empty. Again, the function call is going to return a result. And finally, let's look at example of sending E using an interface. So before I show you this example, I first need to define an interface. Imagine that we're going to call into a contract called I pay. It has two functions deposit and withdraw. The function deposit is payable. Meaning that when we call this function, we can also send ETH to this contract. So in the last example, let's call this function deposit. Sending E. We'll first initialize this interface at this target address. Again, in this example, we're going to send all the E that was sent to this function. We prepare the cost strct. This is the same way that we did in the previous example. Instantiate the call truck and then specify the amount of beef that's going to be sent. And finally, we call the function deposit on the I pay interface at this address in the call struck that we prepared above. Okay, so these are three examples of sending E from a stylus contract. There are two ways to receive E in a stylus contract. The two special functions are called receive and fallback. Both of them must be declared as payable to receive e. Let's see how to declare a payable receive and a fallback function in stylus. To declare a function as the special receive function, we need to attach the macro called receive. To be able to receive beef, we also need to declare as payable. Let's call this function receive. Now the output type is important. We need to declare as a result of a type of empty tuple and vector u8. If we didn't do this and try to compile the contract, the code will not compile. It says over here that it expected the output to be of this type. So let's put this type back in. Since we're returning a result, let's return a okay with a empty tpple. Okay, so this is one way to receive if inside your stylus contract. The other way is to have a payable fallback function. It's going to follow a similar pattern to this function, but let's copy this and then paste it here. For the fallback function, instead of receive, this will be called a fallback. Notice that we're using the fallback macro instead of the receive macro. It's also payable so that it can accept if and the output type is also important, but not as limited as the function receive. For example, here we can pass in an empty tuple. Let's double check by trying to compile the contract. and the code compiles. But we can also pass in other types. For example, a vector of U8. In this case, we'll need to update the type that we're returning here. Let's say a empty vector. Compile the code again. And again, the code compiles. So these are two ways to receive E inside your stylus contract. Either receive or fallback. The most difficult part about using the kit 256 hash function in a stylus contract is to encode the data. To call the function ketchup 256, we need to prepare a single input bytes of the type T where T is this type that looks a little bit complicated. So the most difficult part of calling the catch 256 is preparing the input to match this type. In this video, I'll show you an example of encoding address num and some string message and then passing it into the kit 256 function from a stylus contract. If you want to return bytes 32, we'll need to return fixed bytes having the length 32. The way that we're going to hash all of these inputs is first we're going to encode these inputs into a variable called data and then
Segment 13 (60:00 - 65:00)
pass it into the catch check function. Now the catch function returns a type called B256. However, from a stylus contract, we need to return this data as fixed bytes 32. The simplest way to convert B256 into this type is to simply call into Rust will automatically convert the type into the appropriate type. Now let's move on to the hard part. How do we encode these data into inputs that can be passed into the function get check? The way I found out the answer was to simply ask the AI and then try to compile the code until it worked. Let's start with address. On the address, we need to call the function as slice. This will convert the address into a slice of U8. Next, we need to convert the U256 number into something that looks like a slice of U8. We do this by calling 2B bytes back. Here, BE means big Indian, which means that the most significant bite comes first. For example, if you add 0x12, the most significant bite will be 1 followed by two. So, this turns out to be 16 + 2, which is equal to 18. And finally, we need to convert the message string also into something that looks like a slice of U8. We do this by calling the function adds bytes. And finally, we put all of these slices together by calling the function concat. This is an example of how you prepare the inputs which is to be passed to the function kak. Now, if you have other inputs that you need to pass to this function and if you're having difficulty figuring out how to encode the data into the correct format, I recommend that you ask an AI. When you're creating a ERC20 token, one common specification is that only the owner of the contract is able to mint tokens. So in this video, I'll show you an example of creating a ERC20 token where only the owner of the contract can mint and I will also explain how inheritance work in a stylus contract. So when you're creating a token where only an authorized account can mint, there's a common logic for authorization and some logic for ERC20 tokens. These are usually split into separate files, one for authorization and the logic for ERC20 token. Combining these two together into a single contract, we can easily create a ERC20 token where only the owner can call mint. So let's look at example of how to do inheritance in stylus. First, I'm going to show you where the files are located. Inside source, I have a file called alt. RS. This is a stylus contract that is responsible for authorization. It has a single state variable called owner. There's one public function called owner which will get the current owner of this contract. And there's also a private function to check whether message sender is the current owner or not. Later we'll call this function inside the function mint to check that message sender is the owner of the contract before minting tokens. Okay. So this is the alt contract. Next let's move on to the other contract ERC20 token contract. Now for simplicity I removed most of the logic for an ERC20 token. This little code will be enough to show you example of inheritance. In this example it has the state variable total supply and the public function that will get the current value of the total supply and it also has a private function to increase the total supply. Okay. So now let's combine the contract alt and ERC20 into our contract called token. Let's start with init. Inside the init function, I want to be able to set this owner state variable to message sender. And I only want this function to be initialized once. This function can only be called once. When it is called, it's going to set this owner state variable inside the alt contract to message sender. The first step to inheritance in stylus is to use the macro called inherit. Since this contract is going to inherit the alt and the ERC20 contract here we declare as inherit alt and ERC20. The order of inheritance is important since the function to call on the contract will be searched from the lowest to the highest inheritance. If two inherited contracts implement the same function then the function higher in the inheritance tree will be called. Let's come back to this later. So going back to our function in it we only want this function to be called once. So we first check whether the state variable initialized is set to true or not. If it is not, we set it to true. And now we want to set the owner storage variable that is declared inside alt. We do this by calling self. er set and then putting in message sender. Okay. So this is an example of setting the storage variable inside a contract that this contract is going to inherit. Now let's look at another example. Calling the function mint. Mint will mint tokens if message sender is the current owner of the contract. We can check that contract by calling the private function check alt inside our
Segment 14 (65:00 - 70:00)
contract alt. Inside a contract alt, we have a private function called check alt which checks that message sender is the current owner. Back inside our token contract, we can simply call this function. Next, we call the function mint on the ERC20 contract. Let's look at this function. So inside ERC20 contract, there's a private function called mint. In this example, we simply increase the total supply. Okay. So this is example of calling the function mint. But there's one more step that we need to take to complete this contract. Inside our storage variable declaration, we need to declare that this contract is also going to use the storage that is defined inside alt and also inside ERC20. So we need to say alt is from the alt contract and ERC20 is from the ERC20 contract. And finally we need to put a borrow macro. This is a macro that is provided by the stylus SDK. Okay. And that completes our contract. Let's try compiling this. Inside my terminal I'll type cargo build. Okay. And our contract compiles. Let's also export the AI to see what functions are being exported. Okay. Here are the functions that are being exported. We have the function called init and mint. The I token interface also inherits from I alt and IC20. The interface IC20 exports total supply. The interface I alt exports the owner state variable. Now that we've seen a basic example of how inheritance work in a stylus contract, let's dig a little bit deeper into the details. The two particular rules that I'm going to explain next are the method for the search order and overrides. When a function is called on a contract that inherits other contracts, how is the function to be called searched? And then override. If two contracts implement the same function, then which function is being called? Let's start with the first question. Search order. The rule for how a function is searched is the following. It first searches for the type that has entry point. In our example, it's only this contract that has the macro entry point. We can check this that the alt doesn't have the macro entry point and the ERC20 contract also doesn't have the macro entry point. It's only this contract. So when you call a function on this contract, it's first going to look for this strct token. The search starts here. Let's say that we call the function total supply on our token contract. The search will start here. If the method is not found in that type, the search will continue in the inherited types in the order of inheritance. So let's go through an example. The function total supply is called. It's going to first look for the function inside this token contract. Two functions that are declared inside here are init and mint. Total supply is not declared inside this contract. So now it's going to move on to the next contract alt. inside alt it's going to check again inside the alt contract the only function that exists is owner the function total supply is not declared inside here okay and finally it checks ERC20 contract inside ERC20 contract the function total supply is declared so this is how it finds this function and then execute the code inside here so that is how the function is searched for a status contract next let's talk about override if two contracts implement the same function then which function is getting called. For example, inside our al contract, we have a function called test call. Inside our ERC20 contract, we also have the same function. So when we call the function test call on our token contract, which function is being called? Is it test call inside the alt contract or ERC20 contract? So what is the rule for function overriding? If two types implement the same method, then one in the higher level in the hierarchy will override the one in the lower levels. In our example, the hierarchy of inheritance from lowest to highest will be alt followed by ERC20 and then the highest will be token. So when we call the function test call since the function test call is defined inside alt and ERC20 is higher in the hierarchy of inheritance. This is the function that will be called. The data types that are available in stylus contracts are basically the same as solidity. However, to be able to use these data types, we'll need to use different libraries. To use the unsigned integers and assigned integers, we'll need to use the library alloy primitives. The same goes for address. For booleans, we can use the rust primitive boolean. For fixed bytes such as bytes 32, again we'll use the alloy primitive library. And finally, for bytes will use the stylus SDK. So let's look at some examples. Here I have an empty contract. As a reminder to initialize a contract, we need to
Segment 15 (70:00 - 75:00)
declare a strct and then put a entry point macro. And inside here, if you want to define state variables, you would define it inside this strct. Now smart contract will have public functions. We declare this inside this input example block which defines that the functions inside here are functions meant to be called on the smart contract. Let's first take a look at unsigned integers. There are unsigned integers of different bit sizes. For 8 bits we have the U8. For 16 bits we have U6. For 32 bits we have U32, U64, U128. And finally U256. Here you'll only need to import the types that you need. In our example, let's use U8, U128, and U256. Okay, let's actually initialize these data types inside our function. I'm going to create a function called onsign int. It's going to take a immutable reference. So, this will be a read only function and it's going to return U8, U128, and U256. Let's begin by creating a U8. Let's say that U_8 is equal to U8. From inside here, let's put in a number. Let's say 1. Let's also do the same for U128 and U256. Okay. And then let's return these. I'll use a tpple to return all of these. U8 U128 and U256. Okay. So this is an example of how to work with the unsigned integers in stylus. Moving on to sign integers. Working with sign integers are basically the same as working with unsigned integers. However, the function that you'll have to call to convert it into the types of signed integers for alloy primitives might be a little bit different from the functions that you call for unsigned integers. Okay, let's move on to address. We first need to import the address type from alloy primitive. For this example, I'll also import the address macro. This is a macro that will convert a string into a address with check sum. Okay, so let's look at some examples. The first way to create an address is to call the function from on the address type. In this example, I create an address that will repeat the value 01 20 times. You can also create an address by using the address macro. Inside this macro, you will put the string literal of the address. And this will return a checked sum address. Okay, moving on. The next type that I'll explain are booleans. They're really simple. They're basically the booleans from Rust. And here's an example function that will return true and false. Okay, the next type is fixed bytes. For example, let's create a fixed bytes of 32. So, we'll need to import this fixed bytes and we'll write our example inside this function. The type fixed bytes is actually a generic type. It's generic over the length of the bytes. If we're going to create a 32 bytes, then inside here we put 32. And here's the example of converting a string of 32 bytes into the type fixed bytes of length 32. You might need to come back to this example when we talk about hashing. The kachak 256 hash produces a 32 bytes. Okay. And the final topic is bytes. To work with bytes, you'll need to import the bytes type from abi. Let's create a function that will return bytes. I'll first create a vector of u8 having the values 1 2 and three. And then we convert this vector of u8 into bytes by passing it into the function from. Okay. So these are some examples of the primitive data types that are available in stylus. By default, arbitum stylus contracts compile with re-entry cars enabled. So for example, here I have a simple contract called counter and it has a function called set number. In solidity, you will have to explicitly put a re-entry guard on this function to protect your contract from re-enency attacks. However, notice that this function doesn't have any re-entry guard. But we don't need to put it in since by default styles contracts will compile with re-entry guard. Now if you wanted to remove this re-entry guard you can opt out of it. To do this inside cargo. tl you need to look for the rust crate called styus SDK under the tag dependencies. And what you need to do here is add a feature. The feature to opt out of re-entrance guard is called re-entrant. With this feature enabled let's try compiling the contract again. The contract compiled. Now our contract has the re-entency guard disabled. So this is one of the many advanced features of stylus contracts. In stylist, you can import rust crates and then use it inside your stylus smart contracts. So let me show you an example of importing a rust crates into your stylus contract in this video. Now there are limitations on the rust crates that you can use inside your stylus contract. The biggest limitation is the size of the smart contract. If you were to import these crates, currently there is a 24 kilobyte compressed size limit of the Wom contract. So after you compile stylist Rust contract into WSM and then compress it, the size cannot be larger than 24 kilobytes. The problem with using most Rust crates is that it also
Segment 16 (75:00 - 80:00)
depends on the standard library of Rust. Using Rust code that depends on the standard library will increase the size of your smart contract beyond 24 kilobytes. Therefore, when you import the rust crates into your stylus contract, one recommendation is to look for rust crates that has a tag called no standard library, which means that the crate does not depend on the standard library of rust. Okay, so with that said, let's look at an example. In this example, I'm going to import and use the rust decimal library. This library is a decimal number implementation written purely in Rust and it doesn't use the standard library. Therefore, using this library inside your smart contract will not make the size of your smart contract exceed the limit of 24 kilobytes. Okay, let's first install this library. I'll click on this link. This is the library that we're going to be installing and the version is 1. 37. Inside my code editor, I have a stylus project initialized. We'll need to install the Rust crate into cargo. tl. Inside here under the tag called dependencies, we'll install the Rust decimal library version 1. 37 with the feature macros. The next step is to use this library inside our stylus contract. In this example, all of the logic for the stylus contract is located under lib. RS. I'm going to import the rust decimal library along with the strugg decimal which I'm going to need for this example. Now, let's actually use this library inside our contract. Here I have a function called div which takes in two inputs of the type U256 and U256. What we're going to do is use the Rust decimal library to convert these into decimals, divide and then cast the result back into U256. Now how would I convert a U256 that comes from the alloy primitives into something that I can use with the library rust decimals? Furthermore, how do I use this library to divide and then cast it back into U256? The details of how to use the Rust decimals is not so important in this video. So I'm going to ask the AI to write the code for me. I'm going to ask chatgpt how to use the Rust decimals library to divide two U256s. Okay. So the question that I'm going to ask is example dividing U256 from alloy primitives using Rust decimal and then converting the result back into U256. Okay, here's the result. I'm going to copy most of the code but not all of it. And then back inside our stylus contract, I'll paste the code. Next, I'm going to change the numerator and the denominator to the inputs X and Y. We'll convert all of the numbers into U128. So this Y will be converted into U128. And finally, we'll need to return the result as the type U256. So instead of assigning it to a variable, we'll simply return it. Okay. So that is an example of using the Rust decimal library. Let's try compiling the contract. Cargo build. Okay, the code does not compile since there's no function called from U128. So I need to fix this part of the code. It turns out that the function that I need to call is called from. Okay, let's try compiling the contract again. The next error that I get is called method not found in decimal for the function called expect. So I'm going to remove this then try compiling the code again. Okay, this time I'm getting a different error saying that there's no function called 2 U128. Okay, so after asking the AI several times, it turns out that I need to replace this code with this code over here. The difference is there's no function called 2 U128. I need to first convert it into a string and then to convert a string into U256, I need to call a function called from string radex. Also rename rest to result deck and then try compiling the contract. And now our contract compiles. I've also written a test to test this function out. Inside here, here is a unit test. It deploys a contract and then for the input x and y it uses three and two calls the function div and then checks the result. Let's see what the result is. We'll print out the result and then run cargo test. Now if you also want to see the print statements inside your terminal when you run test you'll have to pass in an extra parameter called no capture. Okay. And our test passed. The result of dividing three with two is equal to one. And finally let's check that the contract can be deployed. To do this, we'll call the function cargo styus check. But before we do that, you need to make sure that you have the nitro dev node running. To run cd into nitro dev node and then run this script. Okay, once the nitro dev node is running, I'm going to open another terminal and inside my stylus project that I imported the rust decimal library, I'll type cargo styus check. Okay, it passed the check. In summary, inside your stylus smart contract, you can use rust crates. The only limitation is the size of the contract if you were to use these crates. The final size of your wasom contract cannot exceed 24 kilobyt. To make sure that your smart contract stays within the size limit, it is recommended to use rust crates under the category called no std. These are rust crates that don't depend on the
Segment 17 (80:00 - 85:00)
standard library. In our example, we imported the Rust decimal library and then used it inside our stylus smart contract. And finally, to check that our smart contract can be deployed, we run the command cargo stylus check. If this passes, then your smart contract can be deployed. Let's look at variables and stylus. We'll start with the simple one and then move towards complex variables. The simplest one is local variables. Let me show you some examples of local variables inside our contract. For example, we can create a local variable of the type U32. Note that this U32 is a primitive data type of Rust and it's not the type that we import from alloy primitives. We can also have booleans and the unsigned integer type that we import from alloy primitives. Here I'm creating a U256. So these are some examples of local variables. They're basically variables that are created live in the memory and then after the function is done executing they are deleted. Okay. The next variables that we can work with are global variables. These are variables that return some information about the blockchain, the contract or the transaction. So let's look at some examples. To access the global variables, we first need to call self. bm. BM standing for the virtual machine. An example of this is getting the current block timestamp. We first call the function BM on self and from it we can call the function block time stamp to get the current time stamp. Now in solidity the time stamp is returned in U in 256. But note here that the time stamp is returned as U64. So that is one difference between solidity and status contracts. When you call contract and send messages, you can get the amount of E that was sent by calling the function message value. If you want to get the sender, you can call the function message sender. The current contract address is store that contract address. And finally, if you want to get the Eve balance of an account, you would call the function balance. In this example, it is getting the Eve balance of this contract. Okay, so these are some examples of working with global variables. Moving on, let's talk about constants. The way you would create constants in a stylus contract is a little bit different from solidity depending on the type of constant that you're creating. For example, to create the data type address or U256, we need to call the function U256 from or address from the library alloy primitives. So to create a constant in Rust, you might think that all we have to do is declare a variable with the const keyword and then call these functions. For example, here it is trying to create a constant of the type address and the next line U256. But this is not how constants can be defined in Rust. When you define a constant, you cannot call into functions. It must be simple values. So we won't be able to do something like this. To this, we first need to create a value that is a literal. For example, for address, we would turn this into a string slice and we won't be able to call the macro address. And the same goes for number. Since we won't be able to call the function U256 from let's change this into a number type that is available in Rust. For example, U64 and then change this into 1 2 3. Okay. And to convert these into constants, we'll need to create a function that will convert them into constants. For example, inside a function, if you wanted to reference that address constant, which is a string lit, but you need to convert it into a address, then you will call the function from the alloy primitive to convert that string lit into the address type. And the same goes for the number constant. In this example, the number constant is of the type U64. But if you wanted to convert it into a U256 from the alloy primitives, then we'll need to call the function U256 from. and this must happen inside a function. So that's a difference between solidity and stylus contracts. Okay, moving on. Let's finally talk about storage state variables. To declare state variables, we'll first need to import some modules from the stylus SDK. I'm going to import bunch of stuff that we're going to need for the examples of state variables. These modules will come from the storage module. Okay, let's look at an example of creating some state variables. So actually there's two ways of creating state variables. The first way is to define our state variables inside a sole storage macro. Inside this macro, we'll be able to write solidity code. For example, we declare a strct called examples. And since this is going to be our contract, we'll also put the entry point macro inside this strct. Let's say that there is a state variable, a boolean called initialized, the owner of the contract of the type address. We can also have UN256, a mapping, and a vector. So this is one way of defining state variables inside a stylus contract. Inside the soul storage macro, you would write your solidity code that defines a strct. Next, let's look at how we would do this same exact thing but
Segment 18 (85:00 - 90:00)
without using the soul storage macro. So I'll first copy these and then I'll paste it inside here. Let's start with boolean. Boolean will be of the type will be a boolean and the storage type that we need to declare here is called storage boolean from the storage module. The next one address will also be storage address. UN256 will be storage U256. Okay. And a mapping. How do we create a mapping? The name of the mapping is balances. And to create a mapping, we need to use storage map. Mapping we need to also declare two more types. The key type and the value type. The storage map is a generic type. And here we define the key type and the value type. For the key type, let's say is an address. This address will come from the alloy primitives library. And then for the value type, it's not U256. Since we're storing a U256 into the storage, this has to be storage U256. Okay, so that's mapping. How about a vector of U in 256? What we need to use is the storage vector. A vector is a generic type. So again, here we'll need to put in the type of the vector. Again, it's not U256. It's actually since we're storing the value inside the storage, it's actually storage U256. Okay, so these are some examples of how to declare state variables in Stylus SDK. Let's do one more. Let's create a nested mapping. Say allowance allowances and then say storage map another address and then finally the value. Next, let's look at examples of how to read and write to these state variables. How do you read from a state variable? Let's start with a simple example. To get the boolean value that is stored inside this contract called initialized, you will call self. initialized and then call the function get. Since the storage variable initialized is a boolean, this will be of the type boolean. If you wanted to get the owner, we do the same thing. Self. owner. get. If you wanted to get the total supply, again the same thing. Self. total supply. get. How about a mapping from the type address to a U256? Again, for a simple mapping, we simply call the function get passing in the key. Since this mapping's key is an address, we pass in an address. This will return a U256. To get the length of the vector, you can call the function length on the storage vector. And to get the value of a vector, you will call the function get passing in the index. For the final examples, let's look at how to write to these state variables. Let's start with the simple example. How do we write to the state variable initialized? We update it by simply calling the function set and then passing in the new value. The same goes for an address. We simply call the function set on storage address passing in the new value. Total supply is the same as well. We create a U256 and then simply set it. How about for a simple mapping of the type from an address to a U256. For mappings, we call the function insert passing in the key and the value type. And here is an example of pushing a new value into a storage vector. You simply call the function push passing in the value. And finally, let's look at how to write into a nested mapping. Earlier, we created a nesting mapping called allowances, which is a map from an address to another and finally to U256. So, how do we write into this mapping? In solidity, it's pretty simple, but in stylus, it's a little bit more complicated. So, let me go through some examples. For this example, I'll be using two address called owner and spender. Oh, how do we access a nested mapping in stylus? Your first guess might be to do something similar to this. First, from the allowances mapping, access the mapping at owner. Imagine that this will return a new mapping. And then to insert it into the owner's mapping, we will call insert passing in the spender and some kind of value. Let's say bell that is defined over here. However, this code will not work. Trying to build a contract. You notice that the code does not compile. This is because when you call the function get it returns a immutable reference. Then on the next function call we're trying to modify from this immutable reference. Hence this code does not compile. Now to work with nested mappings we need to replace this get function with something else. That function is called a setter. So what does this function setter do? Let's check the documentation. The storage map has a function called setter which returns another thing called storage guard mute. You can think about this storage guard mute as like a mutable reference. So calling the function setter is like returning a mutable reference. So what it's doing over here it's returning a mutable reference. And now we can call the function insert to insert a value for the nested mapping from owner to spender. Let's try compiling a contract. Call execute the function cargo bill. And the code compiles. But what if you wanted to get the current value that is stored in the mapping from owner to spender and then
Segment 19 (90:00 - 95:00)
maybe increment it? Well, one way you can do this is by first getting the value that is stored in this mapping. B is equal to self allowances dot get owner and then get spender. Once you get the value, let's say that we wanted to increment this value by one. So this is one way of updating the value. Get the current value and then insert it. But there's another way that we can accomplish the same thing. The idea is to first get the mutable reference. So let's say that I'll call this allowances is equal to self. allowances setter of owner. So this will return a mutable reference. And if you want to know the type, it's called storage guard mute since we're getting the mutable reference. And it's generic over two parameters, the lifetime and the value type. For the value type, we will simply just put a placeholder and let Rust figure it out. So, we're calling the function setter. It's basically what we did over here. We're getting a mutable reference. Since this mutable reference represents another mapping, we can do this again to get the mutable reference for the inner mapping. So, let's say that mute spender allowances is equal to allowances, the mutable references that we got above, and then say setter. This time we pass in the key for spender. So the first code will return a reference the mapping from the spender the amount and the next line will return this amount that we can. And now we can call the function get and set similar to other storage variables to get the current value. Let's say that val is equal to spender allowances. get and to set it we call the function set. For example, let's set it to bell plus U256 from one. Increment it by one. So this code over here and do the same thing. And finally, I'll mention a little bit more about the storage guard mute. Remember that when we call this function setter, it returns a storage guard mute. This storage guard mute is like a mutable reference. And remember that one of the rust borrowing rule is that you cannot have two mutable references existing at the same time. But if you call this function setter twice, this is like creating two mutable references that exist at the same time. So your code will not compile. And to show you this, let's create another storage guard that references the same storage. Let's rename this to another. Try to compile the contract and the code will not compile. This is because we're trying to create two mutable references that exist at the same time. But the compiler will not allow this. This compiler check makes sure that there is only one mutable reference to a storage variable existing at a time. Okay. So these are some examples of writing into state variables. For the final project, you'll be building atomic swaps in stylus smart contracts. So what is an atomic swap? Atomic swap is a technique for swapping tokens across chains without bridges. Currently, the most popular way to transfer tokens across chain is to use bridges. You would send your token to a bridge on one chain and then receive the other token on the other chain. However, using atomic swap, we can accomplish the same thing without a bridge. For example, let's say that we have Alice on chain A with some token. Let's call this A. She wants to take this 100 token and then swap it for token B on chain B. The first step in atomic swap is to find a person on chain B that is willing to give this token over to Alice in exchange for 100 token A on chain A. Let's say that Alice finds Bob offchain. He's willing to swap his token B for Alice's 100 token A. They agreed to swap 100 token A for 30 token B. Here is where Atomic Swap starts. We're going to need basically the same contracts on these two chains. First, Alice needs to come up with some secret value that she is not going to share with anyone. When she uses the hash function on this secret value, she's going to get some kind of hash. She's going to need this and the token which will be locked inside the smart contract. Inside the smart contract, she's going to send over the 100 token that she's willing to give to Bob along with the hash. Now, this token can be unlocked under two conditions. The first condition is that Bob knows the pre-image, the secret value that hashes to this hash. So if Bob knows the secret value when put into a hash function and then produces this hash, then Bob can unlock this 100 token. Now, it's important here that only Bob can unlock this token since Alice also knows the pre-image, but for the protocol, we only want to restrict Bob to be able to unlock this token. The other condition to unlock this token is that there is a
Segment 20 (95:00 - 98:00)
time limit on how long this token is locked inside this contract. When this time is up, then Alice can unlock the token. This is a feature that is needed for Alice to withdraw the token in case Bob does not commit his token on chain B. The next step is for Bob to lock his token. Now, similar to this condition, there are two conditions to unlock token B on chain B. The first condition is that the tokens can only be unlocked by Alice. The other condition is that whoever unlocks it knows the pre-image that when hashed will be equal to this hash. Obviously, Alice knows the secret value that hashes to this hash. So, Alice can unlock this token B. The other condition is meant for Bob. In case Alice does not reveal the pre-image, after a certain time, Bob will be able to unlock his token. Now there's one important constraint on the lock expiry time. The constraint is that the expiration time that is set on this lock must be greater than lock. So why is that? Well, imagine the case that the expiration time set over here is shorter than the expiration time that is set over here. Alice can unlock her token before Bob can unlock his. So what can go wrong? Well, what can go wrong is that Alice can take this coin and also unlock her token. To see this again, let's say that the expiration time over here is shorter than here. What Alice can do is after the lock expires, she would claim her token back and then right after it, she will reveal the pre-image and then also unlock this token. In the end, Alice will have her token A back, and she will also claim Bob's token B, which is a problem for Bob. He wanted token A, but he ended up just simply losing token B. Now, this situation cannot play out as long as the expiration time on this lock is longer than lock. So, here I should also mention that for Alice to be able to claim token B, this lock must not be expired. And the same over here for Bob to be able to claim token A. The lock over here must not be expired. Okay, going back. Let's say that the locks are not expired. The next step is for Alice to reveal her pre-image. The conditions are that she must reveal the pre-image that when hashed will match this hash that is stored in the smart contract. She reveals the pre-image. Hashing the pre-image, it matches the hash stored in the smart contract. So she will receive this token. Now once this pre-image is revealed, Bob also knows the pre-image that will hash to this hash stored on the smart contract. He uses the pre-image that was revealed by Alice to unlock token A. So what is our final result? Our final result is that on chain B, Alice has token B. On chain A, Bob has token A. They were able to swap tokens across chain without a bridge. And this is how an atomic swap works. For the final project, you'll do a atomic swap between Ethereum Superolia and Arbitum Seapoleia. The contracts on the Ethereum Solia side is written in solidity and already prepared. In the final project, what you have to do is write a stylus contract for the arbitrum sepia side. Now, if you want to look at the contracts written in solidity, they are located on their notes atomic swap. Inside here, you'll see instructions on how to build this contract, how to run tests, how to deploy the contracts, and then how to execute scripts to perform a atomic swap.