As part of OpenNodes' Campus Collective's effort to build up a vibrant tech ecosystem, we have prepared self-paced education content so that you can learn about blockchain in a guided manner.

The content in this course and roadmaps are made by students for students and would not have been possible if not for these contributors:

  1. Adithya Narayan (NUS)
  2. Alicia Loke (NUS)
  3. Bernard Choo (NTU)
  4. Eddrick Tay (SMU)
  5. Jo-Adam Mizra (SMU)
  6. Nicholas Hong (SMU)
  7. Simon Teo (NUS)
  8. Shi Ying Tey (SUTD)
  9. Shyam Sridhar (SUTD)

The syllabus of the course is designed to guide you from an absolute beginner in blockchain technology to be able to work with smart contracts.

When you complete this course, there is an avenue for you to apply your knowledge with OpenNodes Campus Collective. This will grant you more opportunities to scope and create your own activities together with the Campus Leads. To do this, there is a test you have to complete. Try your hand at the challenge here.

Phase 0: The content is designed for people who have never heard about blockchain. When you are done with this phase, you should be conversant in blockchain content. This means you should be able to read and discuss blockchain content you find off the net.

Phase 1: Armed with some knowledge of blockchain, this segment will help you identify the suitability of use cases in blockchain and give you an idea of what the ecosystem is like.

Phase 2: Non-technical folks may try the challenge after completing this phase. It will teach you how to interact with the test net. You will learn how to come up with your own use cases for blockchain technology.

Phase 3: For the technical folks, this phase will teach you how to work with smart contracts. There will be just enough solidity taught for you to follow. The content is made so that you will learn major classes of smart contracts simplified for you to understand the logic. Thereafter, you may try your hand at the challenge.

Visual Grammar of this course

This course is put together with Google's Codelab tools for authoring and serving codelabs.

In addition to this self-paced content, we have also put together roadmaps for you too.

Blockchain Roadmap

For an overview of a typical blockchain skills journey, this roadmap is for you.

The basic idea of having the roadmap in place is so that you could get an idea on where you currently stand in your journey of self-discovery and chart out your course accordingly! How it works is that, you would first be expected to learn the fundamental concepts of Blockchain technology and Bitcoin. Once you have mastered this, you could go on to deep dive into Blockchain Development by learning the tools necessary to build apps on Ethereum, Hyperledger and Ziliqa! Just follow the path that eventually terminates to a terminal-node where you will be provided with the links to read more about the specific topic at hand.

Digital Consulting Roadmap

If you're non-technical and would like to have an idea of how technology consulting works, this roadmap is for you.

Check out what a typical process is like and chart out the key activities a consultant might do in a project. There are also useful resources for you to peruse if you'd like to pick up these skills.

What

All power is distributed and dispersed away from a central authority. Most financial and government systems currently in existence are centralized, which means that in such systems there exists a central authority that is incharge of maintaining and managing them.

There are several forms of decentralisation with varying degrees for each form leading to different properties and tradeoffs.

You can be decentralised in terms of architecture, culture, control, and decision-making.

This concept is fundamental to blockchain technology as it is its key value proposition for most use cases.

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.

Issues

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)

Why

  1. It allows for greater participation between non-trust parties
  2. Better reliability in your network as you do not rely on central intermediaries (uptime, processes etc)

How

Using the forms of decentralisation as a guide:

  1. Architecture

Bitcoin - Full nodes, Light Nodes.

Full Nodes

Light Nodes

  • Fully validates transactions and blocks.
  • Help the network by:
  • Accepting transactions and blocks from other full nodes
  • Validating those transactions and blocks
  • Relaying them to further full nodes.
  • Downloads the block header and not the entire blockchain only to validate the authenticity of the transactions
  • Makes use of a method called Simple Payment Verification (SPV) to verify transactions

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

  1. Culture

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.

  1. Control

Ethereum Foundation - incorporated as a Swiss non-profit at Zug, Switzerland. Provides Ethereum Grants that pay for teams that work on projects.

  1. Decision-making

Ethereum Improvement Proposals (EIPs) - are standards specifying potential new features or processes for Ethereum. EIPs contain technical specifications for the proposed changes and act as the "source of truth" for the community. Network upgrades and application standards for Ethereum are discussed and developed through the EIP process. This is currently how all decisions are made.

Which

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.

What

All interaction is made reliable by making it unchangeable by any single party, ensuring that activity records and network participation rules are agreed and kept until all necessary parties agree to change them.

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.

This makes digital goods act like real physical goods and not just a digital representation.

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.

Issues

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.

Why

  1. Sets forth a new paradigm by no longer requiring record-keeping intermediaries
  2. Overcomes the need for trust in records kept as you can verify independently

How

In Blockchain systems, the transactions that are verified are embedded with a time stamp that are secured through hashes. The hash of any new record links together and incorporates the hash of the previous record. Through this we get a chronological chain that joins each block.

Since the entire hashing process always includes the metadata of the previous block while generating a hash for any new block, an "unbreakable" link between the block and the chain is established. After this no further modifications [such as deletion and alteration] can be made to the block that has been included in the chain. If anyone does attempt this, subsequent blocks of the blockchain do not accept the modification as the hash of the block wouldn't be valid anymore.

In Summary:

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

Which

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.

What

The ability to hide the link between transactions and addresses are important due to privacy concerns

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.

Even though the blockchain is considered public and immutable, individual transactions can be anonymous.

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.

Issues

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.

Why

If privacy is implemented

  1. Potentially better control over personal data
  2. Information that requires secrecy can be conducted over public blockchains

If records are meant to be traceable

  1. Makes for a great audit trail for provenance
  2. Allows for fine analysis of actions that occurred over time by any peer
  3. Promote transparency

How

Making transactions traceable

Naive - you can just put your name on the transaction itself and you can be immediately traced to it.

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.

Which

Making transactions traceable

From "When to chains combine; supply chain meets blockchain" by Deloitte

Supply chains contain complex networks of suppliers, manufacturers, distributors, retailers, auditors, and consumers. A blockchain's shared IT infrastructure would streamline workflows for all parties, no matter the size of the business network. Additionally, a shared infrastructure would provide auditors with greater visibility into participants' activities along the value chain.

Traceability improves operational efficiency by mapping and visualizing enterprise supply chains. A growing number of consumers demand sourcing information about the products they buy. Blockchain helps organizations understand their supply chain and engage consumers with real, verifiable, and immutable data.

Making transactions untraceable

From the Princeton Bitcoin book

Nightfall - private transactions on the Ethereum blockchain using zk-snarks by EY

Here are some useful additional resources that you could use to pursue more information independently:

Useful YouTube Channels and Playlists:

The Best Blockchain Newsletters and News Sites:

Here is a Free, Open-Source Course on the Fundamentals of Blockchain Protocols: crytoeconomics.study

Other Useful Websites and Resources:

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.

If you would like to dive deeper into the technical specifications of Bitcoin check out this section right here!

This segment references from "Learn Blockchains by Building One" by Daniel van Flymen.

  1. The blockchain itself

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"
}
  1. Consensus algorithm

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"
  1. Interacting through an API

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
  1. Using the blockchain

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.

WWW and Web2

Before we delve into what web3 is, let us first understand the current developments in the internet space - WWW and Web 2.0.

The Internet and WWW provided a data transmission protocol – TCP/IP – that made the transfer of data between any two remote computers faster and massively reduced the transaction costs of information exchange. Ten years later, the Internet became more mature and programmable. We saw the rise of the so-called Web2, which brought us social media and e-commerce platforms. The Web2 revolutionized social interactions, bringing producers and consumers of information, goods, and services closer together, and allowed us to enjoy P2P interactions on a global scale, but always with a middleman: a platform acting as a trusted intermediary between two people who do not know or trust each other. While these platforms have done a fantastic job of creating a P2P economy, with a sophisticated content discovery and value settlement layer, they also dictate all rules of the transactions, and they control all data of their users.

Need for Web3: Sticking it to the MiddleMan

By now, most of you might have connected the dots between the previously covered topics in this course and why we need Web3. The answer is plain and simple: remove the middleman in transactions. This has the effect of decentralisation, privacy, no single point of failure and many more as covered in the previous sections.

Currently, the internet uses a centralised system where clients send requests to a server.

In the Web3, data is stored in multiple copies of a P2P network (read: blockchain). The management rules are formalized in the protocol and secured by majority consensus of all network participants, who are incentivized with a native network token for their activities. Blockchain, as the backbone of the Web3, redefines the data structures in the backend of the Web. It introduces a governance layer that runs on top of the current Internet, that allows for two people who do not know or trust each other to reach and settle agreements over the Web.

Accessing Web3: Another dark web?

Some of you might think that just a web browser like Chrome/Safari might be sufficient to access the data and write to the blockchain. Others might think that you need to install something special like TOR. Well, actually, it's somewhere in the middle.

Since you need an account to sign and transact on the blockchain, you need a wallet. MetaMask is a very popular Chrome extension crypto wallet. It is your gateway into dApps. Make sure you install it to enter the deep dark world of Web3! There are other wallets too, like Dapper, but MetaMask is the most famous one and we recommend that you use MetaMask for this course.

MetaMask stores all your blockchain network tokens and sometimes spends your tokens to write to the blockchain (reading doesn't require spending any tokens, we will cover that in our gas optimization topic). You cannot interact with the blockchain without a wallet. Your wallet and your wallet address is basically your anonymous identity on the blockchain!

A decision model is a formal system used for perceiving, organizing, and managing the business logic behind a business decision. We will now break down the IBM model to evaluate whether or not one should use a blockchain.

The main idea that the entire model encapsulates, is that a problem to which blockchain would be a possible solution is one that:

In this section we'll do a simple teardown on two important academic papers:

  1. SoK: Blockchain Technology and Its Potential Use Cases - even though it is currently a draft, the paper is comprehensive in breaking down the comparative benefits of utilising blockchain technology
  2. SoK: Research Perspectives and Challenges for Bitcoin and Cryptocurrencies.

SoK: Blockchain Technology and Its Potential Use Cases (2019)

Ruoti, S., Kaiser, B., Yerukhimovich, A., Clark, J., & Cunningham, R. (2019). SoK: Blockchain technology and its potential use cases. arXiv preprint arXiv:1909.12454.

Abstract

This paper mainly aims to answer the questions:

It uses grounded theory to achieve this, identifying data and processes within qualitative data sources generated by humans and filled with imprecise terminology and descriptions.

Key Contributions

The technical properties for Blockchain Technology are defined which form the basis for its capabilities. Through participation in the shared governance and operations of the system, the group of individuals can be rest assured that the system is being operated properly. This is guaranteed through the validation rules which culminate in the consensus protocol for the distributed ledger.

The distributed ledger provides a verifiable state for the participants which accurately reflect their transactions.

It also provides resilience to data loss by providing redundancy.

Blockchain Technology is commonly touted to have several different capabilities. This figure helps us make sense of the technical properties and primitives that lead to these capabilities. Blockchain's 3 core capabilities are the shared governance and operations, verifiable state, and resilience to data loss. An additional 11 capabilities are identified that can be achieved on or off-chain. Blockchain use cases develop from the creation of value from these capabilities.

You might then be wondering to yourself how does blockchain compare to other types of decentralized databases. This paper provides a flowchart that helps you to understand this based on 4 key questions.

Finally, the paper illustrates some normative properties for Blockchain Technology and illustrates the dependencies to achieve these properties. However, the paper also provides a sobering reminder of the reality of some of these properties.

While public participation is certainly possible with Blockchain technology, as there are many examples of systems built using Blockchain technology that fail to achieve these properties. For example, while Bitcoin initially had a low-cost to participate allowing easy-of-entry for miners and community ownership, that is no longer the case as any meaningful participation requires the purchase of a large amount of specialized hardware and the expenditure of a significant amount of electricity.

To conclude, here's an enumeration of the different use cases for blockchain technology:

  1. Financial
  1. Electronic currencies and payments
  2. Asset trading
  3. Markets and auctions
  4. Insurance and futures
  5. Penalties, remedies, and sanctions
  1. Data storage and sharing
  1. Asset tracking
  2. Identity and key management
  3. Multi-organization data sharing
  4. Tamper-resistant record storage
  1. Others
  1. Voting
  2. Gambling and games
  3. IoT and smart property

These use cases apply a combination of the normative properties of blockchain and its core capabilities. A full explanation is given in the paper and it generally follows the format of use case > challenge at hand > core capability helping to resolve the challenge > limitations.

SoK: Research Perspectives and Challenges for Bitcoin and Cryptocurrencies (2015)

SoK: Research Perspectives and Challenges for Bitcoin and Cryptocurrencies. 2015. 104-121. 10.1109/SP.2015.14.

Abstract

This paper:

One point that we find quite interesting is that this is a pre-ethereum paper which was published in 2015

Now we will break down some of the key phrases that the paper highlights which will form the backbone for further research studies.

We will now go over the two strategies that give miners, who don't purely derive utility only through mining rewards, an advantage.

In this section, for this paper we have just summarized and broken down what we believe are potential research questions that this paper highlights. You are highly encouraged to read the original paper and do a breakdown of your own.

This Blockchain Landscape Map captures all the corporates, startups, government agencies and academic institutions that are working towards improving or adopting blockchain technology.

The map first organises these bodies into those that are working towards:

The ‘Application' segment is further divided into different subsections based on their blockchain use cases. These subsections are:

In this next section, we will introduce you all to the website State of the DAppswhich can be used to check the status of currently launched decentralized applications. Similar sites such as DappRadar and Dapp.com also exist, but in this section we'll mainly turn our attention to the SOTD.

SOTD indices:

The website also provides its ranking of various DApps using a ranking algorithm based on weighting 14 different factors that include active users, transaction, development activity, profile freshness and strength, click-through-rate, user recommendations and feedback.

Do visit the website and have a look at it yourself!

Using Remix

Remix will be your main IDE for today. We use it because it's convenient to compile and test your contracts out quickly.

Ropsten

To gain some experience of 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.

Before learning how to code/execute a smart contract, you should first understand what is a smart contract, what are some limitations and how it is used in real life scenarios.

What is a smart contract?

A smart contract is a computer protocol intended to digitally facilitate, verify, or enforce the negotiation or performance of a contract. Once the terms of the contract has been fulfilled by the parties, the smart contract will self-execute. Clauses and data of the smart contract will have to be digitally programmed and fed into the blockchain by a trusted third party, known as the oracle. These oracles are often linked to the Internet of Things (IoT) connected devices, which allow the automated monitoring of data.

The beauty of the decentralized nature of blockchain based peer to peer transactions means that these smart contracts don‘t require anything in the way of a middleman. Traditionally individuals or institutions such as banks and solicitors were required to facilitate legally binding contracts. Smart contracts allow individuals to bypass these costly and often slow middlemen and agree on a contract directly. (https://www.devteam.space/blog/10-uses-for-smart-contracts/)

(https://www.devteam.space/blog/10-uses-for-smart-contracts/)

(https://www.researchgate.net/publication/336369143_Smart_Contracts_Implementation_Applications_Benefits_and_Limitations)

(https://www.thedigitalenterprise.com/op-ed/smart-contracts-real-life-use-cases/)

Smart contracts are gaining the most traction in the finance industry and many financial institutions are adopting blockchain and smart contracts to simplify and digitize their process. The Swiss Bank, UBS has recently adopted the use of blockchain and smart contracts to create a self-paying instrument which will be risk-free and is an available payment stream for the unbanked people. This kind of micro contracts can enable microfinancing which can help in the payment of everyday goods.

The healthcare industry faces several issues pertaining to data conservation and patient privacy. With the use of Smart contracts, middlemen can be eliminated. Applicature, a blockchain development application was developed through smart contracts to foster data integrity, transparency and secure record of patients' health data.

An example from the Music Industry is Inmusik, a music streaming platform that decentralizes revenues and has a proper allocation of revenues to the respective artists, solving one of the common issues in the music streaming industry. A common issue faced in the music industry is that revenues fail to reach the content creators' hand due to centralization, lack of transparency over ownership of the content, royalties payment and many more. With the help of smart contracts, Inmusik enables the validation of the ownership of a song through a transparent tagging system. As a result, the party who creates the track gets the right portion of fees allocated from the royalties.

(https://coinswitch.co/news/5-real-world-smart-contract-application-examples-smart-contract-reviews-2020)

These are some of the many few real examples that industries and companies have adopted smart contracts to simplify and digitize their operations, introducing transparency and accuracy which reduces cost. Really, smart contracts will soon be used in your everyday life.

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.

  1. Choose a smart contract from State of the dapps
  2. Decompile it with eveem by Tomas Kolinko
  3. Understand the decompiled code, formatting it properly might help!
  4. Discuss with your partner what this smart contract does
  5. Share with the class

We analysed a decompiled auction contract from OpenSea.

Simply by looking at the decompiled code, we noticed:

  1. Each auction was a struct
  2. How require works
  3. Some basic checks on deciding price upper and lower limits
  4. Assigning values to variables
  5. Event logging
def 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))

This information presented in this section is obtained from the paper: Issuing and Verifying Digital Certificates with Blockchain, which can be accessed here.

The problem:

The solution that is being proposed: UniCert (Based on UniCoin, a digital currency built on blockchain technology)

Existing solution: Blockcert

How does UniCerts work?:

Step 1: Collection of necessary information for the certification process

Step 2: Hashing of the certificate and storing it in the metadata of the transaction. The hashing is carried out using the Merkle Tree hashing algorithm

Step 3: Pushing of transactions into UniCerts thereby letting it join the validation process

Step 4: Receiving the certificate's hash as an identity number once the transaction validations is complete

Step 5: Retrieving the original certificate wherever necessary which requires the identity number

How does this address the existing issues revolving around the current system:

At this point, you are highly encouraged to go back and do your own teardown of the paper and understand the UniCerts and UniCoin architecture by yourself! This section covers the rough overview of the project and does not go into the architecture.

Other exciting blockchain focused solutions to this problem that you are encouraged to check out are: 0xcert and OpenCerts.

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;

  1. Infura, a hosted Ethereum node cluster that lets your users run your application without requiring them to set up their own Ethereum node or wallet
  2. Web3js, make requests to an individual Ethereum node with JSON RPC in order to read and write data to the network
  3. HDWalletProvider, for account management process required when signing transactions on the blockchain such as metamask

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:

Below is a short walkthrough of truffle commands for creating and setting up your project.

1. truffle init : generates sample files for solidity contracts

2. edit solidity smart contracts

3. truffle compile : compiles .sol into .json format

4. ganache-cli : starts ganache-cli, make sure ganache-cli is already installed (this step is only for deployment on ganache, else skip this step)

5. create a 2deploycontracts.js file with code to export module

6. update truffle.js configuration for module.exports networks

7. truffle migrate (--network ropsten) (--reset): deployment of contracts on ganache/ ropsten / redeploy contract even though one with the same solidity code has been previously deployed.

- truffle console : console to run web3apis to interact with web3 JS apis (link below) (e.g. web3.eth.getTransactionReceipt or calling functions from the smart contracts by using

Bounties.deployed().then(function(instance) {...})

If you haven't decided on a code editor, use Visual Studio Code! There's dark mode, syntax highlighting is pretty and there is a decent Solidity Syntax Plugin by Juan Blanco for Visual Studio Code you can download which 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'll are sitting in broken code. When in doubt, google! You'll be likely directed to issues on github pages or stackoverflow and you can follow their instructions.

But what should i do if i don't know where the problem is? It goes without saying that a good understanding of would be very beneficial to the debugging process. I highly recommend getting started with the articles on the ethereum developers page where just the introductory section is enough to get a good understanding of how the compilation and deployment process works. The truffle links they provide are also helpful if you are looking to write your projects in truffle. Here's the page: https://ethereum.org/developers/

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.

Sometimes there is more than one problem. 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:

Today we will learn about Ethereum development through working with several smart contracts and dapps. Each segment is separated into:

  1. Deploy the smart contract and explore every function
  2. Logic behind the working of the smart contract
  3. Syntax of the smart contract with explanations
  4. Discuss if the smart contract fulfills its intended objectives well, how it can do better in terms of gas use and security, and how one might induce unintended behaviour in the smart contract.
  5. Develop based on our earlier discussion, new functions or fixes for the smart contract
  6. Digest what you've just learned by searching for other real-life examples

Before we dive into writing our smart contract, let us take a quick glance at the syntax of solidity itself. We assume that you have some programming experience before and understand the syntax of any popular pre-existing language as we will be drawing similarities.

Keyword

Meaning

contract

A smart contract object. Somewhat similar to a class as it contains data and logic inside functions but that's where the similarities end. (For more advanced programmers, contracts can be visualised as a singleton object hosted on the public blockchain and hence constructor is called once per deployment and not once per object creation)

import "Contract.sol";

Import statement similar to other languages.

function functionName(<parameters>) <modifiers> returns (<returnType>){

Create a function inside a contract that can be called

uint

Unsigned Integer type with 256 bits

uint8, uint16 ... uint256

Unsigned integer with number of bits.

string

String type

bool

Boolean type

mapping(keyType => valueType)

A key, value pair. Similar to Dictionaries or HashMaps

if (condition) {}

Similar to if in most languages

else

Similar to else in most languages

for

Similar to for in most languages

Welcome to your first ever smart contract. Since it is your first time, we thought you would need some hand-holding and decided to walk you through all the steps involved in deploying the smart contract to the Ropsten Test Network.

Deploy

Mortal Greeter is the "Hello World" equivalent to familiarise users with smart contracts via remix GUI.

We have provided the Mortal/Greeter Smart Contract Code below for you to test and deploy the contract.

  1. Open the Remix IDE

You should see something similar to this! This is your IDE for basic smart contracts! Play around and get(h) used to it.

  1. You can customise your solidity compiler by choosing the solidity plugin. You can change it to 0.6.6 as that's the version we will be using throughout this course.
  2. Once you're familiar with the IDE, create a new file named Greeter.sol and paste the smart contract code into it (available below).
  3. Save the file once and you should see it compile automatically. Make sure you see the green tick next to the solidity compiler which signifies a successful compilation.
  4. Head to the deploy and run transactions (the one below solidity compiler on the left nav bar). Change the environment to Injected Web3 (this is to make sure that you deploy to the actual blockchain and not a temporary local one. You will most likely get a MetaMask prompt and make sure that you connect your account on the ropsten network!
  5. Leave the gas limit and value as is (tweak if you know what you're doing) and go down to the deploy section.
  1. Choose a greeting string for the constructor. Remember, once you choose a word, you cannot change it as it is permanently stored on the blockchain! Hit deploy. Confirm your transaction with MetaMask.
  2. Once the transaction has been mined (you will get a notification from MetaMask). You can see the address of the deployed contract! Hurray! You've deployed your first smart contract on the Ropsten Ethereum Network! It is ingrained forever in the blockchain!

  1. You can call the methods using the Remix GUI by clicking on the method. You can see that it says "Hello OpenNodes!" just like we wanted it to. You can even copy your contract address and check it out on Ropsten Etherscan!

Logic

Code

pragma solidity ^0.6.6;
/* the above line specifies the version of the solidity compiler that */
/* this program is compatible with. */

contract Mortal {
    /* Define variable owner of the type address */
    /* address is a type that stores wallet and contract addresses */
    address owner;

    /* This constructor is executed only once at deployment and sets the owner of the contract */
    constructor() { owner = msg.sender; }

    /* Function to recover the funds on the contract */
    /* notice how function access modifiers are specified after the name */
    function kill() public { 
        if (msg.sender == owner) 
            selfdestruct(msg.sender); 
    }
}
/* Another contract Greeter which inherits from Mortal */
/* Inheritance in Solidity is similar to OOP where */
/* all the methods and data members (that are not private) are */
/* accessible in the subclass */
contract Greeter is Mortal {
    /* Define variable greeting of the type string */
    string greeting;

    /* This runs when the contract is deployed */
    /* 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 that can be called by people */
    /* notice how data is returned */
    function greet() public view returns (string memory) {
        return greeting;
    }
}

Further Points to note on the Syntax

Discuss

  1. What do you think of the deployment process?
  2. What happens if you want to change the greeting?

Digest

Before you get all excited, we will be making a MetaEther/MyEther. Our own currency backed by the blockchain/smart contracts. There are a few standards for tokens like ERC20 and ERC721. ERC20 tokens hold the same value and are interchangeable (a good fit for a currency).

Deploy

Before we get into deploying our own currency, let us take a look at the standards. OpenZeppelin has a list of smart contracts that are tried and tested and they've made it open-source for all of us to use. Yay! They have contracts like SafeMath, ERC20 and Ownable that are widely used. Most production-ready smart contracts usually inherit from one of the OpenZeppelin contracts. You can take a look at all their contracts here. We will be using their ERC20 Contract to make our own ETH.

Ownable is used when contracts have an owner who has admin privileges that normal users don't. SafeMath is used to prevent overflows during math operations.

Logic

This section is a little light on coding. The ERC20 Standard Interface and its implementation is given below (a shorter version of the OpenZeppelin Contract). We will just inherit it, in our contract.

Code

Below is the syntax for your contract.

pragma solidity ^0.6.0;

import "./openzeppelin/ERC20.sol";

contract MyETH is ERC20 {
    constructor(uint256 initialSupply) public ERC20("MyETH", "METH") {
        _mint(msg.sender, initialSupply);
    }
}

Code for the ERC20 Implementation given for your reference. Give it a read. Make sure all your imports are in the right structure.

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "./Context.sol";
import "./IERC20.sol";
import "./SafeMath.sol";

contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    constructor (string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;
        _decimals = 18;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _setupDecimals(uint8 decimals_) internal {
        _decimals = decimals_;
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}

Discuss

Make sure you go through the Ownable Contract as most contracts in deployment inherit from that!

Let us take a look at a more advanced smart contract which has a real-life use case.

Deploy

You are able to send Ether to your agreement, set new terms, and withdraw Ether if an agreement has been violated.

Logic

We are going to model the idea of roommates creating agreements with each other and claiming disputes when the other violates said agreement. It can be visualised as so. This is similar to a normal contract and we are going to make it "smart".

Code

pragma solidity ^0.6.6;
pragma experimental ABIEncoderV2;

contract RoomieAgreement {
    
    // Events are to pass information when something happens on the blockchain to your front-end
    // Your front-end can listen/watch for these events
    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
    // Note that the public modifier only enables public read and not public write
    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
    // Notice the use of msg.value
    // We are not passing the value of money through the parameter
    // But taking it from the transaction which calls the function
    function addDeposit() public payable {
        //require checks a condition. if it fails, it kills the current transaction and reverts
        require(msg.value > minDeposit);
        // mapping 
        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[] memory) {
        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 memory _agreementTerms, uint _roomieId) public {
        require(roomies[_roomieId-1] == msg.sender);
        agreementId++;
        // array.push is used to add element at the end of dynamic array
        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 {
        // notice the use of payable again
        address payable claimant = msg.sender;
        require(roomies[_roomieId-1] == msg.sender);
        require(_dispute <= agreementId);
        disputeId++;
        string memory 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);
    }
}

Further Points to note on the Syntax

Discuss

  1. Privacy - you have put your contract address on the blockchain, how about private voting?
  2. Fake claims - your roomie can essentially just claim all the money anytime
  3. Lack of adjudication - how do you know who was right or wrong in a dispute?
  4. Gas guzzling - are there more efficient ways to write this contract?
  5. Game theory - how do you incentivise someone to not abuse the claiming function?
  6. What is wrong with the Dispute function? How can we do it better?

Develop

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;
    }

Note: You compare strings in solidity by comparing their hashes as shown above. If you want to check if two strings are equal, use keccak256(abi.encodePacked((_name))) == keccak256(abi.encodePacked(("NAME")).

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

Digest

Deploy

This is a message board that can hold up to 10 messages and allows you to post messages. Same process for deployment as the previous contracts.

Logic

Code

pragma solidity ^0.6.6;
//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 has User type member
    struct Message {
        string text;
        User _user;
    }
    
    Message[10] messageBoardLogs;
    
    //8 bit unsigned integer (so no negative values).
    uint8 public messageCount = 0;
    
    //mapping is a key value store. We are specified type of key and value
    mapping (address => User) users;
    
    //Declaring an event
    event MessageCreated (string text, address _address, string name, bool isValue  );

    //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;
    }
    
    // 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;
    }

    
    //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");
        messageCount++;
        emit MessageCreated(_text, users[msg.sender]._address,users[msg.sender].name,true);
        messageBoardLogs[messageCount] = Message(_text, users[msg.sender]);
        return true;
    }
    
    function readMessage(uint _messageIndex) view public returns (string memory){
        require(_messageIndex<=messageCount,"message index out of range");
        return messageBoardLogs[_messageIndex].text;
    }

}

Discuss

Develop

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;
    }


}

Is it possible to use dynamically sized arrays?

Beginner: what are some ways that people have used blockchain to share messages?

Digest

Deploy

This contract simulates a simple voting process. There are 2 candidates you can vote for.

Logic

Each voter (by voter, we mean a single wallet address account on ethereum) can cast a single vote to a single candidate. A user can only vote once (this is ensured using a require).

Code

pragma solidity ^0.6.6;

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);
    }
    
}

Further points on the syntax

Discuss

Develop

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?

Digest

Deploy

Rinse and repeat. Make sure you change the ** to the address of admins so that only the admins can change the answer.

Logic

The aforementioned admins can change the answer (which is stored in a bytes32 variable, bytes32 is an encoded character list). Once a user is registered, he can update his status (his reply to the question). If his status matches the set answer, the user gets points.

Code

pragma solidity ^0.6.6;

contract Users {
    // bytes32 can be visualised as an encoded string    
    bytes32 answer;
    
    // data structure that stores a user
    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
    // modifiers will be explained in detail in the further points section
    modifier checkSenderIsRegistered {
            require(isRegistered());
            // use _ to return to the calling function
      _;
    }



    /**
     * Constructor function
     */
    constructor() public {
        // 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;
        users.push(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(users.length - 1);

        return users.length - 1;
    }



    /**
     * 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;
    }

}

Further points on the syntax

Discuss

Digest

Develop

How can you make something similar? Our challenge is built along the ideas of this smart contract.

Process of creating a dapp (beginners)

Congratulations! You have reached the end of this guide.

The University ecosystem in Singapore, our blockchain research collective.

The blockchain industry in Singapore, OpenNodes.

Are you up for the challenge?