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