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!
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...
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.
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(^
).
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 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; }
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 messageand 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 Variables and 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.
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.…. Definition of parameter here….or in the Indexed Events
Parameter dropdown.
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 also 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, so with a little bit of encoding and decoding, we are using abi.encodePacked() to convert the string into bytes and then using 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!
We should ask the learner to sum up what they have learned. Some checkpoint questions:
Addresses: Apart from a user wallet, what else uses a blockchain (Ethereum) address?
Events: How many parameters can we have in an event?
Searching the chain: What can we use to find a past event?
Transactions: Ethereum uses something for transaction fees. What’s it called?
Logs: Where do the values for event parameters get stored?
Versions: What is the use of the pragma solidity statement in our smart contract?
State variables: Do state variables stay permanently on the blockchain?
Visibility: Can a view function modify the state of the blockchain?
Use cases of smart contract: What can we use a smart contract for?
see the Comment in the very last line of this lesson under ‘Now, please go ahead and hop in the forum/discord to tell us: what is your future in web3?’
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 red, the Solidity Compiler (our next step)
To compile our smart contract we should click on the Solidity Compiler icon in the sidebar (marked in the red box).
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 (marked in the green box). 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:
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.ons 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! *
Apart from a user wallet, what else uses a blockchain (Ethereum) address?
How many parameters can we have in an event?
What can we use to find a past event?
Ethereum uses something for transaction fees. What’s it called?
Where do the values for event parameters get stored?
What is the use of the pragma solidity statement in our smart contract?
Do state variables stay permanently on the blockchain?
Can a view function modify the state of the blockchain?
What can we use a smart contract for?
Now, go to the community in Discord to share your new Open Sourcerer powers......and find the answers too!!!
Developer DAO Foundation © 2023
Website content licensed under CC BY-NC 4.0.
Website code is licensed under MIT.