Lesson 1: Intro to Smart Contract Development
About this lesson
Welcome to our introductory lesson on smart contract development, blockchain fundamentals and your first taste of coding with the Solidity language. For this lesson, you donāt really need to have development experience, although we expect you to have a ātechnical mindsetā to be able to proceed. We would recommend Build a basic NFT as a subsequent lesson for getting familiar with more advanced concepts and developer tooling. But first things first.
We have some questions for you throughout the lesson for testing your previous knowledge, predicting next steps in the lesson, and letting you see for yourself how well you're absorbing the new content. There's also a quiz at the end, of course, so make sure you're checking out all the side-drawers for a deeper dive of any new concepts. To complete the lesson, expect somewhere between one and four hours depending on your previous experience and the need to learn new ideas. Remember your well-being and set the pomodoro timer in the menu header to ensure youāre taking regular breaks, and please do go outside and 'touch some grassā.
What are we building?
Weāre going to build a smart contract, but what on Jupiter is a smart contract? And what on Earth would we use one for? Well, you wouldnāt find a smart contract on either Jupiter, or Earth, and although you may be grounded to the latter, you are going to deploy yours on a blockchain. Not a real blockchain, well not at first. Hang on!
Let's pause here for a couple questions to give you a taste of what's to come and give yourself a chance to test what you already know.
How was that? No stress if the answers didn't come naturally. We're here to fill in the gaps! If you're interested in more context before moving along to the lesson, check out the explainers below.
Did you set the pomodoro? āš
What are we going to do?
By the end of this lesson weāll have learned a lot. A simple breakdown of the steps to get there is:
- Set up our work environment
- Create a Smart Contract
- Learn basic Solidity concepts
- Define variables
- Create a function
- Learn about events on the blockchain
- Deploy our Smart Contract in Remix
At the end of the project, weāre really going to test your knowledge with a quiz, so take in whatever you can. We might even have some questions along the way for fun to expand your already amazing mind!
Letās get this party started!
Weāre going to write a smart contract, which is going to be a āWeb3 fortune-teller". The idea is that you will input a message into the contract, the contract will decode the message, and like a fortune-teller would do, predict your future⦠in Web3.
Before we start, we need a place to write our Solidity language smart contract, and some tools to run it. For that we use the Remix IDE, short for...
Setting up Remix (IDE)
To get started we will use the Remix IDE to write, compile, test and debug code quickly and easily. Head over to https://remix.ethereum.org/ to see what itās all about and get started.
On the left hand side, in the File Explorer tab, delete all the files and
folders in our workspace. We want to start fresh. Create a new folder named
contracts
and in it create a new file named WAGMI.sol
.
The extension .sol
is used for files in the Solidity language.
Well done! Now, go touch some grass! āš
Now we begin writing our code
The first line of a Solidity file is for the license, the second line lets the IDEās compiler know which Solidity version we are working with.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4;
The MIT license is an open source license. We at Developer DAO love open source,
and we love building public goods that others are free to build on⦠for free!
The version of Solidity weāre working with is 0.8.4
and above(^
).
Diving into our Smart Contracts
Virtually all smart contracts contain variables, data containers that can be modified, and functions, which are like ārecipesā with instructions for those variables. When a user wants to make a transaction, the function is called, i.e. executed or initiated.
Letās create our first smart contract.
It is good practice to give the same name to the contract (uppercase first
letter) and the contractās solidity file. Letās create a contract named WAGMI
in our WAGMI.sol
file.
The syntax for creating a basic smart contract is as follows:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; contract WAGMI { // ====== This is where we will write our code ====== }
We can use //
as above in Solidity to write inline comments that are not
executed. As programmers we write text like this to remind ourselves and inform
others what our code is supposed to do. Itās a good habit to get into! Some
types of comments can even be used to automatically generate documentation, but
we'll leave that lesson for later.
How to define a variable
How can we tell the world about our future in web3? We want our contract to be able to store the message from each new user, and also record the count of messages from all future users. We might want to check on their fortunes too! So we store the message and the count in variables. Do you remember the variable = container analogy? Different types of variable can store different types of information, e.g. numbers, āstringsā of text, addresses, etc. In this case we need state variables. While other variables might only be used temporarily in memory, state variables live permanently in the contract which helps to make blockchain transactions transparent. For the moment, weāll just create them as placeholders with no value, but when we interact with our smart contract later, they will be set with actual values.
We chose message
and messageCount
as our variable names, and we will assign
them the types string
and uint256
respectively.
Letās add our two state variable definitions inside our contract:
// ... contract WAGMI { string message; uint256 messageCount; }
Creating Functions
Our contract needs a way to store new information on the blockchain! But both our variables are empty and have no way of storing any info... yet.
Thatās why we now need some functions to store and retrieve information to and from our contract on the blockchain. The basic syntax for a public function in Solidity would look like this:
function functionName(uint256 value) public returns(bool) { // ====== Function logic here ====== }
We want to be able to read both the message and the count, so we are going to create a function to get the value for each variable. Later on we can set (store) the values, but first letās retrieve them. We have to add two new functions below our state variable definitions. Do not delete our message and messageCount variables and please pay attention to all the ; and ( ) symbols:
// ... contract WAGMI { // Don't delete our state variables here! function getMessage() public view returns(string memory) { return message; } function getMessageCount() public view returns(uint256) { return messageCount; } }
Yes, there are some new keywords up there e.g. return, returns, view, etc, but donāt panic:
public
defines the visibility of our function and means it can be called
from anywhere e.g. directly calling it with our externally owned address (e.g.
our crypto wallet), from another smart contract, or even from another function
inside our smart contract. We can use a public function for making a
transaction, or querying a value from a contract.
view
means the function canāt modify the blockchain state, so it doesnāt
store any value or trigger a transaction by itself. It is useful for querying
information from a contract, such as an account balance.
memory
tells us where the variable lives (for some types we have to tell
solidity if a variable is in memory
, storage
or calldata
). We can leave
these concepts for future lessons, or if you canāt wait that long, you can read
some more about them in More on Functions.
Ok, now we have to figure out a way to store some info on our contract!
We want to set a new message in our contract, so now we define a new function below the ones we recently created. The function should receive a new message as a parameter, store it in our state variable and also update the message count:
function setMessage(string calldata newMessage) public { // We add 1 to the messageCount state variable messageCount++; // We update the message state variable message = newMessage; }
If you noticed, this function, unlike the previous ones, does not have the view visibility keyword and it does indeed modify the state of our contract and the blockchain. Therefore, whenever someone calls this function in the future, their wallet is going to ask them to confirm a transaction on the blockchain. That means it will use some gas, which is a transaction fee of a small amount of eth, since we are using the Ethereum blockchain. As we progress on our learning journey we will hear a lot more about gas and fees, but thatās also for future lessons.
Go touch some grass and set the pomodoro when you return āš
Events and dealing with on-chain storage
So far, weāve learned to how make our contract store a message on the blockchain, and how we can use a function to retrieve it. But every time we store a new message, we overwrite the old one.
We would love to have a history of the messages, but storing everything on-chain is expensive and uses that precious space we mentioned before. When we do a deep dive into the fundamentals of nodes and storage, youāll understand why itās precious. Fortunately we have some mechanisms to overcome some of these storage issues and access information we might need.
Every transaction has a transaction log where we can store a limited amount of information more cheaply than we could on the actual blockchain. Transaction logs are not accessible from our smart contract, but after weāve developed our user facing front-end app, we will be able to read them from any Ethereum node. And because Remix emulates a node, the logs are provided for us.
To create a transaction log, we need to define an event
in the contract,
and then we can make one of our functions emit that event to the blockchain
nodes. The values used in the eventās parameters will be stored in the
transaction logs.
We usually define our events near the top of the contract, under the state variables. The basic syntax for defining an event with 3 parameters would be:
event EventName(uint256 indexed param1, bool param2, string param3);
In Solidity, there are two types of event parameter. The first type we define with our indexed keyword. By using a search filter on an indexed parameter we can find a past event on the blockchain. The other type is simply not indexed and therefore not searchable. Each event can have multiple parameters, but only three can be indexed. When you see the transaction logs in Remix, it should help it make more sense.
Each time that āsomething happensā e.g. someone calls setMessage, our function will emit the event and create a log. Thus, to emit one using the example event above we can write:
emit EventName(2, false, 'Hello World!');
Thereās a lot to unpack, so weāll leave it there for now!
Letās create an event to log all the future messages sent to our smart contract and check the fortunes to see if the sent messages are worthy of āmaking itā in the web3 world. š After our state variables, we can define our new event as:
// We add this line after our 'message' and 'messageCount' definitions event web3Future(uint256 indexed messageIndex, address indexed author, string message, string future);
Notice that our second parameter is defined with a new type: address
. We use
this type for Ethereum addresses, whether theyāre user wallet addresses, or
addresses of other smart contracts. Yes, that is too for another lesson š
Inside our setMessage
function, we should emit the event. But before that,
letās decide if the message is GMI or NGMI:
function setMessage(string calldata newMessage) public { // We leave our previous code messageCount++; message = newMessage; // ====== Here begins our new code ====== // We create a local variable in memory to decide if GMI or NGMI string memory future = 'NGMI'; // Only if the new message is 'gm', we change it to a fun response ;o) if (keccak256(abi.encodePacked(newMessage)) == keccak256(abi.encodePacked('gm'))) { future = 'WAGMI'; } // We emit the event notifying the change of our state variable emit web3Future(messageCount, msg.sender, newMessage, future); // ====== End of new code ====== }
If you were wondering what msg.sender is, we can simplify it as the Ethereum address that called the function. You also probably noticed how we compare newMessage with āgmā. Thatās because Solidity doesnāt have a way to easily compare two strings of text. But with a little bit of encoding and decoding, we can use abi.encodePacked() to convert the string into bytes and then use the hashing function keccak256() to compare the two hashes. And if they are equal, the strings are equal too š. You can be sure that weāll be deep diving on these in future lessons, so no worries!
Go touch some grass āš
Compile & Deploy
Now that we have written our smart contract in full, we can compile it and deploy it to a blockchain. Since we are using the Remix IDE, we can use its tools in the sidebar for this. Hereās a brief description of the tools icons:
- At the top, the logo links us to the Home (and help links) of Remix
- Then, we have our File Explorer
- The magnifying glass icon is for searching in files
- Highlighted in red, the Solidity Compiler (our next step)
- Highlighted in green, Deploy & Run transactions
To compile our smart contract we should click on the Solidity Compiler icon in the sidebar.
Leave all the settings in their default, manually select our contract in the drop down menu and click on the Compile WAGMI.sol button.
After compiling, we should see a green check mark on top of the Solidity Compiler sidebar icon.
Now we are ready to deploy our contract. We can now head to the Deploy & Run transactions icon in the sidebar. Below we can see a message that says āCurrently you have no contract instances to interact withā, so we havenāt deployed anything yet.
Again, we are leaving all options to default for now. Weāll be learning how to deploy to a testnet later on. Click on the Deploy button!
Once deployment finishes, we are going to see our shiny new deployed contract instance below.
Open the dropdown under Deployed Contracts, to see our function tabs:
- the blue ones are our view functions that donāt modify our state
- the orange one lets us trigger the setMessage function which will change the state. Be sure to click on the little drop down menu on the right. From here on, we can interact with our contract. Feel free to test the functions. Click on the getMessage and messageCount tabs before and after setting a new message to modify data on our contract.
From here on, we can interact with our contract. Feel free to test the functions. Click on the getMessage and messageCount tabs before and after setting a new message.
On the right hand side, below our contract code, thereās a bar on the bottom. If it hasnāt opened automatically, we can open the console from here to see any transactions with the green check mark. The first transaction is the deployment of our contract. Click it to open it.
Once you set a new message, youāll see again, that the transaction has a green check mark, open it and look for the logs. Hereās my info after setting the message to āMy first smart contractā:
If you look closely, inside the logs part, our smart contract predicted a 'future' for us.
What if you try out setting the message to gm
and look at the logs again. Can
you see the magical spell that is being cast here?
Before you go ahead and tell us what your future in web3 is, have a check on what you didnāt know a little while ago, and what you know now!
Now, go to the community in developerdao.peeranha.io to share your new Open Sourcerer powers! And afterwards get started on our Build a basic NFT lesson. See you soon!
Authors
Reviewers
georgemac510
wolovim
Additional Contributor
mveve