Ethereum
Solidity
[Hide].sol
file. The version is important for proper compilation.
pragma solidity 0.6.0 // Version 0.6.0 pragma solidity ^0.6.0 // Version 0.6.AnythingFurthermore, Solidity strongly recommends us to include the license of our program at the header. The following MIT license indicates that our code is open source.
// SPDX-License-Identifier: MITFurthermore, remember that 1 Ether is worth:
public
, private
, external
, and internal
.
int
and unsigned integer uint
are by default stored in 256-bits. We can, however, specify their number of bits accordingly as below. It is recommended to keep it 256 for compatibility reasons between smart contracts. If an integer has a greater value than the maximum possible, it reverts back to the smallest, making integer addition really a modulo addition.
uint public myInt = 32; // unsigned integer int8 external myInt2 = -3; // signed integer of 8-bits // uint8 max size: 255 // uint16 max size: 65,535 // uint32 max size: 4,294,967,295
bool internal myBool = false; // Boolean
address private EthWallet = 0xA0362AD63a5ac9e630849f19709e46368b9610Ab;
string public myStr = "Hello World";
bytes
for arbitrary-length raw byte data and string
for arbitrary string (UTF-8) data. They have the advantage of using less gas than strings. You can limit the length to a certain number of bytes from bytes1
to bytes32
if possible.
bytes32 myCat = "cat"; // 32-byte data
uint256 tooBig = 250; // too many bits for a small number uint8 justRight = uint8(tooBig) // casted it into a smaller-bit uintSolidity contracts are like a class in any other object-oriented language, which contain data as state variables and functions which can modify these variables. They can be created with the
contract
keyword. The state variables, which contain the data representing the state of the contract, are shown below, and we use the default constructor function, which gets called as soon as the contract is deployed.
contract SimpleStorage { uint256 myInt = 0; // state variable int private myAge = 21; // state variable constructor() {} // default constructor }
function
keyword, followed by the function_name
and its parameters.
function function_name(parameter_list) scope returns(return_type) { // block of code }
function_name(uint a, uint b)
.
returns
keyword. Multiple return types may be specified.
view
or pure
by default have access to both read and modify powers. You do have to make a transaction to call these functions.
pragma solidity ^0.6.0 contract SimpleStorage { // this will get initialized to 0 uint256 favoriteNumber; function store(uint256 _favoriteNumber) public { favoriteNumber = _favoriteNumber } }Note that the state variable of this contract is specified by
favoriteNumber
. Since the function store
is public, we can interact with it from outside the contract, and calling the function with an int256
parameter updates the state variable of it. When we deploy this contract, it becomes a part of the blockchain, and modifying the state variable of this contact means to modify the blockchain itself. Therefore, transactions, smart-contract interactions, and function calls can be used interchangeable (kinda), because whenever you call a function (or whenever you make some state change to the blockchain), you're also making a transaction, which will cost a bit of gas. That is why whenever we call some function, we must always pay a bit of gas fees.
store
function and update the state variable, we can't actually look at what the value of favoriteNumber
is. To do this, we can simply set the visibility of the state variable to public
, changing the line to uint256 public favoriteNumber;
. Furthermore, let us introduce a public view function retrieve
that returns the value of favoriteNumber
and a pure function add
that simply adds the two together (but does not set it as the new state variable). Note that the pure function does not change the state variable of the contract.
pragma solidity ^0.6.0 contract SimpleStorage { // this will get initialized to 0 uint256 public favoriteNumber; function store(uint256 _favoriteNumber) public { favoriteNumber = _favoriteNumber } // view, pure function retrieve() public view returns(uint256) { return favoriteNumber; } function add(uint256 favoriteNumber) public pure { uint256 twoTimes = favoriteNumber + favoriteNumber; return twoTimes; } }Now if we remove the public keyword from the state variable initialization, it gets set back to internal. But this is no problem, since we can call the public retrieve function to return the value.
require
statement checks whether the statement within the parenthesis is true and stops the contract if it is false, providing an error statement and refunding the gas.
function divide(int _num1, int _num2) public pure returns(int) { require(_num2 != 0, "2nd number can't be zero!"); int quotient = _num1 / _num2; return quotient; }
assert
statement checks for a statement that should never be false. If the statement is false, then it reverts all state changes to the blockchain and uses up gas.
function divide(int _num1, int _num2) public pure returns(int) { assert(_num2 > 0); int quotient = _num1 / _num2; return quotient; }
revert
only sends a message and must be put inside an conditional if
statement.
function divide(int _num1, int _num2) public pure returns(int) { if(_num2 < 0) { revert("2nd number must be greater than 0") } int quotient = _num1 / _num2; return quotient; }
block.basefee
gives the current block's base fee in uint
.
block.chainid
gives the current chain id in uint
.
block.coinbase
gives the current block miner's address in address payable
block.number
gives the current block number in uint
.
block.timestamp
gives the current block timestamp in seconds since Unix epoch in uint
.
msg.data
gives the complete calldata in bytes
.
msg.sig
gives the first four bytes of the calldata in bytes4
.
msg.value
gives the number of wei send with the message in uint
.
msg.sender
gives the sender of the message in address
.
block.timestamp
which should give a different uint
every second.
uint
to the global variable/function abi.encodePacked
, which returns a bytes
type. This function performs packed encoding of the given arguments (note that this encoding can be ambiguous).
bytes
to the keccack256
hash function, which outputs a 256-bit output in the form of a bytes32
type.
uint
type, giving it the impression of randomness and modulo it by _max
.
contract demonstrate { function getRandNum(uint _max) public view returns(uint) { uint rand = uint(keccak256(abi.encodePacked(block.timestamp))); return rand % _max; } }
string _str1
, we must write them with the proper memory allocation, one of two ways:
string memory _str1
: The memory
keyword means that the memory for this string will be deleted as soon as the function is finished executing.
string storage _str1
: The storage
keyword means that the memory for this string will persist even after the function execution.
memory
refers to short-term (like RAM) storage that gets deleted after execution of the function, while storage
persists after execution (like drives). Furthermore, the regular built-in functionalities of string such as concatenation, determining the string's length, and reading/changing a character are not built-in within Solidity's string
class! We can make a function to concatenate strings using our global variables again:
function combineString(string memory _str1, string memory _str2) public pure returns(string memory) { return string(abi.encodePacked(_str1, " ", _str2)); // Concatenates _str1 and _str2 with a space in between }Here is a function returning the number of characters within a string:
function numChars(string memory _str1) public pure returns(uint) { bytes memory _byte1 = bytes(_str1); return _byte1.length; }
type[ arraySize ] arrayName # fixed size type[] arrayName # dynamically sizedWe can append a last element, delete the last element, get the length, set the element at a certain index to 0, delete a certain index of an array, sum the elements of an array, and more...
contract demonstrate { uint[] arr1; // create dynamically-sized array uint[10] arr2; // create statically-sized array of 10 elements uint [] public numList = [1, 2, 3, 4, 5]; function addToArray(uint num) public { // push to array (last element) arr1.push(num) } function removeFromArray() public { // pop array (delete last element) arr1.pop(); } function getLength() public view returns (uint) { // return length of array return arr1.length; } function setIndexToZero(uint _index) public { // the delete function just sets the value at the index to 0 delete arr1[_index]; } function removeIndex(uint _index) public { // removes the element at specified index for (uint i = _index; i < arr1.length-1; i++) { arr1[i] = arr1[i+1]; } arr1.pop(); } function getArrayVals() public view returns(uint[] memory) { // needed since the arr1 state variable is not public return arr1; } function sumNums() public view returns(uint) { // sums all elements of an array uint _sum = 0; for (uint i = 0; i <= numList.length-1; i++) { _sum += numList[i]; } return _sum; } function sumNums2() public view returns(uint) { // sums all elements of an array, but using a while loop uint _i = 0; uint _sum = 0; while (_i < numList.length) { _sum += numList[_i]; _i++; } return _sum; } }
contract test { struct Book { string title; string author; uint book_id; } // Initialize a public Book object called 'book' Book public book = Book('Learn Java', 'TP', 1); }Let us create a contract that implements a struct, with functions that adds structs to arrays and gets from them too.
contract demonstrate { struct Customer { string name; string custAddress; uint age; } Customer[] public customers; function addCust(string memory n, string memory ca, uint a) public { // adds Customer object to to customers array customers.push(Customer(n, ca, a)); } function getCust(uint _index) public view returns (string memory n, string memory ca, uint a){ // returns Customer object by index from customers array Customer storage cust = customers[_index]; return (cust.name, cust.custAddress, cust.age); } }
mapping(key => value) mapping_name; // Initialization of mapping mapping_name[key] = value // Adding key-value to mappingLet us create a contract implementing a mapping that assigns a superhero name with the real name.
contract demonstrate { mapping(string => string) public myMap; function addHero(string memory _secret, string memory _name) public { // adds hero name and real name to myMap myMap[_secret] = _name; } function getName(string memory _secret) public view returns(string memory){ // returns the real name given the hero name from myMap return myMap[_secret]; } function deleteName(string memory _secret) public { // deletes the hero/real name pair delete myMap[_secret]; } }We can also predefine a struct and implement a contract that takes in a mapping that maps an index to a Customer object.
contract demonstrate { mapping(uint => Customer) customer; function addCust2(uint custID, string memory n, string memory ca, uint a) public { // Map customer data to a index customer[custID] = Customer(n, ca, a); } function getCust2(uint _index) public view returns (string memory n, string memory ca, uint a) { // Retrieve customer data using an index return (customer[_index].name, customer[_index].custAddress, customer[_index].age); } }
contract demonstrate { mapping(address => mapping(uint => Customer)) public myCusts; // initialize iterated mapping function addMyCusts(uint custID, string memory n, string memory ca, uint a) public { // Adds index-Customer data for each person calling function // Now has data on which address sent which data myCusts[msg.sender][custID] = Customer(n, ca, a); // msg.sender is global variable returning address of person calling contract } }Finally, we can create a ledger with a smart contract. The following creates a map from addresses to
uint
, where the values can be changed or returned.
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]; } }
while (condition) { ... }Implementing it in a contract looks like this, which, upon calling the
loop()
function, pushes 5 elements into the data
array.
contract demonstrate { uint[] public data; uint8 j = 0; function loop() public returns (uint[] memory) { while (j < 5) { j++; data.push(j); } return data; } }The do-while loop is very similar to the while loop, but it checks the condition at the end of the loop. So, the loop will execute at least one time even if the condition is false.
do { ... } while (condition)We can implement it in a contract as such:
contract demonstrate { uint[] public data; uint8 j = 0; function loop() public returns (uint[] memory) { do { j++; data.push(j); } while (j < 5); return data; } }A for loop has syntax similar to that of JavaScript.
for (initialization; test condition; iteration statement) { ... }It is implemented in a contract as such:
contract test { uint[] public data; uint8 j = 0; function loop() public returns (uint[] memory) { for (uint i = 0; i < 5; i++) { j++; data.push(j); } return data; } }We can implement conditionals in a contract. In this example, we will create a function that returns what school level you should be going to depending on your age.
contract demonstrate { string myName = "Muchang"; uint age = 8; function whatSchool() public view returns(string memory) { if (age < 5) { return "Stay home"; } else if (age == 5) { return "Go to Kindergarten"; } else if (age >= 6 && age <= 17) { uint _grade = age - 5; string memory _gradeStr = Strings.toString(_grade) // convert uint to string return string(abi.encodePacked("Grade ", _gradeStr)); } else { return "Go to college"; } } }
contract demonstrate { 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); } }
Shape
contract with state variables height
and width
. We can set their values upon initialization as such, similar to how we would do it in other OOP languages.
contract Shape { uint height; uint width; constructor(uint _height, uint _width) { height = _height; width = _width; } }To construct a child contract that inherits from a parent contract, we use the
is
keyword. In here, we construct a child class Square
and Rectangle
.
contract Square is Shape { constructor(uint s) Shape(s, s) {} function getArea() public view returns(uint) { return s**2; } } contract Rectangle is Shape { constructor(uint h, uint w) Shape(h, w) {} function getArea() public view returns(uint) { return h*w } }
Owner
contract which first identifies the owner address and immediately saves it as msg.sender
within the constructor. Then, we create the modifier with the require statement that whoever sent the message must be the owner. The main use for modifiers is for automatically checking a condition prior to executing a function. If the function does not meet the modifier requirement, an error is thrown. The syntax for one kind of modifier is
modifier MyModifier { require(msg.sender == owner); _; }The symbol
_;
is called a merge wildcard, and it must always be present in the modifier. It merges the function code with the modifier code where the _;
is placed. In other terms, the body of the function (to which the modifier is attached to) will be inserted where the special symbol _;
appears in the modifier’s definition.
contract Owner { address owner; constructor() public { owner = msg.sender; } // If caller is owner then continue executing the function that uses this modifier modifier onlyOwner { require(msg.sender == owner); _; // a merge wildcard } }We can place whatever restricted-access function within this contract, but we will create a child contract for our purposes. Let us create a subcontract with two state variables, a mapping of purchasers and a price of some good. Clearly, the deployer of the contract, who is made the owner (from the constructor of
Owner
) sets the price upon deployment. If an individual would like to express the intent to purchase this good for this price, they can call the public payable function purchase
which adds their address (msg.sender
) and their intent to buy (true
) in the purchasers
mapping. The function setPrice
can change the price, but because of the onlyOwner
modifier, the conditions mentioned in the modifier above are immediately placed, restricting access to this function to everybody except for owner
.
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; } }
address payable
rather than address
. This distinction only exists in the Solidity type system at compile-time and is gone in the compiled contract code. You can use .transfer(...)
, .send(...)
on address payable
, but not on address. However, can you can use .call(...)
on both, and since May 2021, the call function is recommended.
payable
means that we can send ether along with the message when we call this function. All the ether that gets send to this function is now held within the contract owning the function. Given a certain address addy
, the ether balance of it can be found by calling addy.balance
.
contract EtherWallet { address payable public owner; // initialize address payable state variable constructor() { owner = payable(msg.sender); // set sender address to owner variable upon deployment } function deposit() public payable {} // function that can receive ETH payments modifier onlyOwner { require(owner == msg.sender); _; } function withdraw(uint _amount) public onlyOwner { (bool success, ) = owner.call{value: _amount}(""); require(success, "Failure: Not Sent"); } function withdrawAll() public onlyOwner { (bool success,) = owner.call{value: address(this).balance}(""); require(success, "Failure: Not Sent"); } function getBalance() external view returns (uint) { return address(this).balance; } }Here is the address of the wallet. Just for reference, you can also use the transfer function, which has less confusing syntax:
function withdraw(uint _amount) public { payable(msg.sender).transfer(_amount); }
TBD
[Hide]