Solidity Notes
Solidity Resources
Overview
Solidity - High-level programming language to deploy smart contracts on EVM(Ethereum Virtual Machine). Inspired by C++, JavaScript and Python
Statically Typed, supports inheritance, libraries and complex user-defined types programming language.
Basic Syntax
pragma - tells the solidity compiler version to be used
Contract - Similar to a class
Variable Types
- Integer
- Unsigned Integer
- Bool
- Fixed Point Numbers
- address
No full support for float/double in solidity
Variables
- State Variables − Variables whose values are permanently stored in a contract storage.
- Local Variables − Variables whose values are present till function is executing.
- Global Variables − Special variables exists in the global namespace used to get information about the blockchain.
pragma solidity ^0.5.0;
contract SolidityTest {
uint storedData; // State variable
constructor() public {
storedData = 10;
}
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return storedData; //access the state variable
}
msg.sender // Global Variable
msg.value // Global Variable
}
Storage is expensive on the blockchain
Local Variables do not use any gas because they are always on memory
Variables declared inside a function are stored in stack. They are called stack memory.
String by default is stored in storage. You have to explicitly mention it to be stored on memory
Variable Types
- Constant - Has to be initialised when declared. Value cannot be changed later
- Variable - Value can be changed later
- Immutable - Does not have to be declared when initialized and the value cannot be changed later.
Constant and immutable uses lower gas units
Variable Scope
- Public − Public state variables can be accessed internally as well as via messages. For a public state variable, an automatic getter function is generated.
- Internal − Internal state variables can be accessed only internally from the current contract or contract deriving from it without using this.
- Private − Private state variables can be accessed only internally from the current contract they are defined not in the derived contract from it.
pragma solidity ^0.5.0;
contract C {
uint public data = 30;
uint internal iData= 10;
function x() public returns (uint) {
data = 3; // internal access
return data;
}
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data(); //external access
}
}
contract D is C {
function y() public returns (uint) {
iData = 3; // internal access
return iData;
}
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return storedData; //access the state variable
}
}
Loops
- While
- Do While
- For Loop
- Loop control - break, continue
Decision Making
- If
- if else
- if else if
Strings
- string
- bytes32
bytes32 is usually preferred because it uses less gas
pragma solidity ^0.5.0;
contract SolidityTest {
string data = "test";
bytes32 new_data = "new_test";
}
// bytes32 - string conversion
bytes memory bstr = new bytes(10);
string message = string(bstr);
Arrays
Static Array - type arrayName [ arraySize ];
uint balance[10];
Dynamic Array - type[] arrayName;
Initializing Array
uint balance[3] = [1, 2, 3]; // Declare and intialize an array
uint balance[] = [1, 2, 3]; // Declare and initialize an array
balance[2] = 5; // set a value for an elemnt in an array
Fixed length array
// declaring a fixed-size array of type uint with 3 elements
uint[3] public numbers = [2, 3, 4];
// declaring fixed-size arrays of type bytes
bytes1 public b1;
bytes2 public b2;
bytes3 public b3;
//.. up to bytes32
Dynamic Length Array
contract DynamicArrays{
// dynamic array of type uint
uint[] public numbers;
// returning length
function getLength() public view returns(uint){
return numbers.length;
}
// appending a new element
function addElement(uint item) public{
numbers.push(item);
}
// returning an element at an index
function getElement(uint i) public view returns(uint){
if(i < numbers.length){
return numbers[i];
}
return 0;
}
// removing the last element of the array
function popElement() public{
numbers.pop();
}
function f() public{
// declaring a memory dynamic array
// it's not possible to resize memory arrays (push() and pop() are not available).
uint[] memory y = new uint[](3);
y[0] = 10;
y[1] = 20;
y[2] = 30;
numbers = y;
}
}
Members
- length − length returns the size of the array. length can be used to change the size of dynamic array be setting it.
- push − push allows to append an element to a dynamic storage array at the end. It returns the new length of the array.
pragma solidity ^0.5.0;
contract test {
function testArray() public pure{
uint len = 7;
//dynamic array
uint[] memory a = new uint[](7);
//bytes is same as byte[]
bytes memory b = new bytes(len);
assert(a.length == 7);
assert(b.length == len);
//access array variable
a[6] = 8;
//test array variable
assert(a[6] == 8);
//static array
uint[3] memory c = [uint(1) , 2, 3];
assert(c.length == 3);
}
}
Strings vs Bytes
- Bytes will be stored as hexadecimal while string will be stored as utf-8.
- You can push to bytes but you can't push to a string
- bytes1.push('x') - valid
- string1.push('x') - invalid
- You can return an index from a bytes variable but you can't do it with a string.
- bytes1[i] - valid
- string1[i] - invalid
- You can return the length of a bytes variable but you can't return the length of a string.
- bytes1.length - valid
- string1.length - invalid
Constructor
- Special function that is executed just once when the contract is initialised either by another contract or when deployed.
contract Base {
uint data;
constructor(uint _data) public {
data = _data;
}
}
Structs
- It is a complex data type that consists of elementary data types
- We use to represent a single thing that has various properties like car, person, etc
- Struct by default is saved in storage even if declared inside a function.
struct Instructor{
uint age;
string name;
address addr;
}
contract Academy{
// declaring a state variabla of type Instructor
Instructor public academyInstructor;
// initializing the struct in the constructor
constructor(uint _age, string memory _name){
academyInstructor.age = _age;
academyInstructor.name = _name;
academyInstructor.addr = msg.sender;
}
// changing a struct state variable
function changeInstructor(uint _age, string memory _name, address _addr) public{
if (academyState == State.Open){
Instructor memory myInstructor = Instructor({
age: _age,
name: _name,
addr: _addr
}
);
academyInstructor = myInstructor;
}
}
}
Enums
- Enums are used to create user-defined types
- They are explicitly convertible to and from integer
// declaring a new enum type
enum State {Open, Closed, Unknown}
// declaring and initializing a new state variable of type State
State public academyState = State.Open;
Mapping
- Solidity mapping is similar to python dictionaries. Key, value pair
- All keys must be of the same type and all values must be of the same type.
- Values can be any data type but keys cannot be mapping, dynamic array, struct, enums.
- Mapping is always stored in storage regardless of where it is initialized. Mappings are state variables.
- Cannot iterate through a mapping
contract Auction{
// declaring a variable of type mapping
// keys are of type address and values of type uint
mapping(address => uint) public bids;
// initializing the mapping variable
// the key is the address of the account that calles the function
// and the value the value of wei sent when calling the function
function bid() payable public{
bids[msg.sender] = msg.value;
}
}
Storage vs Memory
Storage is call by reference. Memory is call by value
// Storage changes the state of the blockchain
contract A{
string[] public crypto= ['BTC', 'ETH', 'BNB'];
function myFunction() public{
string[] storage s = crypto;
s[2] = 'XMR';
}
// Memory does not change the state of the blockchain
contract A{
string[] public crypto= ['BTC', 'ETH', 'BNB'];
function myFunction() public{
string[] memory s = crypto;
s[2] = 'XMR';
}
Built in Global Variables
- msg - Contains info about the account that makes the generates the transaction and also about the transaction.
- msg.sender - account address that generated the transaction
- msg.value - eth value sent to the contract (wei)
- msg.gas - remaining gas
- msg.data - data field from the transaction or call that executed this function
- this - The current contract
- gasleft() - Shows the amount of gas that can still be consumed by the function
contract GlobalVars{
// the current time as a timestamp (seconds from 01 Jan 1970)
uint public this_moment = block.timestamp; // `now` is deprecated and is an alias to block.timestamp)
// the current block number
uint public block_number = block.number;
// the block difficulty
uint public difficulty = block.difficulty;
// the block gas limit
uint public gaslimit = block.gaslimit;
address public owner;
uint public sentValue;
constructor(){
// msg.sender is the address that interacts with the contract (deploys it in this case)
owner = msg.sender;
}
function changeOwner() public{
// msg.sender is the address that interacts with the contract (calls the function in this case)
owner = msg.sender;
}
function sendEther() public payable{ // must be payable to receive ETH with the transaction
// msg.value is the value of wei sent in this transaction (when calling the function)
sentValue = msg.value;
}
// returning the balance of the contract
function getBalance() public view returns(uint){
return address(this).balance;
}
Get ether to contract address
There are 2 ways to receive ETH to a contract address
- If the contract has receive() or the fallback() functions, ETH can be directly sent to the contract address from externally owned accounts.
- By calling the payable function and sending ETH along with the transaction.
contract Deposit{
// either receive() or fallback() is mandatory for the contract to receive ETH by
// sending ETH to the contract's address
// declaring the receive() function that is executed when sending ETH to the contract address
// it was introduced in Solidity 0.6 and a contract can have only one receive function,
// declared with this syntax (without the function keyword and without arguments).
receive() external payable{
}
// declaring a fallback payable function that is called when msg.data is not empty or
// when no other function matches
fallback() external payable {
}
// ether can be send and received by the contract in the trasaction that calls this function as well
function sendEther() public payable{
}
// returning the balance of the contract.
function getBalance() public view returns (uint) {
// this is the current contract, explicitly convertible to address,
// and balance is a member of any variable of type address.
return address(this).balance;
}
Sending ETH to an address from the contract
function transferEther(address payable recipient, uint amount) public returns(bool){
// checking that only contract owner can send ether from the contract to another address
require(owner == msg.sender, "Transfer failed, you are not the owner!!");
if (amount <= getBalance()){
// transfering the amount of wei from the contract to the recipient address
// anyone who can call this function have access to the contract's funds
recipient.transfer(amount);
return true;
}else{
return false;
}
}
Variable and Function Visibility
- Public - Can be called both internally(by the contract) and externally(by Externally Owned Accounts EOA)
- Private - Can only be accessed by the contract where the function or variable is defined in,
- Internal - They can be accessed by both the contract where it is defined and the contracts that inherit the contract where it is defined.
- External - It can only be called by other contracts or by EOA
Interface
- cannot have any functions implemented
- can inherit from other interfaces
- all declared functions must be external
- cannot declare a constructor
- cannot declare state variables
Misc
Pure - The function does not read or modify state variables
View - These are read-only functions that do not modify the state variables
Lottery Smart Contract
pragma solidity >=0.5.0 <0.9.0;
contract Lottery{
// declaring the state variables
address payable[] public players; //dynamic array of type address payable
address public manager;
// declaring the constructor
constructor(){
// initializing the owner to the address that deploys the contract
manager = msg.sender;
}
// declaring the receive() function that is necessary to receive ETH
receive () payable external{
// each player sends exactly 0.1 ETH
require(msg.value == 0.1 ether);
// appending the player to the players array
players.push(payable(msg.sender));
}
// returning the contract's balance in wei
function getBalance() public view returns(uint){
// only the manager is allowed to call it
require(msg.sender == manager);
return address(this).balance;
}
// helper function that returns a big random integer
function random() internal view returns(uint){
return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players.length)));
}
// selecting the winner
function pickWinner() public{
// only the manager can pick a winner if there are at least 3 players in the lottery
require(msg.sender == manager);
require (players.length >= 3);
uint r = random();
address payable winner;
// computing a random index of the array
uint index = r % players.length;
winner = players[index]; // this is the winner
// transferring the entire contract's balance to the winner
winner.transfer(getBalance());
// resetting the lottery for the next round
players = new address payable[](0);
}
}