Wi-fi password: Guest@42
Miguel Palhas
@naps62
@subvisual
Software & blockchain developer
Agenda for today
Morning
- What is a smart contract?
- Solidity crash course
- coffee break / practice
Afternoon
- A bit more Solidity
- interaction
- practice
Toolkit for today:
Option #1
remix.ethereum.org/
Option #2
CLI + hardhat/truffle
hardhat.org/
https://www.trufflesuite.com/
What is a blockchain?
we all know this by now, right?
Consensus
(PoW, PoS, ...)
Protocol
(Bitcoin, Ethereum, polkadot)
(D)apps
Smart Contracts
What is a transaction?
A user may say this ☝️
But we nerds know better 👨💻
BEGIN TRANSACTION; -- pay bounties
UPDATE balances SET balance = balance - 1
WHERE username = 'ethlisbon'
UPDATE balances SET balance = balance + 500
WHERE username = 'naps62'
COMMIT;
Transactions...
... update the state, with a set of operations
Account | Balance |
---|---|
ethlisbon | Ξ 10000 |
naps62 | Ξ 0 |
gabriel | Ξ 0 |
pay_bounties()
Account | Balance |
---|---|
ethlisbon | Ξ 9498 |
naps62 | Ξ 500 |
gabriel | Ξ 2 |
function pay_bounties() {
for (int i = 0; i < winners.length; ++i) {
balances[ETHLISBON_ADDRESS] -= winners[i].prize
balances[winners[i].address] += winners[i].prize
}
}
EVM / Solidity
Solidity code:
pragma solidity 0.4.25;
contract Demo1 {
uint public balance;
uint public totalAdditions;
function add(uint value) public returns (uint256) {
balance = balance + value;
totalAdditions += 1;
return balance;
}
}
EVM bytecode:
608060405234801561001057600080fd5b5060c78061001f6000396000f3
0060806040526004361060485763ffffffff7c0100000000000000000000
0000000000000000000000000000000000006000350416631003e2d28114
604d578063b69ef8a8146074575b600080fd5b348015605857600080fd5b
5060626004356086565b60408051918252519081900360200190f35b3480
15607f57600080fd5b5060626095565b6000805482019081905591905056
5b600054815600a165627a7a7230582063aa00920d824233ab5307ef3a379
c757bdbee62fe00fe36a5d852c766e58fef0029
pragma solidity ^0.8.6;
contract ExtremelyBasicERC20 {
uint mapping(address => uint256) public balances;
function constructor() {
balances[msg.sender] = 1e10; // 10000000000
}
function transfer(address to, uint256 amount) public {
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
interface ERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender)
external view returns (uint256);
function transfer(address to, uint256 value)
external returns (bool);
function approve(address spender, uint256 value)
external returns (bool);
function transferFrom(address from, address to, uint256 value)
external returns (bool);
}
Solidity Crash Course
Contracts / Instances
contract FooToken {
string public name;
function constructor(name _name) {
name = _name;
}
}
const foo1 = Foo.deploy("foo")
// 0x8f03f1a3f10c05e7cccf75c1fd10168e06659be7
const foo2 = Foo.deploy("another_foo")
// 0xc365c3315cf926351ccaf13fa7d19c8c4058c8e1
Variables
contract FooToken {
string public name;
string private symbol;
address owner;
mapping(address => uint256) => balances;
struct Item {
address addr;
uint id;
}
Item[] items;
}
Functions
contract FooToken {
function balanceOf(address x) public view returns (uint256) {
return balances[x];
}
function transfer(address to, uint256 amount) public {
balances[msg.sender] -= amount;
balances[to] += amount;
}
function notCallableFromOutside() private {
// internal logic
}
}
Inheritance
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract FooToken is ERC20 {
function constructor() ERC20("ETHLisbon", "ETHL") {
}
}
Send/Receive Ether
contract FooToken {
function mint() public payable {
balances[msg.sender] += msg.value;
}
function burn(uint256 amount) public {
balances[msg.sender] -+ amount;
payable(msg.sender).transfer(amount);
}
}
Verify Hashes
contract Foo {
bytes32 secret;
string original;
constructor(bytes32 _secret) {
secret = _secret;
}
function reveal(string _original, string _password) public {
bytes32 hash = keccak256(abi.encodePacked(_original, _password));
require(hash == secret);
original = _original;
}
}
Exercise #1
Build a simple token (ERC20-like) contract:
- must keep a list of balances
- must allow transfers
- must allow exchanging Ether for minting new tokens
- each transfer charges a 1% fee, sent to the contract deployer
slides (and spoilers ahead):
https://slides.com/naps62/eth-lisbon-solidity-101
If my time management
is any good,
this is probably lunch time
Solidity, part 2
Require (fail-early)
contract Ownable {
address owner;
function adminOperation() public {
require(msg.sender == owner, "not owner");
veryDangerousFunction();
}
}
Modifiers
contract Ownable {
address owner;
function constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
}
Inter-contract interactions
contract Token { /* ... */ }
contract TokenVault {
Token token;
mapping(address => uint256) balances;
function deposit(uint256 amount) public {
balances[msg.sender] += amount;
token.transferFrom(msg.sender, address(this), amount);
}
function withdraw(uint256 amount) public {
balances[msg.sender] -= amount;
token.transfer(msg.sender, amount);
}
}
web3.js
Actually, two alternatives:
- web3.js
- ethers.js
Live demo
Exercise #2
// player 1
play("rock");
// player 2
play("paper");
// winner account can call this
// contract will check that sender is the winner
claimPrize();
contract RockPaperScissor {
address public p1;
address public p2;
string public m1;
string public m2;
}
contract RockPaperScissor {
// ...
function play(string _move) public payable {
require(!finished(), "all moves set");
if (p1 == address(0)) {
p1 = msg.sender;
m1 = _move;
} else {
require(p1 != msg.sender, "can't play both sides");
p2 = msg.sender;
m2 = _move;
}
}
}
contract RockPaperScissor {
// ...
function winner() internal view returns (address) {
require(finished(), "moves missing");
if ((m1 == "rock" && m2 == "scissor") ||
(m1 == "paper" && m2 == "rock") ||
(m1 == "scissor" && m2 == "paper")) {
return p1;
} else {
return p2;
}
}
function finished() internal view returns (bool) {
return p2 != address(0);
}
}
contract RockPaperScissor {
// ...
function claimReward() public {
require(winner() == msg.sender);
uint256 reward = address(this).balance;
payable(msg.sender).transfer(reward);
}
}
But wait, something's wrong!
// player 1
play("rock"); // this submits a transaction
// player 2 can then read existing transactions,
// and spy on what player 1's move was
play("paper");
// player 1
// plays a hash: keccack256("rock-secr3t")
play("d05c2b9c8221593088d05e5603e95a65629c485c7491a5cc5d394f0b489e4308");
// player 2
// plays a hash: keccack256("paper-12345")
play("bc12654d21eed85d6b0b3de3ef094c5c8f7c436b44ecf61f8bf01c11cc26fae2");
revealMove("rock", "-secr3t");
revealMove("paper", "-12345");
// winner account can call this
// contract will check that sender is the winner
claimPrize();
contract RockPaperScissor {
// ...
function revealMove(string _move, string _password) public {
bytes32 m = keccak256(abi.encodePacked(_move, _password));
if (m1_encrypted == m) {
m1 = _move;
} else if (m2_encrypted == m) {
m2 = move;
}
}
}
Thank you
(?)
ETHLisbon - Solidity 101
By Miguel Palhas
ETHLisbon - Solidity 101
- 203