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:
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.
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.
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.
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.
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.
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.
Full Nodes | Light Nodes |
|
|
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) - 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.
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.
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.
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.
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
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.
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.
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.
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
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.
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.
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.
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.
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:
Ruoti, S., Kaiser, B., Yerukhimovich, A., Clark, J., & Cunningham, R. (2019). SoK: Blockchain technology and its potential use cases. arXiv preprint arXiv:1909.12454.
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.
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:
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. 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.
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.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.
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.
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))
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;
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.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:
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 |
| 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 statement similar to other languages. |
| Create a function inside a contract that can be called |
| Unsigned Integer type with 256 bits |
| Unsigned integer with number of bits. |
| String type |
| Boolean type |
| A key, value pair. Similar to Dictionaries or HashMaps |
| Similar to if in most languages |
| Similar to else in most languages |
| 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.
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.
You should see something similar to this! This is your IDE for basic smart contracts! Play around and get(h) used to it.
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
storage
and memory
.msg.sender:
Each function call can access the address of the caller of the function through this variable.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).
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.
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.
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 { } }
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.
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 storedWe 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".
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
emit
keyword.msg.value
.address(this).balance
) and you can also transfer funds from the contract to an address using transfer. Make sure the address is payable too!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
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.
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; } }
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?
This contract simulates a simple voting process. There are 2 candidates you can vote for.
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).
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
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?
Rinse and repeat. Make sure you change the ** to the address of admins so that only the admins can change the answer.
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.
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
How can you make something similar? Our challenge is built along the ideas of this smart contract.
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 contractsCongratulations! 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?