description |
---|
10-15-22 |
{% embed url="https://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.7+commit.e28d00a7.js" %}
- Solidity is used to develop smart contracts that run on Ethereum
Well, what is a smart contract?
- A program that runs on the Ethereum Blockchain
- It can have a balance
- Capable of handling transactions
- Commonly used to ensure payment
- Impose penalties in certain situations
- While reviewing source code, you will commonly see "
pragma solidity >=0.4.0 <0.7.0
;
" - This is known as a pragma
- A pragma contains a set of instructions for compilers about how to treat the source code
- A contract in the sense of Solidity is a collection of code and data that resides at a specific address on the Ethereum blockchain
- Blockchain -> Smart Contract -> Address; Hash of Smart Contract
- First, you must include a license (comment)
Example:
// SPDX-License-Identifier: MIT
Stacked License Example:
// SPDX-License-Identifier: Apache-2.0 OR MIT
Unlicensed Example:
// SPDX-License-Identifier: UNLICENSED
- Next, we need to declare the compiler version
- A.K.A. the pragma
- YOU MUST END all of your statements with SEMI-COLONS ;
Example:
Declare different solidity versions for our compiler
0.7.0 is our minimum and 0.9.0 cannot be surpassed in this example
pragma solidity >= 0.7.0 < 0.9.0;
Your code should now be looking something like this:
// SPDX-License-Identifier: MIT
// Declare the versions of the Solidity compiler
pragma solidity >= 0.7.0 < 0.9.0;
// Used to convert uint to string
import "@openzepplin/contracts/utils/Strings.sol";
- uint? Unsigned integer
- Now it is time to start making a contract!
contract TestContract{
bool public canVote = true;
}
bool
- Remember, booleans can only be true or false
int
uint
string
contract TestContract{
bool public canVote = true;
int private myAge = 47;
uint internal favNum = 3;
string myName = "Hacker";
}
- Floats or Floating Point Numbers are not used in Solidity because it is a financial programming language
- Floats are not very accurate as they represent a range of numbers
- Financial data needs to be very accurate for obvious reasons
- Therefore, you would need to use an outside library if floats needed to be used
- What is a constructor
- Used to initialize variables
Function format:
function funcName(parameterList) scope returns() {statements}
// SPDX-License-Identifier: MIT
// Declare the versions of the Solidity compiler
pragma solidity >= 0.7.0 < 0.9.0;
// Used to convert uint to string
import "@openzepplin/contracts/utils/Strings.sol";
contract TestContract{
bool public canVote = true;
int private myAge = 47;
uint internal favNum = 3;
string myName = "Hacker";
//
constructor() {}
// function funcName(parameterList) scope returns() {statements}
function getSum(uint _num1, uint _num2) public pure returns(uint){
uint _mySum = _num1 + _num2;
return _mySum;
}
}
// SPDX-License-Identifier: MIT
// Each source file begins by identifying its license
// No license : UNLICENSED
// GNU : GPL-2.0-only
// Apache or MIT : Apache-2.0 OR MIT
// A Blockchain is essentially a database that stores transactions.
// When we add to the blockchain that transaction is timestamped.
// We can only add to the blockchain and not delete, but we can
// update sort of. When you update however we do so using new
// hashes or addresses.
// Each block contains data, the hash for the block and the hash for
// the previous block. The hash for the previous block is the chain
// part of a blockchain.
// A Hash is unique and is used to identify a block and its content
// Blockchains are secured from tampering by slowing down the creation
// of new blocks. Also the blockchain is shared by many entities and
// if a new block is added it is verified by everyone that shares
// that blockchain.
// Ethereum Virtual Machine : Runtime environment for smart contracts
// Smart contracts are isolated and have limited access to other contracts
// When we make a smart contract and deploy it it gets an account
// The address of a contract is determined when the contract is created
/* If you click Compile and then Deploy we see information about the contract
on the blockchain */
// You can add multiple plugins on the left
// Declare the versions of the Solidity compiler that work
// with our code
pragma solidity >= 0.7.0 < 0.9.0;
// Used to convert uint to string
import "@openzeppelin/contracts/utils/Strings.sol";
contract TestContract{
// ----- VARIABLES -----
// Begin with a letter or underscore and can also contain numbers
// State : Values are permanently stored in contract storage and is available
// to all functions in the contract
// Local : Values available only in the function in which they are defined
// Global : Provide info about the blockchain and are built into Solidity
// A Private variable can only be called within the contract
// ----- DATA TYPES -----
// Booleans are true or false
// State variable that is accessible outside of the contract because
// of public
bool public canVote = true;
// Integers store signed and unsigned whole numbers
// Private so can only be accessed in the contract
int private myAge = 47;
// Unsigned Integers (You Ints) start as uint8 and increase to uint256
// in increments of 8
// You also have uint8, uint16, uint32, ...
// An internal variable can only be accessed in the contract
// or by contracts that inherit from this one
uint internal favNum = 3;
// If you want to use numbers bigger than 256 bits, or floating
// points you have to emulate them. There are libraries that
// do but they lack standardized number formats
// Because Solidity concerns itself with finance values must
// be exact and since floats aren't exact we can't use them
// as we do with other languages
// Strings
string myName = "Derek";
// Used to initialize contract variables (More on them later)
constructor(){}
// ----- CASTING UINT & BYTES -----
// We want to reduce our computational expenses by using
// the correctly sized uint
// A uint is 256 bit unsigned integer by default
// uint 256 : Max size 1.15792089 x 10^77
// uint8 : 2^8 - 1 : 255
// uint16 : 2^16 - 1 : 65,535
// uint32 : 2^32 - 1 : 4,294,967,295
// We convert by putting the value to convert inside parentheses
// with the type to convert to before
uint toBig = 250;
uint8 justRight = uint8(toBig);
// ----- ETHER UNITS -----
// Ether is the currency of the Ethereum blockchain
// Wei is the smallest denomination of Ether
// 1 Ether = 10^18 Wei (Way)
// 1 Ether = 1,000,000,000 Gwei
// 1 Ether = 1,000 Finney
// We need this many zeroes because $1 == .00032 Ethereum
// ----- FUNCTIONS -----
// function funcName(parameterList) scope returns() {statements}
// Create a function and define parameters it receives
// We are stating to store the arguments in memory
// Arguments are either passed by VALUE : Value changes in function
// don't effect the value outside of it
// Or, by REFERENCE : changes in function effect value outside
// Function variables should start with an underscore to
// differentiate them from global variables
// ----- SCOPE -----
// 1. Public functions are excessible by other contracts
// 2. Private functions are only accessible to code that is in the contract
// Private function names begin with _
// 3. External functions can't be called by the contract, but can be
// called outside of the contract
// 4. Internal functions are only accessible within the contract or by
// other contracts that inherit from this contract
// A VIEW function works with data and allows us to view the
// results of the function (Won't modify the state)
// A PURE function doesn't allow reading or modifying state
// This function returns a uint
// Since it doesn't return a state value but instead a
// calculation it is marked as pure
function getSum(uint _num1, uint _num2) public pure returns (uint) {
// Variables created in a function are only available in
// the function : Local Variables
uint _mySum = _num1 + _num2;
return _mySum;
}
function getResult() public pure returns(uint){
uint a = 10;
uint b = 5;
uint result = a + b;
return result;
}
uint specialVal = 10;
// External means it can only be called from outside the contract
// but not from within (We can change state variables)
function changeSV(uint _val) external {
specialVal = _val;
}
// View needed to return the value but restrict changing it
function getSV() external view returns(uint){
return specialVal;
}
// ----- MATH OPERATORS -----
// You can return multiple values
function doMath(int _num1, int _num2) public pure returns(int, int, int,
int, int, int){
// If we want to require something to be true to continue
// executing the function use require
// ----- ERROR HANDLING -----
// assert : If condition is false revert state changes (Uses up gas)
// require : If condition is false provides option to return message (Refunds Gas)
// revert : Only sends back a message if condition in if statement calls it
require(_num2 != 0, "2nd Number can't be Zero");
// Assert will cancel execution also if the condition
// isn't true
assert(_num2 > 0);
if(_num2 < 0){
revert("2nd Number Must be Greater than 0");
}
// Assignment Operators : += -= *= /= %=
// Increment : ++ Decrement : --
int _add = _num1 + _num2;
int _sub = _num1 - _num2;
int _mult = _num1 * _num2;
int _div = _num1 / _num2; // Integer division
int _mod = _num1 % _num2;
int _sqr = _num1 ** 2;
return (_add, _sub, _mult, _div, _mod, _sqr);
}
// ----- GENERATE RANDOM NUMBER -----
function getRandNum(uint _max) public view returns(uint){
// Generate a pseudo-random hexadecimal using the hash function
// keccak256 (K Chak) which takes an input and converts it into a random
// 256 bit hexadecimal number
// Perform packed encoding of the data before using it to generate
// the random value
uint rand = uint(keccak256(abi.encodePacked(block.timestamp)));
// Takes result and returns values up to _max
return rand % _max;
}
// ----- STRINGS -----
// Printable ASCIII characters between single or double quotes
//
string str1 = "Hello";
// There are escape characters
// \n \\ \' \" \r \t \xNN (Hex Escape) \uNNNN (Unicode)
// Memory states that you want to temporarily store this string
// data (Memory is deleted after each function execution)
// Storage stores data between function calls
function combineString(string memory _str1, string memory _str2) public pure returns (string memory){
// Concat the 2 strings passed
return string(abi.encodePacked(_str1, " ", _str2));
}
// Working with bytes saves computations versus working with strings
// This function returns the number of characters in the string
function numChars(string memory _str1) public pure returns(uint){
// Convert string to bytes
bytes memory _byte1 = bytes(_str1);
return _byte1.length;
}
// ----- CONDITIONALS -----
// Comparison Operators : == != > < >= <=
// Logical Operators : && || !
uint age = 8;
// We are stating to store the arguments in memory
function whatSchool() public view returns (string memory){
if (age < 5) {
return "Stay Home";
} else if (age >= 5 && age <= 6){
return "Go to Kindergarten";
} else if (age >= 6 && age <= 17){
uint _grade = age - 5;
// Convert uint into a string
string memory _gradeStr = Strings.toString(_grade);
// Concat strings
return string(abi.encodePacked("Grade ",_gradeStr));
} else {
return "Go to College";
}
}
// ----- ARRAYS -----
// Create an array that holds a dynamic number of values
uint[] arr1;
// Create fixed size array
uint[10] arr2;
// This creates an array of uints 5 in length
uint [] public numList = [1,2,3,4,5];
// Add 1, 2, 3, 4 to array
function addToArray(uint num) public {
// Add to end of array
arr1.push(num);
}
// Array is now 1, 2, 3
function removeFromArray() public {
// Remove the last element
arr1.pop();
}
// Length should be 3
function getLength() public view returns (uint){
// Get array length
return arr1.length;
}
// If we pass 2 : 1, 2, 0
function setIndexToZero(uint _index) public {
// Sets value at specific index to 0
// When you delete a value in an array the length
// stays the same
delete arr1[_index];
}
// ----- FOR LOOP -----
// Initialize starting index for loop
// Define the loop length
// How index value changes after each loop
// If we pass 1 : 1, 0
function removeIndex(uint _index) public {
// Move the values up to replace the value to replace
for(uint i = _index; i < arr1.length-1; i++){
arr1[i] = arr1[i+1];
}
// Remove the last element
arr1.pop();
}
// Get values in array
function getArrayVals() public view returns (uint[] memory){
return arr1;
}
// ----- FOR LOOP -----
function sumNums() public view returns (uint){
uint _sum = 0;
for(uint i = 1; i <= numList.length; i++){
_sum += numList[i];
}
return _sum;
}
// ----- WHILE LOOP -----
function sumNums2() public view returns (uint){
uint _i = 0;
uint _sum = 0;
while (_i < numList.length) {
_sum += numList[_i];
_i++;
}
return _sum;
}
// ----- STRUCTS -----
// Create a data type that contains multiple variables
struct Customer {
string name;
string custAddress;
uint age;
}
Customer[] public customers;
function addCust(string memory n, string memory ca, uint a) public {
customers.push(Customer(n, ca, a));
}
function getCust(uint _index) public view returns (string memory n, string memory ca, uint a){
Customer storage cust = customers[_index];
return (cust.name, cust.custAddress, cust.age);
}
// ----- MAPPING -----
// Allows you to create key / value pairs
// The key can be a string, uint, or bool
// Value can be anything
mapping(string => string) public myMap;
// Assigns key / value pair
// If I add "Superman", "Clark Kent"
function addSuper(string memory _secret, string memory _name) public {
myMap[_secret] = _name;
}
// Returns value assigned to key
// If sent "Superman" this returns "Clark Kent"
function getName(string memory _secret) public view returns(string memory){
return myMap[_secret];
}
function deleteName(string memory _secret) public {
delete myMap[_secret];
}
// ----- STRUCTS & MAPPING -----
mapping(uint => Customer) customer;
// Map customer data to a index
function addCust2(uint custID, string memory n, string memory ca, uint a) public {
customer[custID] = Customer(n, ca, a);
}
// Retrieve customer data using an index
function getCust2(uint _index) public view returns (string memory n, string memory ca, uint a)
{
return (customer[_index].name, customer[_index].custAddress, customer[_index].age);
}
// ----- NESTED MAPPING -----
// Maps inside maps
// If you wanted a customer list for multiple businesses
// The businesses would have a unique uint
mapping(address => mapping(uint => Customer)) public myCusts;
// Map customer data to different address
function addMyCusts(uint custID, string memory n, string memory ca, uint a) public {
// msg.sender is a global variable that is the address that is
// calling the contract
myCusts[msg.sender][custID] = Customer(n, ca, a);
}
// ----- DATE & TIME -----
// Solidity has time units with the lowest unit at 1 second
function timeUnits() public pure {
// If any of these aren't true the function throws
// an error
assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 days == 24 hours);
assert(1 weeks == 7 days);
}
// ----- ENUMS -----
// Enums are variables that can only have a limited number of values
enum shirtSize{SMALL, MEDIUM, LARGE}
// Create variable of type shirtSize
shirtSize custSize;
// Set a default size and mark it as constant
shirtSize constant defaultSize = shirtSize.MEDIUM;
function pickShirtSmall() public {
custSize = shirtSize.SMALL;
}
function pickShirtMedium() public {
custSize = shirtSize.MEDIUM;
}
// Get current size as a uint
function getShirtSize() public view returns(shirtSize) {
return custSize;
}
}
// ----- SPECIAL VARIABLES -----
// Contracts are like objects of date and functions
// to manipulate that data
contract MyLedger {
// Create a map of addresses and balances
mapping(address => uint) public balances;
// Change the balance for the address
function changeBalance(uint newBal) public {
// msg.sender is the sender of the message
balances[msg.sender] = newBal;
}
// Get current balance for address
function getBalance() public view returns (uint){
return balances[msg.sender];
}
}
// ----- FUNCTION MODIFIER -----
// Create a contract that will be inherited that contains a function modifier
// that blocks execution of certain functions unless the entity executing
// those functions is the owner
contract Owner {
// Holds the address for the owner who deployed this contract
address owner;
// Set the owner as that entity that created the contract
constructor() public {
owner = msg.sender;
}
// Create function modifier that will block anyone from changing
// prices except for the owner
modifier onlyOwner {
// Requires this condition to be true or an error is thrown
require(msg.sender == owner);
// If the caller is the owner then continue executing the
// function that uses this function modifier
_;
}
}
// By inheriting from Owner we can restrict access to change prices
contract Purchase is Owner {
// Mapping that links addresses for purchasers
mapping (address => bool) purchasers;
uint price;
constructor(uint _price) {
price = _price;
}
// You can call this function along with some ether because of
// payable
function purchase() public payable {
purchasers[msg.sender] = true;
}
// Only the owner can change this price
function setPrice(uint _price) public onlyOwner {
price = _price;
}
}
// ----- WHAT IS GAS -----
// It is a fee charged for executing a contract
// on the Ethereum Virtual Machine
// The cost depends on supply and demand. (Price is in Gwei)
// Gas Limit : Max amount of gas your willing to spend for a transaction
// ----- FALLBACK FUNCTIONS -----
// An anonymous function that doesn't take input nor provide output
// It executes if no other function matches the function identifier
// or if no data was provided with a function call
// It also executes when the contract receives Ether without data
// as long as it is marked Payable
contract FallbackTest {
// Maps address to its balance
mapping (address => uint) balance;
// Event that logs gas
event Log(uint gas);
// Function shouldn't do much because it will fail if it uses
// to much gas. This keeps the Ether and emits a log
fallback () external payable {
emit Log(gasleft());
}
function getBalance() public view returns(uint) {
return address(this).balance;
}
}
// This contract sends Ether to FallbackTest contract
// 1. Deploy FallbackTest & TransferToFallback
// 2. Click copy next to FallvackTest to copy its address
// 3. Paste address into transferFallback
// 4. Change value to 2 Ether
// 5. Click transferFallback
// 6. Click getBalance to see the Ether was transferred
// 7. callFallback works the same
contract TransferToFallback {
// Sends Ether with transfar method
function transferFallback(address payable _target) public payable {
_target.transfer(msg.value);
}
// Sends Ether with call method
function callFallback(address payable _target) public payable {
(bool sent,) = _target.call{value:msg.value}('');
require(sent, 'FAILURE: Not Sent');
}
}