Introduction
Ethereum is a decentralized, open-source blockchain with smart contract functionality. Ether is the native cryptocurrency of the platform. After Bitcoin, it is the largest cryptocurrency by market capitalization.Ethereum has its own programming language, called Solidity. As a blockchain network, Ethereum is a decentralized public ledger for verifying and recording transactions.
Basics of Solidity and Ethereum
Variables and Math operations
Solidity is a statically typed language, which means that variable's datatype must be specified upon declaration.
There are 3 types of variables in Solidity
- State Variables : values permanently stored in contract storage
- Local Variables : values stored in memory, impermanently. Declared inside a function
- Global Variables : special variables which exists in global namespace
Global Variables in Solidity
blockhash(uint blockNumber) returns (bytes32) : Hash of the given block - only works for 256 most recent, excluding current, blocks
block.coinbase (address payable): Current block miner's address
block.difficulty (uint): Current block difficulty
block.gaslimit (uint): Current block gaslimit
block.number (uint): Current block number
block.timestamp (uint): Current block timestamp as seconds since unix epoch
gasleft() returns (uint256): Remaining gas
msg.data (bytes calldata): Complete calldata
msg.sender (address payable): Sender of the message (current caller)
msg.sig (bytes4): First four bytes of the calldata (function identifier)
msg.value (uint): Number of wei sent with the message
now (uint): Current block timestamp
tx.gasprice (uint): Gas price of the transaction
tx.origin (address payable): Sender of the transaction
Storage vs Memory
In solidity there are two locations to store variables, in storage and in memory. Memory in Solidity is a temporary place to store data whereas Storage holds data between function calls. The Solidity Smart Contract can use any amount of memory during the execution but once the execution stops, the Memory is completely wiped off for the next execution. Whereas Storage on the other hand is persistent, each execution of the Smart contract has access to the data previously stored on the storage area.
It is always better to use Memory for intermediate calculations and store the final result in Storage.
- State variables and Local Variables of structs, array are always stored in storage by default.
- Function arguments are in memory.
- Whenever a new instance of an array is created using the keyword ‘memory’, a new copy of that variable is created. Changing the array value of the new instance does not affect the original array.
Storing time in solidity
Solidity provides some native units for dealing with time.
The variable now
will return the current unix timestamp of the latest block (the number of seconds that have passed since January 1st 1970).
Solidity also contains the time units seconds
, minutes
, hours
, days
, weeks
and years
. These will convert to a uint
of the number of seconds in that length of time. So 1 minutes
is 60
, 1 hours
is 3600
(60 seconds x 60 minutes), 1 days
is 86400
(24 hours x 60 minutes x 60 seconds), etc.
Here's an example of how these time units can be useful:
uint lastUpdated;
// Set `lastUpdated` to `now`
function updateTimestamp() public {
lastUpdated = now;
}
// Will return `true` if 5 minutes have passed since `updateTimestamp` was
// called, `false` if 5 minutes have not passed
function fiveMinutesHavePassed() public view returns (bool) {
return (now >= (lastUpdated + 5 minutes));
}
Variable conversions in solidity
...
Exercise:
What will be the value in the numbers array after this code has run once?
contract helloGeeks
{
int[] public numbers;
function Numbers() public
{
numbers.push(1);
numbers.push(2);
int[] memory myArray = numbers;
myArray[0] = 0;
}
}
Write a function for generating random number in solidity using global variables
Data Structures
Structs
Array
dynamic and fixed, public array gets their own getter functions
Array of structs
add to array
uint[] public numbers;
numbers.push(12)
getting length of an array
uint length_of_array = numbers.push(99)
Mappings
key-value store for storing and retrieving data
mapping (address => uint) luckyNumber;
function setMyNumber(uint _myNumber) public {
luckyNumber[msg.sender] = _myNumber;
}
function myNumberIs() public view returns (uint) {
// Retrieve the value stored in the sender's address
// Will be `0` if the sender hasn't called `setMyNumber` yet
return luckyNumber[msg.sender];
}
When to use an array and when to use a mapping ?
Use an array to iterate over objects and use a mapping for quick lookups.
Addresses in Ethereum
The Ethereum blockchain is made up of accounts, which you can think of like bank accounts. An account has a balance of Ether (the currency used on the Ethereum blockchain), and you can send and receive Ether payments to other accounts, just like your bank account can wire transfer money to other bank accounts.
Each account has an address, which you can think of like a bank account number. It's a unique identifier that points to that account, and it looks like this:
retrieving address inside a function using a global variable msg.sender
Functions
public and private
Internal and External
internal is the same as private, except that it's also accessible to contracts that inherit from this contract.
external 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.
Pure and View
Pass arguments by value vs passing reference
Constructors
Special function that has the same name as the contract. It will get executed only one time, when the contract is first created.
Function Modifiers:
Modifiers are kind of half-functions that are used to modify other
functions, usually to check some requirements prior to execution.
<modifier example>
Working with Events
//declare the event
event AdditionEvent(uint a, uint b, uint sum);
//trigger the event
function add(uint _a, uint _b) public returns(uint){
uint sum = _a + _b;
// fire the event
emit AdditionEvent(_a, _b, sum);
return sum;
}
//listen to the event
MyContract.AdditionEvent(function(error, result){
console.log(result);
});
event Transfer(address indexed from, address indexed to, uint256 value);
Q What does the "indexed" keyword do in the below line of code? I'm guessing it just tells the event object that the following input should be logged?
The indexed parameters for logged events will allow you to search for these events using the indexed parameters as filters.
Q Can we use it other places ie outside of events?
The indexed keyword is only relevant to logged events.
Require in solidity
Revert in solidity
Working with web3.js
Exercise : Write a function to compare two strings in solidity.
function sayHiToVitalik(string memory _name) public returns (string memory) {
require(keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("Vitalik")));
return "Hi!";
}
Contracts
Inheritance
contract Groot {
function catchphrase() public returns (string memory) {
return "I am groot";
}
}
contract BabyGroot is Doge {
function anotherCatchphrase() public returns (string memory) {
return "I am smol groot";
}
}
Multifile Inheritance
import "./Groot.sol";
contract BabyGroot is Groot {
...}
Interacting with another contracts by declaring an interface
// sample contract luckynumber.sol
contract LuckyNumber {
mapping(address => uint) numbers;
function setNum(uint _num) public {
numbers[msg.sender] = _num;
}
function getNum(address _myAddress) public view returns (uint) {
return numbers[_myAddress];
}
}
// declaring inheritance in another contract MyContract.sol
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
// using the above declared interface
contract MyContract {
address NumberInterfaceAddress = 0xab38...
// ^ The address of the FavoriteNumber contract on Ethereum
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
// Now `numberContract` is pointing to the other contract
function someFunction() public {
// Now we can call `getNum` from that contract:
uint num = numberContract.getNum(msg.sender);
// ...and do something with `num` here
}
Gas optimization tips
Struct Packing to save gas
struct options{
uint amount;
uint price;
uint expiry;
bool exercised;
}
uint defaults to uint256 , and bool is uint8 in solidity
so the above struct occupies 4 storage slots of size 256bits each.
struct options{
uint128 amount;
uint64 price;
uint64 expiry;
bool exercised;
}
After some modifications we are able to pack the amount and price variable as well as the expiry and exercised variable together. So that the options struct now occupies only 2 storage slots.
Further optimization is possible
struct options{
uint64 amount;
uint56 price; // <-----------
uint64 expiry;
bool exercised;
}
By changing the price variable from uint64 to uint56 we are able to pack the whole struct in just one storage space since 64+56+64+8 = 256
uint56 is more than enough for price, as it can fit numbers from 0 to 2**56 - 1.
So now the options struct only costs one storage slot worth of gas.
Security Best Practices
An important security practice is to examine all your public
and external
functions, and try to think of ways users might abuse them. Remember — unless these functions have a modifier like onlyOwner
, any user can call them and pass them any data they want to.
Solidity Gotchas
compile learnings from interview questions etc
underflow and overflow in solidity
Source
https://docs.soliditylang.org/en/v0.5.5/structure-of-a-contract.html#:~:text=State%20variables%20are%20variables%20whose,State%20variable%20%2F%2F%20...%20%7D
https://www.tutorialspoint.com/solidity/solidity_types.htm
https://www.bitdegree.org/learn/solidity-types
https://www.geeksforgeeks.org/solidity-types/