QZ:
SY:
Our objective for today is to make you conversant in blockchain technology. Therefore at the end of the day, you should be able to participate in a conversation about blockchain meaningfully.
For some of you, this is the first time you are interacting with blockchain technology. Let's start by making sure you have metamask installed.
Install Google Chrome, you can do it from this link
Install metamask, you can do it from this link
Install node, please do not install version 12, you can use version 11 from this link
Go to our download the required files to run the frontend locally
Connect to our network via metamask.
Click the dropdown menu on the top and go to "Custom RPC". You will see this page. Fill it up with these details.
Network Name: BCDevWorkshop New RPC URL: https://k0tifewhc0:AMYdoFjO2rD4Ch8ArKY6dEBTSpOFUMCDX5zti_NJp2g@k0mv66045h-k0r5t5e62r-rpc.kr0-aws.kaleido.io
Add your account.
Choose a private key from the list here and add your name to the column so others don't take it. You are allowed to do anything with each others' accounts. Each account will be funded 1000 ETH.
As a bonus, the address with the highest ETH or points will win a prize.
Click the circular icon on the top right, then "Import Account".
Enter your private key.
Open your cmd prompt/ console
cd app-users npm install npm run dev
Open your browser and go to http://localhost:8080
Make your first blockchain transaction.
Click on "Register" and fill in your details!
You will get a metamask pop up, approve your transaction!
Now you can go back to "Statuses" or wait for the page to refresh when the block is mined.
I like to use this analogy when explaining blockchain. It is a:
Every concept will follow this format:
Concepts to cover today:
We will finally put it all together with an example.
We will now cover how it is like to work with blockchain technology in terms of development tools and debugging tools.
You can be decentralised in terms of architecture, culture, control, and decision-making.
Decentralization can be viewed from a blockchain perspective as a mechanism that provides a way to remodel existing applications and paradigms or build new applications in order to give full control to users. There are conflicting ideas on the importance of decentralisation but most agree that it removes the need for trust when parties collaborate. This gives rise to new types of goods and services that are still untested.
Scaling – there are challenges in keeping a network decentralized if one intends to scale. A common problem is the transaction speed, which plagued Ethereum for quite a while. Some solutions involve lowering levels of decentralization as a tradeoff for faster transactions, but it fundamentally lowers the level of security of the network due to the need for trusted parties (eg. Supernodes)
Economics – as a network grows, one cannot predict how its peers might interact when utilising the resources a network provides. This might lead to levels of centralization in a network. (Mining power centralization, Centralised exchanges)
Using the forms of decentralisation as a guide:
Bitcoin - Full nodes, Light Nodes, Simple Payment Verification (SPV) Clients.
Hyperledger Fabric - Client, Peer, Orderer
Client | Peer | Orderer |
Submits an actual transaction-invocation to the endorsers, and broadcasts transactions-proposals to the orderer | A node that commits transactions and maintains the state and copy of the ledger. Can also have a special endorser role | Node running the communication service that implements a delivery guarantee |
Open source - an ethos that values collaborating on community projects
Linux Foundation - arguably the most successful open source foundation. Their involvement in blockchain is through the Hyperledger project.
Ethereum Foundation - incorporated as a Swiss non-profit at Zug, Switzerland. Provides Ethereum Grants that pay for teams that work on projects.
Ethereum Improvement Proposals (EIPs) - currently this is how all decisions are made.
Self-sovereign identity where users can manage your own reputation, data, and digital assets
ID2020 - supports digital identity programs across the world
uPort - build tools for decentralised identity data management
Decentralised exchanges that requires no central coordinating party and all participants interact based on incentives
Kyber Network
Decentralised Autonomous Organisations allow participants to carry out democratic processes without trusting any particular institution as a central party. This could be through decentralised means to propose ideas, vote on ideas, making decisions for the organisation or individuals all carried out through smart contracts.
MakerDAO - provides a stable digital currency. The DAO sets the ‘stability fee', ‘collateralization ratio' and triggers an ‘emergency shutdown' if needed.
The structure of the blockchain itself affords the ability for individuals to independently verify the current state of the network. Each peer can compute the blockchain records for themselves to replicate the changes in the state of the network. A malicious peer will have to compute the consensus protocol for blocks already mined and then outcompete all the miners and broadcast their blocks first to every peer.
Consensus protocols cannot be easily changed and require a fork.
A fork can also be used to roll back records but it requires most peers to agree to the fork.
When records can no longer be easily changed by any single party, this creates a new type of good that is purely digital. This is because previously, people could simply make copies and replicate the good, thus no digital good is scarce. The immutability of the records achieved through decentralisation means that the state of such a good must be agreed upon by the entire network, analogous to physical goods that can be verified independently through sight or touch.
Hard forks - rewriting large portions of the blockchain means that records are not absolutely immutable
Irreversibility - wrong actions once accepted and confirmed by the network cannot be easily reversed. Data on the blockchain will stay on the blockchain. Vulnerable smart contracts can be easily decompiled and exploited. The infamous DAO attacker left his message to the Ethereum Community. See it for yourself in the blockchain explorer where multiple transactions were made resulting in a loss of $70M.
New records - Keep the hash of the previous block and a unique nonce, miners compete to find the nonce for the next block
Old records - easily verified that previous blocks are correct by hashing the blocks and checking that the nonces are correct
ERC721 - To emulate rare, collectible items with Ethereum tokens by making these tokens non-fungible.
ERC20 - Allows smart contracts to act very similarly to a conventional cryptocurrency so that this, hosted on the Ethereum blockchain, can be sent, received, checked of its total supply and checked for the amount that is available on an individual address.
You can match the transaction to the address on the blockchain itself quite easily. When you go to a block explorer and view any block, you will see every transaction in the block, the miner, and any part of the block structure.
The pseudonymity that comes with most blockchains is that the address is not traced to a physical identity.
It's this pseudonymity that made blockchain technology an ideal method for illegal activity in its earlier days such as through Silk Road.
However once an address is linked to an individual, this pseudonymity is immediately broken and can be easily traced.
The problems with being traceable is the lack of privacy. One key concern is the General Data Protection Regulation (GDPR) that would make it impossible to conduct business on the blockchain.
Time - privacy schemes take longer to compute and might severely impact usability of blockchain applications.
That being said, there are new encryption schemes such as homomorphic encryption that might help resolve this.
If privacy is implemented
If records are meant to be traceable
Making transactions traceable
Naive - you can just put your name on the transaction itself and you can be immediately traced to it. Block explorers greatly aid this process.
Data science - such as this work on analysing decentralised exchanges. Google has also made the Bitcoin and Ethereum blockchains available on BigQuery.
Making transactions untraceable
Zero Knowledge Proofs - let's you prove that you know some secret to another party without revealing the secret itself
Homomorphic encryption - introduced by Ronald L. Rivest and Len Alderman (the R and A of RSA). It allows one to perform computations on the data without ever decrypting it.
This is a BIG deal because it's a major stumbling block for practical blockchain applications.
Making transactions traceable
From "When to chains combine; supply chain meets blockchain" by Deloitte
Making transactions untraceable
From the Princeton Bitcoin book
Nightfall - private transactions on the Ethereum blockchain using zk-snarks by EY
You cannot mention blockchain without first understanding Bitcoin. It sets forth the very first principles and ideas that made blockchain technology possible.
Bitcoin's consensus protocol was revolutionary at that time and has become contentious recently due to energy concerns, mining centralisation, and up-and-coming ‘new' consensus protocols. But let's start with the original.
Bitcoin also set forth the original protocol and all network participants have to follow. This was key as it was the first decentralised and public method for individuals to transfer value.
This segment references from "Learn Blockchains by Building One" by Daniel van Flymen.
You need to represent the blockchain by defining what is a transaction, what is a block, and how the blocks reference previous blocks.
block = { 'index': 1, 'timestamp': 1506057125.900785, 'transactions': [ { 'sender': "8527147fe1f5426f9dd545de4b27ee00", 'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f", 'amount': 5, } ], 'proof': 324984774000, 'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }
The guide implements a basic proof of work algorithm that is difficult to find but easy to verify. The consensus algorithm searches for hashes that end in a certain number of 0s.
import hashlib import json from time import time from uuid import uuid4 class Blockchain(object): ... def proof_of_work(self, last_proof): """ Simple Proof of Work Algorithm: - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p' - p is the previous proof, and p' is the new proof :param last_proof: <int> :return: <int> """ proof = 0 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof @staticmethod def valid_proof(last_proof, proof): """ Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes? :param last_proof: <int> Previous Proof :param proof: <int> Current Proof :return: <bool> True if correct, False if not. """ guess = f'{last_proof}{proof}'.encode() guess_hash = hashlib.sha256(guess).hexdigest() return guess_hash[:4] == "0000"
The guide uses Flask so that peers can make transactions, mine, call the chain, add new nodes, resolve conflicting blocks.
... import requests class Blockchain(object) ... def valid_chain(self, chain): """ Determine if a given blockchain is valid :param chain: <list> A blockchain :return: <bool> True if valid, False if not """ last_block = chain[0] current_index = 1 while current_index < len(chain): block = chain[current_index] print(f'{last_block}') print(f'{block}') print("\n-----------\n") # Check that the hash of the block is correct if block['previous_hash'] != self.hash(last_block): return False # Check that the Proof of Work is correct if not self.valid_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True def resolve_conflicts(self): """ This is our Consensus Algorithm, it resolves conflicts by replacing our chain with the longest one in the network. :return: <bool> True if our chain was replaced, False if not """ neighbours = self.nodes new_chain = None # We're only looking for chains longer than ours max_length = len(self.chain) # Grab and verify the chains from all the nodes in our network for node in neighbours: response = requests.get(f'http://{node}/chain') if response.status_code == 200: length = response.json()['length'] chain = response.json()['chain'] # Check if the length is longer and the chain is valid if length > max_length and self.valid_chain(chain): max_length = length new_chain = chain # Replace our chain if we discovered a new, valid chain longer than ours if new_chain: self.chain = new_chain return True return False
Postman is used so that you can easily make API calls and try it out for yourself.
This is a really barebones and simple blockchain, nothing compared to the Bitcoin source code itself but it gives you just enough to understand how a blockchain works.
Dev tools are important as it reduces development time.
To interact with the blockchain, it is common to start with geth, which will install the whole ethereum blockchain on your computer. However this might take hours, which is not practical if you are trying out blockchain technology.
Therefore to allow you to work with the Ethereum blockchain without running a full node there are several methods such as;
This list is not exhaustive as new and better ways are still being created.
To work even faster especially for proofs-of-concepts or hackathons, we can use Ganache. Ganache is a popular personal ethereum client which you can use to run tests, execute commands, and inspect state while controlling how the chain operates.
Most tutorials have their networks deployed on a mix of Ganache and Ethereum testnet(s) connected via infura.
To speed up the development and code testing, we have Truffle which is a development environment, testing framework and asset pipeline for Ethereum. truffle init
creates the following file structure, which provides the base for our project.
File structure:
Migrations.sol
, which we'll talk about later.Get Solidity Syntax Plugin by Juan Blanco for Visual Studio Code as it is especially useful since many tutorials online have outdated solidity code and this handy code editor can remind you of the new solidity code syntax.
From the pace at which the development tools in blockchains are developed and new versions of solidity language are pushed out, it is common that the tutorial you have been following is outdated and you are sitting with broken code. When in doubt, google!
Here are some common community resources:
Check the main developer page.
If you are looking to develop an app which requires reading the state of some part of the blockchain, knowledge of web3js and how metamask works would be helpful.
When I was editing the code for the project on voting, I noticed that the Event always on loading if metamask is enabled in browser. So i went to the place in the code where I check if there is a current provider for web3 and perform a few console logs to check the state of my accounts and voila! I found my problem: Web3.getaccounts returned an empty array. With help from stack overflow i picked the possible underlying problems (solvable with actions):
As you can see, some knowledge of how these things work is essential to pinpoint the location of the error (in this case I checked web3.currentProvider). And from there onwards stack overflow and github issues help to identify the deprecated features and other developer's workaround to the issue with the correct keywords.
Other interesting issues:
Barnabé Monnot, Robust Incentives Group, Ethereum Foundation
What do we mean when we talk about incentives?
Game-theoretic view of the Bitcoin protocol
Transaction economics: Auctions and tolls
Hacking the games we play
Jocelyn Chang,[email protected]
www.makerdao.com
1. What is DeFi?
2. Difference between traditional finance and decentralized finance
3. Unbundling of financial intermediary functions
4. DeFi Ecosystem and different DeFi tools
5. A few real world uses cases of MakerDAO
6. The total usage volume of DeFi
7. Benefits and concerns of DeFi
For today, before you ask the workshop organisers, do a quick Google first or check Stackoverflow. We want you to try and debug by yourself first as part of the learning process. After 10 minutes, if you're still stuck, let us know. However we will be walking around throughout the day to ensure you're having a fruitful experience.
We will separate you into pairs and match according to your experience in programming. Today we will be adopting the pedagogy of pair programming so we can learn from each other.
Remix will be your main IDE for today. We use it because it's convenient to compile and test your contracts out quickly.
To gain some experience what it's like developing on Ethereum, we will use test ether from the Ropsten network. You can do this by using the Ropsten Ethereum Faucet or the Metamask Ropsten Ethereum Faucet..
Let's dive right into real smart contracts on the blockchain. Smart contracts are compiled before being migrated onto the blockchain. Thus you need to decompile them.
We analysed a decompiled auction contract from OpenSea.
Simply by looking at the decompiled code, we noticed:
auction
was a structrequire
worksdef createAuction(address _tokenAddress, uint256 _tokenId, uint256 _startingPrice, uint256 _endingPrice, uint256 _duration, address _seller): # not payable require not uint8(stor0.field_160) require _startingPrice < 0xffffffffffffffffffffffffffffffff require _endingPrice < 0xffffffffffffffffffffffffffffffff require _duration <= 18446744073709551615 require ext_code.size(_tokenAddress) call _tokenAddress.ownerOf(uint256 tokenId) with: gas gas_remaining - 710 wei args _tokenId require ext_call.success require ext_call.return_data[12 len 20] == caller require ext_code.size(_tokenAddress) call _tokenAddress.transferFrom(address from, address to, uint256 value) with: gas gas_remaining - 710 wei args caller, addr(this.address), _tokenId require ext_call.success require uint64(_duration) >= 60 auction[addr(_tokenAddress)][_tokenId].field_0 = _tokenAddress auction[addr(_tokenAddress)][_tokenId].field_256 = _seller auction[addr(_tokenAddress)][_tokenId].field_512 = uint128(_startingPrice) auction[addr(_tokenAddress)][_tokenId].field_640 = uint128(_endingPrice) auction[addr(_tokenAddress)][_tokenId].field_768 = uint64(_duration) auction[addr(_tokenAddress)][_tokenId].field_832 = uint64(block.timestamp) log AuctionCreated( address contract=addr(_tokenAddress), uint256 tokenId=_tokenId, uint256 startingPrice=_startingPrice << 128, uint256 endingPrice=_endingPrice << 128, uint256 duration=uint64(_duration))
Today we will learn about Ethereum development through working with several smart contracts and dapps. Each segment is separated into:
"Hello World" equivalent to familiarise users with smart contracts via remix GUI
greet
function, post the transaction id onto google docs and let people try the greet
function with my contractpragma solidity >=0.4.22 <0.6.0; contract Mortal { /* Define variable owner of the type address */ address owner; /* This constructor is executed at initialization and sets the owner of the contract */ constructor() public { owner = msg.sender; } /* Function to recover the funds on the contract */ function kill() public { if (msg.sender == owner) selfdestruct(msg.sender); } } /*inheritors*/ contract Greeter is Mortal { /* Define variable greeting of the type string */ string greeting; /* This runs when the contract is executed */ /* explain memory */ /* public functions are functions that can be called by people while private functions are functions that can only be called by contracts */ constructor(string memory _greeting) public { greeting = _greeting; } /* Main function */ function greet() public view returns (string memory) { return greeting; } }
You are able to send Ether to your agreement, set new terms, and withdraw Ether if an agreement has been violated.
**
with your address and your roommate's addressId
acts like a password and you need to put the same Id as your address' position in the array it is storedpragma experimental ABIEncoderV2; contract RoomieAgreement { //Events are to pass information when something happens on the blockchain to your front-end event NewAgreement (uint agreementId, string agreementTerms); event NewDispute (uint disputeId, string dispute, uint _claim, address claimant); //Variables must be declared uint agreementId = 0; uint minDepositVal = 1; //Solidity only support integers uint minDeposit = minDepositVal / 1000; uint totalDeposit = 0; uint disputeId = 0; //Structs let you create data types with multiple properties struct agreement { uint agreementId; string agreementTerms; } // Declare an array called agreements of type agreement (a struct) that is publicly viewable agreement[] public agreements; //hardcoded addresses of your roomies address[] public roomies = [**,**]; // Mappings are like dictionaries, it's 1 to 1 mapping (address => uint) public roomieDeposits; // This function checks that you submit the minimum deposit and includes it // The payable modifier lets you send money to the smart contract through this function function addDeposit() public payable { //require checks a condition. if it fails, it kills the current transaction and reverts require(msg.value > minDeposit); roomieDeposits[msg.sender] = msg.value; totalDeposit = totalDeposit+msg.value; } // This is actually an experimental function but let's you check the agreements function viewAgreements() public view returns (agreement[]) { return agreements; } //This lets you view the current total deposits function viewDeposits () public view returns (uint) { return totalDeposit; } // This function checks that you picked your right Id and adds your new agreement function createAgreement(string _agreementTerms, uint _roomieId) public { require(roomies[_roomieId-1] == msg.sender); agreementId++; agreements.push(agreement(agreementId,_agreementTerms)); // Event is fired off when this function is complete emit NewAgreement(agreementId,_agreementTerms); } // This function lets you execute a claim and will send it to the claimant function createDispute(uint _dispute, uint _claim, uint _roomieId) public { address claimant = msg.sender; require(roomies[_roomieId-1] == msg.sender); require(_dispute <= agreementId); disputeId++; string dispute = agreements[_dispute-1].agreementTerms; //Event is fired off when this function is complete totalDeposit = totalDeposit-_claim; claimant.transfer(_claim); emit NewDispute(disputeId, dispute, _claim, claimant); } }
Advanced: Implement a solution to any one of these problems we have discussed.
Simple passwords
Having a simple password on the contract. Note that the password should at least be hashed.
uint[] private passwords = [12345, 123456]; //should be hashed function createAgreement(string _agreementTerms, uint _roomieId, uint _password) public { require(roomies[_roomieId-1] == msg.sender); require(_password == passwords[_roomieId-1]); agreementId++; agreements.push(agreement(agreementId,_agreementTerms)); // Event is fired off when this function is complete emit NewAgreement(agreementId,_agreementTerms); }
Adding a username password pair
Before allowing the transaction, check if the password and username is correct. Again, this should be hashed. Also, the return function should be an emit
instead.
bool has_Login = false; function enterPassword(string _username, string _password) public returns (string memory){ if (keccak256(abi.encodePacked((_username))) == keccak256(abi.encodePacked(("hello"))) && keccak256(abi.encodePacked((_password))) == keccak256(abi.encodePacked(("yes")))){ has_Login = true; return "Welcome!"; // emit welcomeMessage(bool _has_Login); } else{ return "Login Failed!"; // emit welcomeMessage(bool _has_Login); } } function addDeposit() public payable { //require checks a condition. if it fails, it kills the current transaction and reverts require(msg.value > minDeposit); require(has_Login == true); roomieDeposits[msg.sender] = msg.value; totalDeposit = totalDeposit+msg.value; }
Intermediate: Create a function that adds another party as an adjudicator
bool disputeApproval = false; function approveDisputeClaim() public { require(msg.sender==roomies[2]); disputeApproval = true; } function createDisputeAdjudication(uint _dispute, uint _claim, uint _roomieId) { require(disputeApproval == true); address claimant = msg.sender; require(roomies[_roomieId-1] == msg.sender); require(_dispute <= agreementId); disputeId++; string dispute = agreements[_dispute-1].agreementTerms; //Event is fired off when this function is complete totalDeposit = totalDeposit-_claim; claimant.transfer(_claim); disputeApproval = false; emit NewDispute(disputeId, dispute, _claim, claimant); }
Beginner: Do some research on escrow contracts and share with the workshop
This is a message board that can hold up to 10 messages and allows you to post messages.
You will get the chance to call other people's contracts once they have uploaded.
pragma solidity ^0.4.22; //Contract is just like classes contract MessageBoard { //Defining User structure. address is a variable type from solidity struct User { address _address; string name; bool isValue; } //Defining Message structure. You can see it have User type member struct Message { string text; User _user; } Message[10] public messageBoardLogs; //Declaring an event event MessageCreated (string text, address _address, string name, bool isValue ); //mapping is a key value store. We are specified type of key and value mapping (address => User) users; // public function, which is returning boolean function setUsername(string memory name) public returns (bool) { //require() is throwing error, if the input is not true require(userExists(msg.sender) == false, "userAddress has been previously registered"); //msg.sender is the address, from where the signUp was called. users[msg.sender] = User(msg.sender, name, true); return true; } //8 bit unsigned integer (so no negative values). uint8 public messageCount = 0; //it is creating an event Message with text and user. function sendMessage (string text) public returns (bool) { //require() is throwing error, if the input is not true require(userExists(msg.sender) == true,"msg.sender is not added into users"); messageCount++; emit MessageCreated(text, users[msg.sender]._address,users[msg.sender].name,true); messageBoardLogs[messageCount] = Message(text, users[msg.sender]); return true; } //We are using view keyword, to specify that it is not modified the state of contract. It will consume less resources. function userExists(address userAddress) view public returns (bool) { return users[userAddress].isValue; } function readMessage(uint messageIndex) view public returns (string text){ require(messageIndex<=messageCount,"message index out of range"); return messageBoardLogs[messageIndex].text; } }
Advanced: Implement a solution to any one of these problems we have discussed.
Implement a check to prevent consecutive posting and payment
Ensure that any post will need to be paid for
address prev; uint minVal = 1; uint minDeposit = minVal / 10000; function sendMessage (string text) public payable returns (bool) { //require() is throwing error, if the input is not true require(userExists(msg.sender) == true,"msg.sender is not added into users"); require(msg.sender != prev, "You're already the previous sender! Wait a while."); require(msg.value >= minDeposit); messageCount++; latest++; latest = latest % maxLog; emit MessageCreated(text, users[msg.sender]._address,users[msg.sender].name,true); messageBoardLogs[latest] = Message(text, users[msg.sender]); prev = msg.sender; return true; }
Make message posters perform some form of proof-of-work
Store proofs that have already been done to prevent repeats. Message posters will have to hash their text and a nonce to form their own proof. Check that the proof has 4 leading 0s.
mapping (bytes32 => bool) proofs; //it is creating an event Message with text and user. // To control for spam, must hash your message with a nonce to obtain a proof with // required amount of leading zeros. function sendMessage (string text, uint256 nonce, bytes32 proof) public returns (bool) { //require() is throwing error, if the input is not true require(proofs[proof] == false); require(keccak256(abi.encodePacked(text, nonce)) == proof); require(userExists(msg.sender) == true,"msg.sender is not added into users"); require(proof[0] == 0); require(proof[1] == 0); require(proof[2] == 0); require(proof[3] == 0); proofs[proof] = true; messageCount++; emit MessageCreated(text, users[msg.sender]._address,users[msg.sender].name,true); messageBoardLogs[messageCount] = Message(text, users[msg.sender]); return true; }
Intermediate: devise a solution for the 10 message limit
//messageBoardLogs can store up to 20 messages Message[21] messageBoardLogs; //state change when messageBoardLogs is full bool messageBoardLogsFull = false; //Declaring an event event MessageCreated (string text, address _address, string name, bool isValue ); //empty constructor will be generated at compile-time if code is not provided. //constructor() public {} //mapping is a key value store. We are specified type of key and value mapping (address => User) users; // public function, which is returning boolean function setUsername(string memory name) public returns (bool) { //require() is throwing error, if the input is not true require(userExists(msg.sender) == false, "userAddress has been previously registered"); //msg.sender is the address, from where the signUp was called. users[msg.sender] = User(msg.sender, name, true); return true; } //8 bit unsigned integer (so no negative values). uint8 public messageCount = 0; //it is creating an event Message with text and user. function sendMessage (string memory text) public returns (bool) { //require() is throwing error, if the input is not true require(userExists(msg.sender) == true,"msg.sender is not added into users"); //replaces message when threshold message number exceeded if(messageCount<=20){ messageCount++; }else{ messageBoardLogsFull = true; messageCount = 1; } emit MessageCreated(text, users[msg.sender]._address,users[msg.sender].name,true); messageBoardLogs[messageCount] = Message(text, users[msg.sender]); return true; } function userExists(address userAddress) public view returns (bool) { return users[userAddress].isValue; } function readMessage(uint messageIndex) public view returns (string memory text){ if(messageBoardLogsFull == false){ require(messageIndex<=messageCount,"message index out of range"); }else{ require(messageIndex<=21,"message index out of range"); } return messageBoardLogs[messageIndex].text; } function readLatestMessage() public view returns(string memory text){ require(messageCount>0,"there are no messages in messageBoardLogs"); return messageBoardLogs[messageCount].text; } }
Beginner: what are some ways that people have used blockchain to share messages?
This contract simulates a simple voting process. There are 2 candidates you can vote for.
To view the frontend, download this file.
pragma solidity ^0.5.1; contract Election { // Model a Candidate struct Candidate { uint id; string name; uint voteCount; // uint _candidateId; } // Store accounts that have voted mapping(address => bool) public voters; // Store Candidates // Fetch Candidate mapping(uint => Candidate) public candidates; // Store Candidates Count uint public candidatesCount; // voted event event votedEvent ( uint indexed _candidateId ); constructor () public { addCandidate("Candidate 1"); addCandidate("Candidate 2"); } function addCandidate (string memory _name) private { candidatesCount ++; candidates[candidatesCount] = Candidate(candidatesCount, _name, 0); } function vote (uint _candidateId) public { // require that they haven't voted before require(!voters[msg.sender], "if voter is not msg.sender"); // update candidate vote Count candidates[_candidateId].voteCount ++; // trigger voted event emit votedEvent(_candidateId); } }
Advanced: Implement a solution to any one of these problems we have discussed.
Intermediate: Implement a way you can check that the candidate is valid and the voter votes only once.
function vote (uint _candidateId) public { // require that they haven't voted before require(!voters[msg.sender], "if voter is not msg.sender"); // require a valid candidate require(_candidateId > 0 && _candidateId <= candidatesCount,"if candidate not valid"); // record that voter has voted voters[msg.sender] = true; // update candidate vote Count candidates[_candidateId].voteCount ++; // trigger voted event emit votedEvent(_candidateId); }
Beginner: what other aspects of the democratic process could be decentralised?
You've deployed a local front-end connected to the decentralised attendance and question-answer dapp.
pragma solidity ^0.5.0; contract Users { // data structure that stores a user bytes32 answer; struct User { string name; string status; address walletAddress; uint createdAt; uint updatedAt; uint points; } // it maps the user's wallet address with the user ID mapping (address => uint) public usersIds; // Array of User that holds the list of users and their details User[] public users; // event fired when an user is registered event newUserRegistered(uint id); // event fired when the user updates his status or name event userUpdateEvent(uint id); // Modifier: check if the caller of the smart contract is registered modifier checkSenderIsRegistered { require(isRegistered()); _; } /** * Constructor function */ constructor() public { // NOTE: the first user MUST be emtpy: if you are trying to access to an element // of the usersIds mapping that does not exist (like usersIds[0x12345]) you will // receive 0, that's why in the first position (with index 0) must be initialized addUser(address(0x0), "", ""); // Some dummy data addUser(address(0x333333333333), "Leo Brown", "Available"); addUser(address(0x111111111111), "John Doe", "Very happy"); addUser(address(0x222222222222), "Mary Smith", "Not in the mood today"); } function postAnswer(string memory _answer) public returns(bytes32){ require(msg.sender==** || msg.sender==** || msg.sender==**); answer = keccak256(abi.encode(_answer)); } /** * Function to register a new user. * * @param _userName The displaying name * @param _status The status of the user */ function registerUser(string memory _userName, string memory _status) public returns(uint) { return addUser(msg.sender, _userName, _status); } /** * Add a new user. This function must be private because an user * cannot insert another user on behalf of someone else. * * @param _wAddr Address wallet of the user * @param _userName Displaying name of the user * @param _status Status of the user */ function addUser(address _wAddr, string memory _userName, string memory _status) private returns(uint) { // checking if the user is already registered uint userId = usersIds[_wAddr]; require (userId == 0); // associating the user wallet address with the new ID usersIds[_wAddr] = users.length; uint newUserId = users.length++; // storing the new user details users[newUserId] = User({ name: _userName, status: _status, walletAddress: _wAddr, createdAt: now, updatedAt: now, points: 0 }); // emitting the event that a new user has been registered emit newUserRegistered(newUserId); return newUserId; } /** * Update the user profile of the caller of this method. * Note: the user can modify only his own profile. * * @param _newUserName The new user's displaying name * @param _newStatus The new user's status */ function updateUser(string memory _newUserName, string memory _newStatus) checkSenderIsRegistered public returns(uint) { // An user can modify only his own profile. uint userId = usersIds[msg.sender]; User storage user = users[userId]; if (keccak256(abi.encode(_newStatus)) == answer){ user.points += 10; } user.name = _newUserName; user.status = _newStatus; user.updatedAt = now; emit userUpdateEvent(userId); return userId; } /** * Get the user's profile information. * * @param _id The ID of the user stored on the blockchain. */ function getUserById(uint _id) public view returns( uint, string memory, string memory, address, uint, uint, uint ) { // checking if the ID is valid require( (_id > 0) || (_id <= users.length) ); User memory i = users[_id]; return ( _id, i.name, i.status, i.walletAddress, i.createdAt, i.updatedAt, i.points ); } /** * Return the profile information of the caller. */ function getOwnProfile() checkSenderIsRegistered public view returns( uint, string memory, string memory, address, uint, uint, uint ) { uint id = usersIds[msg.sender]; return getUserById(id); } /** * Check if the user that is calling the smart contract is registered. */ function isRegistered() public view returns (bool) { return (usersIds[msg.sender] > 0); } /** * Return the number of total registered users. */ function totalUsers() public view returns (uint) { // NOTE: the total registered user is length-1 because the user with // index 0 is empty check the contructor: addUser(address(0x0), "", ""); return users.length - 1; } }
This is our last dapp for the day, thus it is CAPSTONE TIME.
Fully design and implement a new feature for this dapp.
Process of creating a dapp (beginners)
truffle init
to create your own sample project and copy and paste the solidity codetruffle.js
or truffle-config.js
for deployment of contractsThe University ecosystem in Singapore, our blockchain research collective.
The blockchain industry in Singapore, OpenNodes.
Congratulations! You have reached the end of this workshop.