Príprava projektu

Budeme pracovať s VS Code, ktorý (predpokladám) už všetci máte a poznáte.
Projekt si inicializujeme pomocou create-react-app a doinštalujeme moduly potrebné pre Solidity

npx create-react-app lottery
cd .\lottery\
npm install solc@0.7.4 web3@1.0.0-beta.26 truffle-hdwallet-provider@0.0.3 mocha ganache-cli fs-extra

Ďalej budeme potrebovať nainštalovať rozšírenie do (podporovaného) prehliadača s názvom MetaMask, pomocou ktorého získame peňaženku pre ethereum, ale budeme ho využívať aj pre komunikáciu s kontraktom.
Pri inštlácii si prosím niekde uložte mnemonic (postupnost 12 slov), budeme to potrebovať.
Ak máte problém so sťahovaním balíčkov tak Vám možno chýba Git, alebo node-gyp, ktorý doinštalujete pomocou
npm install -g node-gyp
Ak budú nejaké iné problémy tak to poriešime na hodine (alebo píšte/volajte)

Jednoduchý kontrakt ("Hello World")

Remix šikovný nástroj pre vývoj, testovanie a debugovanie smart kontraktov.

// SPDX-License-Identifier: MIT

pragma solidity ^0.7.4;
	
contract Inbox {
	string public message;
		
	constructor(string memory initialMessage) {
		message = initialMessage;
	}
		
	function setMessage(string calldata newMessage) public {
		message = newMessage;
	}
		
	function getMessage() public view returns(string memory) {
		return message;
	}
}

								

Lottery contract

Úloha 1: doprogramujte funkciu enter() pre zaradenie užívateľa (jeho účtu) do lotérie

Hints
  • skontrolujte (require()), či je poslaná suma rovná (alebo väčšia ako) "0.01 ether"
  • docieľte, aby si kontrakt zapamätal adresu účtu užívateľa

Úloha 2: doprogramujte funkciu pickWinner() pre ukončenie lotérie a vyhlásenie výhercu

Hints
  • vyberte (psedudo)náhodnú adresu
  • na vybranú adresu pošlite (adresa.transfer(množstvo ether)) všetky "peniaze" (balance), ktoré vlastní inštancia kontraktu (this)
  • vyresetujte kontrakt tak, aby mohol znova slúžiť ako nová lotéria (new array[](length))

Poznámka: nezabudnúť poslať ether
									// SPDX-License-Identifier: MIT

pragma solidity ^0.7.4;
										
contract Lottery {
	address public manager;
	address payable[] public players;
										
	constructor(){
		manager = msg.sender;
	}									
	
	function random() private view returns (uint256) {
		return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players)));
	}
		
	function enter() public payable {
		// uloha 1
	}

	function pickWinner() public {
		// uloha 2
	}
										
	function getPlayers() public view returns (address payable[] memory) {
		return players;
	}
										
}

Prejdeme do VS Code, kde už máma vygenerovanú React app s potrebnými modulami.
V priečinku lottery/src si vytvoríme priečinok ethereum kde si vytvoríme súbor compile.js

const path = require('path');
const fs = require('fs-extra');
const solc = require('solc');

const buildPath = path.resolve(__dirname, 'build');
fs.removeSync(buildPath);

const contractPath = path.resolve(__dirname, 'contracts', 'Lottery.sol');
const source = fs.readFileSync(contractPath, 'utf8');

const params = {
  language: 'Solidity',
  sources: {
    'Lottery.sol': {
      content: source
    }
  },
  settings: {
    outputSelection: {
      "*": {
        "*": [ "abi", "evm.bytecode" ]
      }
    }
  }
};
 
const output = JSON.parse(solc.compile(JSON.stringify(params)));

for (let contract in output.contracts["Lottery.sol"]) {
    fs.outputJSONSync(
      path.resolve(__dirname, 'build', "Lottery.json"),
      output.contracts["Lottery.sol"][contract]
    );
}

súbordeploy.js

const HDWalletProvider = require('truffle-hdwallet-provider');
const Web3 = require('web3');
const contract = require('./build/Lottery.json');

const provider = new HDWalletProvider(
  'mnemonic z MetaMask',
  'https://rinkeby.infura.io/v3/d843679381fb4c15a1abf523de22b180'
);
const web3 = new Web3(provider);

const deploy = async () => {
  const accounts = await web3.eth.getAccounts();

  console.log('Attempting to deploy from account', accounts[0]);

  const result = await new web3.eth.Contract(contract.abi)
    .deploy({ data: contract.evm.bytecode.object})
    .send({ from: accounts[0], gas: '1000000' });

  console.log('Contract deployed to', result.options.address);
};
deploy();

súborweb3.js

import Web3 from 'web3';

const web3 = new Web3(window.ethereum);
window.ethereum.enable();

export default web3;

súborlottery.js


import web3 from './web3';
import { abi } from './build/Lottery.json';

const address = 'adresa kontraktu';

export default new web3.eth.Contract(abi, address);

potom si vytvoríme priečinok contracts kde vytvoríme súbor Lottery.sol s našim kontraktom
následne vytvoríme priečinok test kde si vytvoríme súbor Lottery.test.js


const assert = require('assert');
const ganache = require('ganache-cli');
const Web3 = require('web3');
const contract = require('../build/Lottery.json');

const web3 = new Web3(ganache.provider());

let accounts;
let lottery;

beforeEach(async () => {
    accounts = await web3.eth.getAccounts();
    
    lottery = await new web3.eth.Contract(contract.abi)
        .deploy({ data: contract.evm.bytecode.object})
        .send({ from: accounts[0], gas: '1000000' });
});

describe('Lottery Contract', () => {
    it('deploys a contract', () => {
      assert.ok(lottery.options.address);
    });
  
    it('allows one account to enter', async () => {
      await lottery.methods.enter().send({
        from: accounts[0],
        value: web3.utils.toWei('0.02', 'ether'),
      });
  
      const players = await lottery.methods.getPlayers().call({
        from: accounts[0],
      });
      
      assert.strictEqual(1, players.length); 
      assert.strictEqual(accounts[0], players[0]);
    });
  
    it('allows multiple accounts to enter', async () => {
      await lottery.methods.enter().send({
        from: accounts[0],
        value: web3.utils.toWei('0.02', 'ether'),
      });
  
      await lottery.methods.enter().send({
        from: accounts[1],
        value: web3.utils.toWei('0.02', 'ether'),
      });
  
      await lottery.methods.enter().send({
        from: accounts[2],
        value: web3.utils.toWei('0.02', 'ether'),
      });
  
      const players = await lottery.methods.getPlayers().call({
        from: accounts[0],
      });
      
      assert.strictEqual(3, players.length);
      assert.strictEqual(accounts[0], players[0]);
      assert.strictEqual(accounts[1], players[1]);
      assert.strictEqual(accounts[2], players[2]);
    });
  
    it('requires a minimum amount of ether to enter', async () => {
      try {
        await lottery.methods.enter().send({
          from: accounts[0],
          value: 200,
        });
      } catch (err) {
        return assert(err);
      }
      assert(false);
    });
  
    it('only manager can call pickWinner', async () => {
      try {
        await lottery.methods.enter().send({
          from: accounts[0],
          value: web3.utils.toWei('0.02', 'ether'),
        });
  
        await lottery.methods.pickWinner().send({
          from: accounts[1],
        });
      } catch (err) {
        return assert(err);
      }
      assert(false);
    });
  
    it('sends money to the winner and resets the players array', async () => {
      await lottery.methods.enter().send({
        from: accounts[1],
        value: web3.utils.toWei('2', 'ether'),
      });
  
      const initialBalance = await web3.eth.getBalance(accounts[1]);
  
      await lottery.methods.pickWinner().send({
        from: accounts[0],
      });
  
      const finalBalance = await web3.eth.getBalance(accounts[1]);
      const difference = finalBalance - initialBalance;
  
      assert(difference == web3.utils.toWei('2', 'ether'));
  
      const lotteryBallance = await web3.eth.getBalance(lottery.options.address);
      assert.strictEqual('0', lotteryBallance);
  
      const players = await lottery.methods.getPlayers().call({
        from: accounts[0],
      });
  
      assert.strictEqual(0, players.length);
    });
  });

súbor App.js


import React, {Component} from 'react';
import './App.css';
import web3 from './ethereum/web3';
import lottery from './ethereum/lottery';

class App extends Component {
  state = {
    manager: '',
    players: [],
    balance: '',
    value: '',
    message: ''
  };

  async componentDidMount() {
    const manager = await lottery.methods.manager().call();
    const players = await lottery.methods.getPlayers().call();
    const balance = await web3.eth.getBalance(lottery.options.address);

    this.setState({ manager, players, balance });
  }

  onSubmit = async (event) => {
    event.preventDefault();

    const accounts = await web3.eth.getAccounts();

    this.setState({ message: 'Transakcia sa spracuva'});

    await lottery.methods.enter().send({
      from: accounts[0],
      value: web3.utils.toWei(this.state.value, 'ether')
    });

    this.setState({ message: 'Uspesne ste sa zapojili do loterie'});
  }

  onClick = async () => {
    const accounts = await web3.eth.getAccounts();

    this.setState({ message: 'Transakcia sa spracuva'});

    await lottery.methods.pickWinner().send({
      from: accounts[0]
    })

    this.setState({ message: 'Vyherca bol vyzrebovany'});
  }

  render(){
    return (
	<div>
        <h2>Loteria</h2>
        <p>Tento kontrakt spravuje {this.state.manager}
        <br />Aktualne vstupilo do loterie {this.state.players.length} ludi, ktori hraju o 
        cenu {web3.utils.fromWei(this.state.balance, 'ether')} ETH </p>

        <hr />

        <form onSubmit={this.onSubmit}>
          <h4>Chces skusit svoje stastie?</h4>
          <div>
            <label>Vlozit do loterie </label>
            <input
              value={this.state.value}
              onChange={event => this.setState({ value: event.target.value })} 
            />
            <label> ETH</label>
          </div>
          <button>Potvrdit</button>
        </form>

        <hr />

        <h4>Pripraveny vybrat vyhercu?</h4>
        <button onClick={this.onClick}>Vyzrebovat vyhercu</button>

        <hr />

        <h1>{this.state.message}</h1>
      </div>
    );
  }
}

export default App;

Úloha 3: pri vylosovaní vypísať adresu výherného účtu

Hints
  • upraviť kontrakt aby si pamätal kto bol vylosovaný
  • nezabudnúť re-deploynúť kontrakt a použiť adresu nového kontraktu pre vytvorenie jeho kópie

Úloha 4(ak bude veľa času a 3. bude ľahká xD ): upraviť kontrakt, aby nepovolil rovnakému účtu zúčastniť sa lotérie na tomto kontrakte viackrát

Hints
  • POZOR! iterovanie poľom s dynamickou veľkosťou (teda môže a veľmi pravdepodobne sa bude zväčšovať) je drahé, teda chcelo by to štruktúru, ktorá poskytuje nájdenie prvku so zložitosťou O(1)
  • nezabudnúť re-deploynúť kontrakt a použiť adresu nového kontraktu pre vytvorenie jeho kópie

Ďakujem za pozornosť