solidity101
solidity101 copied to clipboard
A basic introduction to solidity, at the ETHLisbon hackathon.
#+TITLE: Solidity 101
- Link https://github.com/hrkrshnn/solidity101
- Resources to get started
- https://soliditylang.org/
- https://docs.soliditylang.org/en/v0.8.9/
- https://cryptozombies.io/
- Basic Faucet
#+begin_src solidity :tangle Faucet.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.9;
/// Comments that start with three slashes: NatSpec comments. /// /// A faucet that holds ETH distributes it later. contract Faucet { /// Function that allows the Faucet to receive ETH /// Anyone can send eth to the address of the contract. /// Notice the keyword payable. receive() payable external { }
/// Sends back 0.1 ether to the sender
function claim() external {
payable(msg.sender).transfer(0.1 ether);
}
}
#+end_src
- Faucet that tracks claims
#+begin_src solidity :tangle TrackingFaucet.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.9;
contract TrackingFaucet { mapping(address => uint) claims; /// Function that allows us to receive ETH receive() payable external { }
/// Sends back 0.1 ether to the sender
function claim() external {
// Note: in real code try to use custom errors
// instead of revert strings
require(
claims[msg.sender] <= 0.2 ether,
"You claimed too much."
);
claims[msg.sender] += 0.1 ether;
payable(msg.sender).transfer(0.1 ether);
}
}
#+end_src
-
Proof of work faucet #+begin_src solidity :tangle POWFaucet.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.9;
contract POWFaucet { /// Mapping. /// A hash table with key being an address /// and a unsigned integer as value. /// Notice the keyword public. /// Public keyword for state variables automatically /// creates a getter function. /// Getter function with signature /// "function claims(address) view returns (uint);" mapping(address => uint) public claims; receive() payable external { } /// Sends back 0.1 ether to the sender, up to 3 times. /// Proof of work is required for claiming ether. /// Needs to call claim function with a unique integer for /// each address, which is obtained by work! /// /// TODO: the same proof of work can be used for three times. /// Implement it in such a way that you will need different /// proof of work numbers for each turn. /// Hint: nonces. function claim(uint pow) external { bytes memory data = abi.encodePacked(msg.sender, pow); uint256 hash = uint256(keccak256(data)); // TODO: try to use custom errors instead of strings. require(hash % 10 == 0, "Proof of work required"); // TODO: try to use custom errors instead of strings. require(claims[msg.sender] <= 0.2 ether, "You claimed too much"); claims[msg.sender] += 0.1 ether; // TODO: <address payable>.transfer has some issues if // the address executes code on ETH transfer or for certain proxy // contracts. What is the problem? How do we fix it? // What about re-entrancy? // Hint: msg.sender.call{value: 0.1 ether)(""); payable(msg.sender).transfer(0.1 ether); } }
#+end_src
-
Proof of work contract #+begin_src solidity :tangle ProofOfWork.sol // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.9;
/// A contract for finding the proof of work number /// Note: this does not really have to be done using a contract, or on chain. contract ProofOfWork { /// Returns unsigned integer that can be used to claim eth from the faucet. function pow() external view returns (uint) { // Assuming that keccak256 is uniformly distributed, // it takes around 10 tries for "keccak256(random_number) % 10 == 0" // to be true. // We do this in a for loop. for (uint i = 0; i < 15; i++) { bytes memory data = abi.encodePacked(msg.sender, i); uint256 hash = uint256(keccak256(data)); if (hash % 10 == 0) return i; } revert("Could not find a hash"); } } #+end_src
#+RESULTS:
- Design Tips
- Have a specification for each function and for the entire
contract:
- Who should be able to call the function?
- What external calls will the function make? Are they trusted or untrusted?
- What state variables should the function modify?
- When should the function revert?
- Should the function be able to receive ETH?
- Should the contract hold ETH or tokens?
- Design Tips
- Architecture that minimizes gas usage:
- Minimize: storage writes (around 20000 gas) storage reads (around 2100 gas) and external calls (around 2600 gas).
- Do not use Proxies or Upgradable contracts unless absolutely necessary.
- Try to avoid centralization, e.g., one address with a lot of power in the smart contract.
- Reuse existing libraries: https://github.com/OpenZeppelin/openzeppelin-contracts