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