Solidity
Version Pragma
All solidity source code should start with pragma keyword, telling the code which Solidity compiler version should be used.
pragma solidity >=0.5.0 <0.6.0;Contracts
Solidity's code is encapsulated in contracts.
contract HelloWorld {
// Code goes here
}Libraries
Similar to contracts, but stateless (no storage, so no state variables);
Used for code reuse;
Declared with
librarykeyword.
library StringUtils {
struct String {
bytes32 value;
uint256 length;
}
function concat(String memory _self, String memory _other) internal pure returns (String memory) {
String memory result = String({
value: _self.value,
length: _self.length
});
for (uint256 i = 0; i < _other.length; i++) {
result.value |= bytes32(uint256(_other.value) * (2 ** (8 * (i + _self.length))));
result.length++;
}
return result;
}
}Deployed libraries
If a library has at least one public or external function, it must be deployed to the blockchain;
Function calls from contract to library use the delegatecall opcode, which means that the library's code is executed in the context of the contract (execute library's code with contract's storage & state). For instance, it keeps the same
msg.senderandmsg.valuevalues;Contracts using deployed libraries have a placeholder for the library address in their bytecode after compilation. Example of a placeholder:
__$30bbc0abd4d6364515865950d3e0d10953$__;To replace the placeholder, deployed libraries must be linked to the contract. This process can be done by frameworks like Truffle, Hardhat or Foundry, or by using the solc compiler. Example of a deployment with linkage using Foundry:
Inlined (or embedded) libraries
If a library has no public or external functions, it can be inlined (included) in the contract's bytecode, meaning that it will not be deployed;
Function calls from contract to library use the JUMP opcode (like normal function calls);
Those libraries are inlined for efficiency (cheaper gas fees) & simplicity (no need to link libraries to contracts);
For size efficiency, only the used functions of the library are embedded in the contract importing it.
Variables data location
storage variables are stored permanently on the blockchain. State variables (variables declared outside of functions) are by default storage. However, those variables are really expensive in gas fees.
memory variables are temporary. They're erased after exiting a local scope (eg: a function). Those are called local variables.
calldata is similar to memory, but it's only available to external functions.
Types
Value types
Integers
int/uint: Signed & unsigned integers of 256 bits.
(u)int8 to (u)int256 in steps of 8 are for specifying the numberof bits to use.
Addresses
Types holding a 20 byte value (size of an Ethereum address).
address: for an address that cannot be sent Ether.
address payable: for an address that can receive Ether.
Reference types
Data location
Every reference type has a data location, either calldata, memory or storage.
Calldata
Behaves mostly like memory.
Only valid for parameters of external functions.
Non-modifiable variables.
Memory
Not written to the blockchain.
Must be defined inside a function or as a function parameter.
Destroyed after exiting the function.
Local scope only.
Storage
Written to the blockchain (persistent).
Accessible from anywhere (inside or outside the smart contract).
Global variables are by default storage variables.
Incur gas fees.
Arrays
There are two types of arrays:
Fixed:
Dynamic:
To add an element to an array, we use de method push():
Strings
Arbitrary-length UTF-8 data.
Structs
Structs let us create complex data types with multiple properties.
Mapping types
Iterable mappings
A mapping is a key-value store.
Math operations
Same as most of the programming languages:
Addition: x + y
Subtraction: x - y
Multiplication: x * y
Division: x / y
Modulus: x % y
Power: x**y
Conditions
if/else
require()
Check if condition is true, in order to execute the rest of the code. If not, it will throw an error, stop executing in the local scope and refund the user the rest of the gas.
assert()
Similar to require, but will not refund the user in case of error. It is typically used when something has gone horribly wrong with the code (like a uint overflow).
Loops
for loops:
Visibility
private keyword let a source code element to be callable within the same contract only.
public keyword let an element of the source code to be callable for any other contract (functions are public by default).
internal keyword is the same as private, except that it's also accessible to contracts that inherit from this contract.
external keyword is similar to public, except that these functions can ONLY be called outside the contract — they can't be called by other functions inside that contract.
Functions
Functions are declared with the function keyword:
Two ways to pass an argument:
By value: original variable value not changed (with memory keyword).
By reference: original variable value changed.
memory keyword is required for all reference types such as arrays, structs, mapping & strings.
It's convention (but not required) to start function parameter variable names with an underscore (_) in order to differentiate them from global variables.
Return values
To return a value from a function, we need the returns keyword in the declaration, specifying the return type. Moreover, we need return (without the 's') in the function:
Note that in Solidity, functions can return multiple values:
In this case, we can handle multiple return values like this:
Function modifiers
view: specifies that a function is only viewing the data but not modifying it.
If a view function is called externally (with the external visibility keyword), it doesnt' cost any gas. However, an internally view function will still cost gas because the other function creates a transaction that will need to be verified from every Ethereum node.
pure: specifies that a function is not even accesing any data in the app:
payable: allows a function to receive ether:
custom modifiers with modifier keyword: you can create your own function modifier. It can take parameters.
One of the most common use-cases is to add a quick require check, that will be executed before a function executes.
The "_;" in the onlyOwner modifier is what tells the code to execute the function at a given time (in this eg: renounceOwnership()).
Function overriding
Override a base function by inheriting from a parent contract.
Base function must be marked as virtual.
Overriding function must have the same function signature as the base function.
Overriding function must specify which parent contract it is overriding from.
Function signature
First 4 bytes of the keccak-256 hash of the concatenation of the function name & its ordered list of parameters types;
AKA function selector.
Type casting
Let us convert between data types:
Events
Allow for logging and listening to contract activities;
Can be indexed with up to 3
indexedparameters (topics);Non-indexed parameters are event's data;
Emitted events can be filtered by topics.
Inheritance
Inheritance let our code to be more manageable and organize. We can create "sub"-contracts which have access to the inherited contract's content.
In this exemple, BabyDoge inherits from Doge, so if we use it, we will have access to both catchphrase() and anotherCatchphrase(). We can also inherit from multiple contracts at once:
Import
For a better codebase structure, we can divide our Solidity code in multiple files. In order to link a file with another, we use the import keyword.
Interfaces
In order to interact with external contracts, we need to define an interface, which is just a contract inside our code, with the functions we want by writing their declaration only, and a quick setup:
External contract:
Our code:
Comments
Comments in Solidity are just like JS or C ones. We can use // for single line comments or /* */ for multi line comments.
However, Solidity use a special form of comments to provide rich documentation, NatSpec, which was inspired by Doxygen. To differenciate those type of comments from the normal ones, we use ///.
Tags
@title
A title that should describe the contract/interface
Contract, library, interface
@author
The name of the author
Contract, library, interface
@notice
Explain to an end user what this does
Contract, library, interface, function, public state variable, event
@dev
Explain to a developer any extra details
Contract, library, interface, function, state variable, event
@param
Documents a parameter just like in Doxygen (must be followed by parameter name)
Function, event
@return
Documents the return variables of a contract’s function
Function, public state variable
@inheritdoc
Copies all missing tags from the base function (must be followed by the contract name)
Function, public state variable
@custom:...
Custom tag, semantics is application-defined
Everywhere
Notable variables
msg.sender (address)
Global variable available to all functions. It refers to the address of the person (or smart contract) who called the current function.
block.timestamp (uint256)
Returns current timestamp of the latest block (number of seconds that have passed sicne January 1st 1970).
Solidity also has time units (seconds, minutes, hours, days, weeks & years). Each of them is converted to seconds (1 minutes = 60 seconds, ...)
Notable functions
keccak256(bytes memory) returns (bytes32)
Returns a 32 byte hash of memory.
Note that our string needs to be converted to binary. That's why we use the abi.encodePacked() method.
payable address.transfer(value)
Transfers eth stored in the contract (value) to a payable address (object).
Notable contracts
Ownable
OpenZeppelin contract which provides a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions.
Notable libraries
SafeMath
OpenZeppelin library which provides mathematical functions that protect your contract from overflows and underflows.
Optimization
In order to execute a function, users have to pay gas fees. The gas cost is based on how much computing resources will be required to perform an operation. That's why code optimization is really important in Ethereum.
Gas has been made in order to avoid people clogging up the network with infinite loops, or hogging all the network resources with really intensive computation.
Struct packing for gas saving
Normally, Solidity reserves 256 bits of storage regardless of the type size, except inside structs: use smallest sized types possible and cluster identical data types together:
Good to know
Token burning is when a token is sent to address 0, making it unrecoverable. It can be used for raising the value of a token, but there is many more reasons behind.
Last updated