Skip to content

OpenElements/voting-contract

Repository files navigation

The voting contract tutorial

This project is a tutorial for the Hedera hashgraph and Java. The tutorials gives an overview how smart contracts can be deployed and used on the Hedera hashgraph.

voting

The tutorial was part of a workshop at WeAreDevelopers 2023 by Michael Heinrichs and Hendrik Ebbers. The slides of the workshop can be found here here.

Prerequisites

The tutorial is based on Java 17. No additional tools are needed to execute the tutorials. Maven is used as buildtool for the project but since the Maven wrapper is part of the project a local Maven installation is not needed.

Once you have checked out the project you can build it by running the following command from the root folder of the project:

./mvnw verify

Internals

The tutorial is using the Hedera Java SDK and web3j. All functionality that is generic (like calling a smart contract) is done by using web3j that acts as a generic Java library to interact with public ledgers like Hedera or Ethereum. The Hedera Java SDK is used to interact with the Hedera network if special functionality is needed or not implemented by web3j.

Tutorials

The tutorials are split into different parts. The goal is to understand what a smart contract is, how it is deployed in a public ledger and how it can be used.

1. Hedera hashgraph

Hedera provides 3 different Networks: The Mainnet, the Testnet and the Previewnet:

  • The Hedera Mainnet (short for main network) is where applications are run in production, with transaction fees paid in HBAR. Transactions are submitted to the Hedera mainnet by any application or retail user; they're automatically consensus timestamped and fairly ordered. All nodes of the main network are run by Council members of the Hedera Foundation like Google, IBM, Deutsche Telekom, and others.
  • The Hedera Testnet is a public network where developers can test their applications and smart contracts. The testnet is a fully functional Hedera network, with all the same features as the mainnet. The testnet is a great place to test your application before deploying it to the mainnet. The testnet is also a great place to test your smart contracts before deploying them to the mainnet.
  • The Hedera Previewnet is a public network where developers can test their applications and smart contracts. The previewnet is a fully functional Hedera network, with all the same features as the mainnet.The Hedera previewnet is designed to offer developers in the Hedera community early exposure to features coming down the pipe. It’s not always stable and accounts / data are likely to be lost when the network codebase is upgraded.

instanzen

In the tutorial we will use the Testnet. To use the testnet you need to create an account on the Hedera portal. A step by step guide can be found here.

Once an application is ready you will normally deploy it on Mainnet. A step by step guide can be found here.

Note You can even setup a network on your local machine. This is super useful for testing and development. You can run integration tests automatically against such local network or use it to test your smart contracts when you are offline and can not use the testnet. The local network is open source and created based on Docker containers. You can find the source code on GitHub.

2. Writing a Smart Contracts

The first part of the tutorial is about smart contracts. We will show how a smart contract can be created by using Solidity and how it can be deployed on the Hedera hashgraph.

A smart contract is a computer program that is running on a public ledger. One objective of smart contracts is the reduction of need for trusted intermediators since the code is running in the ledger and can not be mutated by any party.

The smart contracts that we will use are all written in Solidity. Solidity is the most common language for creating smart contracts. Smart contracts are compiled to EVM bytecode that can be executed on the Ethereum Virtual Machine. You can think about this as a virtual machine that is running on the public ledger. The EVM bytecode is then deployed on the public ledger and can be executed by anyone. The Hedera hashgraph is using the same EVM bytecode as Ethereum. This is why we can use the same smart contracts on both ledgers and interact with them by using web3j.

smart-contract

A "Hello World" smart contract is shown below:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    function helloWorld() public pure returns (string memory) {
        return "Hello, World!";
    }
}

A full documentation of the Solidity language can be found on the official documentation. Therefore we won't go into detail about the language in this tutorial.

Such contract can be added to the src/main/solidity folder of the smart-contract module of the project. All contracts will be compiled by doing a maven build:

./mvnw verify

The maven build will not only compile the smart contracts but also generate the Java wrapper classes that can be used to interact with the smart contracts. Internally the Solidity compiler is used that will create 2 outputs for each contract:

  • a .bin file that contains the EVM bytecode of the contract (see target/generated-sources/bin/**.bin)
  • a .json file that contains the ABI of the contract (see target/generated-sources/abi/**.json)

The ABI contains the public interface (the API) of the smart contract and is used to create the Java wrapper. All wrapper classes are generated in the target/generated-sources/java folder.

The generated Java wrapper classes can be used to interact with the smart contract. Before we have a look at how we can use the smart contract we need to deploy it on the Hedera hashgraph. To deploy a smart contract we need to upload the compiled binary of the contract to the Hedera network.

3. Deploying a Smart Contract

The smart contract that we have created in the previous step can be deployed on the Hedera hashgraph. To do so we need to authorize ourselves against the network. This is done by using a Hedera account that we have created. The account information can all be found at the Hedera portal. The account information must be stored in the .env file in the root folder of the project. Once the information is copied the file looks like this (all keys are invalid samples):

HEDERA_ACCOUNT_ID=0.0.53854625
HEDERA_PRIVATE_KEY=3030020100300706052b8104000a04220420c23ff08c429aa5a1d80bb300f436dd89adc5a4aa9a4544d7f3b00b2045c6cc37
HEDERA_PUBLIC_KEY=302d300706052b8104000a03220003cd09e0aaafe0d4a7c602f581aa00202c5aa4ffbae9b96f479fc1db36f4594a17

In Java we use the java-dotenv library to read the environment variables from the .env file. The following code shows how we can read the account information from the .env file:

final String accountIdValue=Dotenv.load().get("HEDERA_ACCOUNT_ID");
final String privateKeyValue=Dotenv.load().get("HEDERA_PRIVATE_KEY");

The project contrains the ContractDeploymentUtils class that contains all the generic functionality to deploy a smart contract. The following sample shows how it can be used to deploy a smart contract:

final String contractName="HelloWorldContract";
final String accountIdValue=Dotenv.load().get("HEDERA_ACCOUNT_ID");
final String privateKeyValue=Dotenv.load().get("HEDERA_PRIVATE_KEY");

final AccountId accountId=AccountId.fromString(accountIdValue);
final PrivateKey privateKey=PrivateKey.fromString(privateKeyValue);
final byte[] byteCode=ContractDeploymentUtils.readBin(contractName);

ContractDeploymentUtils.deploy(HederaNode.TESTNET,accountId,privateKey,byteCode);

By executing the code the compiled smart contract will be deployed on the Hedera testnet. The id of the deployed smart contract will be printed to the console:

contract successfully created with FileId '0.0.3818143' and ContractId '0.0.3216587'

Once this is done we can interact with the contract by using its unique id.

Note The project contains the deploy-hello-world-contract.sh script that can be called to deploy the smart contract by calling the HelloContractDeployment java class. The script can be used if you have no Java IDE.

4. Interacting with a Smart Contract

The smart contract that we have deployed in the previous step can be interacted with by using the generated Java wrapper. To do so we need to store the id of the deployed smart contract. The ContractConstants interface can be used to store the id of the deployed smart contracts.

To interact with the smart contract we will use the wrapper class that has been generated by the maven build. The wrapper class provides Java functions for each function of the smart contract. By doing so it is really easy to call a method. But before we can do that we need to introduce gas and gas price. Gas is the fee that is paid to the network to execute functionality like calling a function of a smart contract. The gas price is the price that is paid for each unit of gas. Internally the EVM has a gas price defined for each operation (like a simple multiplication). Whenever we call a smart contract function we need to pay the gas price for each operation that is executed. To not become bankrupt by calling a function that has a bug and ends in an infinite loop we need to limit the amount of gas that can be used to execute the function. With web3j we do this by defining a GasProvider. The following code shows how we can define such provider in a simple way:

final BigInteger gasPrice=BigInteger.valueOf(20_000_000_000_000L);
final BigInteger gasLimit=BigInteger.valueOf(500_000L);
final StaticGasProvider staticGasProvider=new StaticGasProvider(gasPrice,gasLimit);

Now that we have definded the gas limit we have everything together to call the function of the deployed smart contract. The following code shows how we can call the say_hello method of the smart contract on testnet:

final HederaNode node=HederaNode.TESTNET;
final PrivateKey privateKey=PrivateKey.fromString(Dotenv.load().get("HEDERA_PRIVATE_KEY"));
final ContractId contractId=HELLO_WORLD_CONTRACT_ID;

final Web3j web3j=HederaUtils.createWeb3j(node);
final Credentials credentials=HederaUtils.toCredentials(privateKey);

final BigInteger gasPrice=BigInteger.valueOf(20_000_000_000_000L);
final BigInteger gasLimit=BigInteger.valueOf(500_000L);
final StaticGasProvider staticGasProvider=new StaticGasProvider(gasPrice,gasLimit);

final HelloWorldContract statefulContract=HederaUtils.createContractWrapper(node,
        web3j,
        credentials,
        HelloWorldContract.class,
        contractId,staticGasProvider);

        logger.info("Greeting method result: {}",statefulContract.say_hello().send());

Note The project contains the call-hello-world-contract.sh script that can be called to call the "Hello World" smart contract by calling the HelloContractCall java class. The script can be used if you have no Java IDE.

5. Creating a contract for voting

TODO

Note The project contains the deploy-voting-contract.sh script that can be called to deploy the smart contract by calling the VotingContractDeployment java class. The script can be used if you have no Java IDE.

6. A simple ui for interacting with contracts

The backend module of the project contains a spring boot application that provides a simple web ui to interact with smart contracts. The ui has several views while each view represents a single functionality of a contract like voting or authorizing an account to vote.

TODO: How to start the backend.

Note The project contains the start-backend.sh script that can be called to start the backend by calling the Application java class. The script can be used if you have no Java IDE.

Once the backend is started it can be reached at localhost:8080. backend-view

7. How the backend calls smart contracts

TODO