Hi friends, welcome!
In this first post, I’ll want to share some layouts for contracts and common designs that I learn in the Solidity documentation. It's nice developers adopt when writing their smart contracts.
And why should I care?
First, you know most contracts receive money. I hear from Alex Van de Sande when I was in the EthereumRio Bootcamp that every new design for Solidity contracts was because someone loses a lot of money stuck inside the contract.
This problem involves more writing the code than the style guide, but writing neatly makes it easier to find things you don't want to happen.
Remember, smart contracts are an empty paper, you need to tell him every little thing you want him to do and what not to do. (Don't give free money to bots!)
Second, it’s nice to write clean code, which anyone can take, read, and understand. Hey, we are in an open-source world! It’s not too hard to keep clarity and make it easy for new people to come.
Lastly, according to solidity official docs, this style guide is not about “the right” or “the best” way to write smart contracts, is all about consistency.
Look at this note from Solidity Docs:
"Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important.
But most importantly: know when to be inconsistent – sometimes the style guide just doesn’t apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don’t hesitate to ask!"
Very inspiring huh? So let’s start.
Basic Structure of a Contract:
Every contract will have these 3 things:
- SPDX – license: Every source file should start with a comment indicating its license. You can l find more information about licenses here.
- pragma version: Enable certain compiler features or checks, it just instructs the compiler to check whether its version matches the one required by the pragma. Here you specify the compiler version to be used for the current Solidity file. If it does not match, the compiler issues an error.
- Contract: Contain all the data in state variables and functions that can modify these variables. Basically the heart and brain of a smart contract.
Now you know the basic struct, but contracts can have too: import, interfaces, and libraries. We go more deeply in other posts about it.
Inside Contracts
Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, Errors, Struct Types, and Enum Types. Furthermore, contracts can inherit from other contracts. Lots of things huh?
It’s nice to organize a contract in this order:
- State Variables
- Events
- Function Modifiers
- Struct, Arrays, or Enums
- Constructor
- Fallback function
- External visible functions
- Public visible functions
- Internal visible functions
- Private visible functions
This is an example:
This is a consistent structure of a basic contract! In the code comments, I also put the name styles to avoid confusion. But now, let’s put some code inside the functions and see some more styles:
Let's comment each line.
But for now, without many details about every little type, there will be other more detailed posts.
Here we declare, inside the contract, a few state variables of different types. You can see an address, boolean, string and uint256.
I initialized the value of price
with 0.01 ether
, and the BLOG_NAME
with Ian's road to Web3.
In this line, we declare an event
called newMember
and when it is called, store their arguments sender
and nickname
on the blockchain.
The modifier function onlyOwner
requires the EOA (Externally Owned Account) who wants to use (msg.sender
) have an address equivalent to the owner we have created. If the requirements don’t be attended to, then the function reverts the transaction and throws a message “Only owner”. And if attend, the underscore tells the contract to execute the rest of the code.
Inside the struct
we create a group of state variables, a name
, wallet
, and balance
. Right below we create an array of this struct, so we can save which User
.
A mapping type uses the syntax key => value. Inside the isMember
, we will pass an address and the mapping will return us if is a member or not.
Constructor function is executed only one time when the contract is created, and can’t be called afterward. Inside the constructor, we passed a value for the state variable owner, the EOA who create the contract (msg.sender
) will be de owner, so he will have the permission to call functions that have the modifier function onlyOwner
.
The receive
function is the way you can send ether to this contract without sending any data or via the payable function.
Now the functions :D
1º
The first external function called setMember
receives a parameter _name
of type string. The memory
word specifies the location of the data. This is also payable
, so you can send ether to the contract via this function.
The first line requires a condition of the value sent, by calling this function, to be more or equal to the variable price (0.01 ether
). If low, reverts the transaction and throws "insufficient funds", and if attends the minimum value then execute the rest of the function.
The next line, adds the msg.sender
to the isMember
mapping. Now, the contract knowsmsg.sender
is a member.
Next, the new member is added to the users
array with the information from the User
struct.
Finally, is emitted to the blockchain the address
of the new member and the name
he chose.
2º
The second external function stopMessages
can only be called by the conditions specified by the modifier function onlyOwner
. Inside we call another function pause()
, below we will see what this function does, but you can imagine by the name hehe.
3º
Did you note how the style of this function was written? We put the public
and returns
keywords right below the function. This is the style indicated for long-named functions by the solidity documentation.
The sendMessage()
is a public function, any EOA or contract can access this function. It receives a parameter _message
and returns a string. The first line requires that the variable paused
be false to execute the next lines, otherwise it reverts. In the next line we put the value of _message
on the message
, and the next, call addMessage()
that will see below. And finally returns a message with the new value.
4º
The addMessage()
is an internal function, and can only be called inside this contract or inside contracts that inherit this contract. When it's called, increments the totalMessages
variable. (that's why we used this function on the third line of sendMessage
function).
Finally, the private function pause()
, it's can only be called inside this contract. When the owner calls the stopMessage()
function, pause()
is called, and change the value of the pause to true. This will stop the execution of sendMessage()
function because the condition will not be satisfied.
Did you notice any vulnerability or inconsistency within the contract? No?
Maybe you'll look again and find it because I purposely left some loopholes that can disrupt its functioning, you can comment on them below (:
So we're done here!
Today we learned a little more about structuring a smart contract and its writing styles for the different types of variables and keeping it well organized so that it is easy for everyone to understand. If you liked it and found it useful, share it with your solidity beginner dev group!
See you in the next post!