A web3 "Hello world" with Solidity

Table of Contents

Introduction

This note documents how to deploy a basic "Hello world" type smart contract on Ethereum. It covers setting up a connection to an Ethereum testnet, compiling and deploying a simple smart contract, and interacting with this smart contract.

Software

The two packages that we'll use are the geth Ethereum client and the solc compiler for the Solidity smart contract language. This tutorial assumes Debian linux but the instructions should port over to other settings as well.

geth (go-ethereum) is an Ethereum client written in go and with source available on github https://github.com/ethereum/go-ethereum. You can compile it from source or alternatively download a binary from from https://geth.ethereum.org. In my case I used the latest binary (version 1.9.15 at the time of this writing):

$ wget https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.15-0f77f34b.tar.gz
$ tar xvzf geth-linux-amd64-1.9.15-0f77f34b.tar.gz       

Solidity is a programming language for smart contracts. It uses a curly-bracket syntax that will be familar to C/C++/Java developers. One of the compilers you can use for solidity is solc which is implemented in C++. For ease of installation, we will use a variant of solc known as solcjs. The solcjs package is installed using npm (download node and npm in one package from https://nodejs.org/en/.)

$ npm config set prefix=$HOME/node
$ npm install -g solc
$ export PATH=$HOME/node/bin:$PATH

To test that solcjs is installed correctly, execute the following:

$ solcjs --version
0.6.11+commit.5ef660b1.Emscripten.clang

Example code

Here is the source code of hello.sol This contract simply stores a public string that can be read or updated by anyone.

pragma solidity ^0.6.11;

contract HelloWorld {

    string public message;

    constructor(string memory initMessage) public {
        message = initMessage;
    }

    function update(string memory newMessage) public {
        message = newMessage;
    }
}

The string is stored in a public member variable called message. It is initialized by the constructor function when the contract is first deployed. Subsequently, it can be read and it can also be updated by calling the member function update.

Compiling the smart contract

We compile hello.sol in two parts. In the first part we generate an ABI file, which is essentially a header file that describes the methods and variables of the contract and will be useful for us (or other smart contracts) to interact with it later.

$ solcjs hello.sol --abi

This creates the file hello_sol_HelloWorld.abi. Next, we compile the smart contract to binary:

$ solcjs hello.sol --bin

This creates the file hello_sol_HelloWorld.bin.

Launching the ethereum client and get some free (testnet) Ether

We will connect to the Goerli ethereum testnet get some free "eth" from a faucet. From the geth directory, run the following command, replacing the datadir argument with your desired location for storing the blockchain data.

$ ./geth --goerli console --syncmode "light" --datadir /media/usb/testnet --port 30304

In this command, we are directing geth to connect to the Goerli testnet (to avoid transacting in real eth) using the "light" mode of synchronization with the blockchain. Although we could interact directly with geth using the process we launched, this is somewhat inconvenient since geth prints frequent status messages. Hence we are going to open another terminal and run another instance of geth that will attach to the already running instance:

$ ./geth attach  --datadir /media/usb/testnet

You will see a > symbol which indicates that geth is ready to accept commands. This is an interactive JavaScript console that lets you perform account management and also invoke methods from the web3.js library. The first commands are going to execute are to make an account:

> acct = personal.newAccount("")
> eth.defaultAccount=acct
> acct
"0xA....." (your account shows here)

If you check your balance at this point, you'll see it is empty.

> eth.getBalance(acct)
0

We will need some Ether to deploy our smart contract and more generally make any transactions that can change the state of the blockchain. We can get some free Ether on this testnet by using the Goerli faucet at https://goerli-faucet.slock.it/. Go there and enter your address to get some.

After a few minutes you should be able to see a non-zero balance in your account

> eth.getBalance(acct)
50000000000000000

Deploying the smart contract

To submit the smart contract, follow these steps. First we tell geth about the abi. Take the contents of hello_sol_HelloWorld.abi and assign it to a variable like so:

> abi = [{"inputs":[{"internalType":"string","name":"initMessage","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"message","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"newMessage","type":"string"}],"name":"update","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Next, do the same thing with hello_sol_HelloWorld.bin.

> bin = "0x60806040523480156100115760006000fd5b50604051610505380380610505833981810160405260208110156100355760006000fd5b81019080805160405193929190846401000000008211156100565760006000fd5b8382019150602082018581111561006d5760006000fd5b825186600182028301116401000000008211171561008b5760006000fd5b8083526020830192505050908051906020019080838360005b838110156100c05780820151818401525b6020810190506100a4565b50505050905090810190601f1680156100ed5780820380516001836020036101000a031916815260200191505b506040526020015050505b8060006000509080519060200190610111929190610119565b505b506101c9565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061015a57805160ff191683800117855561018d565b8280016001018555821561018d579182015b8281111561018c578251826000509090559160200191906001019061016c565b5b50905061019a919061019e565b5090565b6101c691906101a8565b808211156101c257600081815060009055506001016101a8565b5090565b90565b61032d806101d86000396000f3fe60806040523480156100115760006000fd5b506004361061003b5760003560e01c80633d7403a314610041578063e21f37ce146101045761003b565b60006000fd5b610102600480360360208110156100585760006000fd5b81019080803590602001906401000000008111156100765760006000fd5b8201836020820111156100895760006000fd5b803590602001918460018302840111640100000000831117156100ac5760006000fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050909091929090919290505050610188565b005b61010c6101a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561014d5780820151818401525b602081019050610131565b50505050905090810190601f16801561017a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600060005090805190602001906101a1929190610247565b505b50565b60006000508054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561023f5780601f106102145761010080835404028352916020019161023f565b820191906000526020600020905b81548152906001019060200180831161022257829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061028857805160ff19168380011785556102bb565b828001600101855582156102bb579182015b828111156102ba578251826000509090559160200191906001019061029a565b5b5090506102c891906102cc565b5090565b6102f491906102d6565b808211156102f057600081815060009055506001016102d6565b5090565b9056fea26469706673582212206a611de2a70898c4073a822d00b8d0e3ff875299831a62ed66f954d9c1211f9464736f6c634300060b0033"

Note that we add the 0x and placed the whole thing in quotes. Next, we define a contract creation callback:

>  contract_creation_cb = function(e, contract) {
    if (e) {
        console.log("err creating contract", e);
    } else {
        if (!contract.address) {
            console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined...");
        } else {
       console.log("Contract mined! Address: " + contract.address);
         address = contract.address
         console.log(contract);
       }
   }
  }

Now, unlock your account and deploy the contract:

> personal.unlockAccount(acct,"",300)
> simpleContract = eth.contract(abi)
> simpleContract.new("Yo",{from: acct, data: bin, gas: 0x47b760}, contract_creation_cb)

Let us break down these commands a bit. The first command unlocks the account, enabling us to spend the Ether we got from the faucet. The second step instantiates an object simpleContract that will represent an instance of the smart contract we are going to deploy. The third command deploys the smart contract. Note that there are three arguments to the new function. The first is the parameter that we want to pass to the constructor - in this case we're initializing the message variable with the string "Yo". The second and third arguments relate to what account to deploy from, how much Ether to spend in the deployment, and what callback to invoke after the deployment.

Deploying the contract generates a transaction on the blockchain, and we can identify this transaction by the transactionHash that's shown at the end of the output. After about a minute, you should get a message like

Contract mined! Address: 0x35412a4c745357a02c7705d6f8c1b48722719bb6

Great, our contract has been deployed and we can interact with it at the address 0x35412a4c745357a02c7705d6f8c1b48722719bb6

Testing it out

If we want to read the message stored in the contract we first need to load the ABI to tell the client about the smart contract interface (not necessary if we are in still in the geth session used to deploy our contract):

>  abi = [{"inputs":...
>  simpleContract = eth.contract(abi)

Now we can query and send messages to the smart contract.

> mySimpleContract = simpleContract.at("0x35412a4c745357a02c7705d6f8c1b48722719bb6")
> mySimpleContract.message.call()

We can update the message like follows:

>  mySimpleContract.update("Bye",{from:acct})

Subsequent calls to message.call() will return the updated message. Note that the update might not occur immediately, since calling update generates a transaction that needs to be confirmed on the blockchain. However, within 15 seconds or so it should be settled.

Date: 2020-08-24 Mon 00:00

Email: [email protected]

Validate