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.