Quantum (Qtum) Blockchain Developer Tutorial - Hello World!

in #qtum7 years ago

qtom_logo.png

Introduction

In my previous post, I described how to set up a 3-node regtest Quantum (Qtum) blockchain environment using Docker. Hopefully you have this up and running -- since we'll be using it in this tutorial.

As a first introduction to developing a smart contract on the Quantum blockchain, we'll be re-creating the Ethereum Greeter tutorial at https://ethereum.org/greeter . We'll re-use the same Solidity contract, but we'll be creating and invoking the contract using the Quantum command-line tool (i.e. qtum-cli).

After completing this initial tutorial, you will hopefully gain a better understanding of the Quantum blockchain and be inspired to develop novel decentralized applications.

Before We Start... Why Quantum (Qtum) instead of Ethereum?

This is a good question, which also has a very good answer.

As you know, Quantum (a.k.a. Qtum) has leveraged and also evolved the strengths of other blockchain projects, namely:

  • Bitcoin: For its UTXO model (and the security this provides), as well as support for Simplified Payment Verification (SPV).
  • Ethereum: For its smart contract execution virtual machine (i.e. the EVM).
  • Blackcoin: For its Proof-of-Stake (POS) model, over which Quantum also added some improvements.

The Quantum Account Abstraction Layer innovation enables the UTXO (address) model to interoperate with the Ethereum (account) model. In addition to the Ethereum VM, it was recently announced that Qtum is working on supporting other VMs.

Support for Simplified Payment Verification (SPV) allows Quantum clients to run on devices where it is not practical to download and persist full blocks of the blockchain (e.g. due to bandwidth costs and/or storage constraints). As such, SPV support enables Quantum apps to run on your iOS (e.g. iPhone / iPad) or Android device -- and even on a satellite. While Quantum supports SPV, it should be noted that this is not the only part of their strategy (i.e. they are not solely focused on the mobile market).

Quantum's support for Proof-of-Stake makes it possible to mint new blocks using lower-power devices such as the Raspberry Pi. This is much more environmentally friendly than Proof-of-Work as used by Bitcoin and (still today) Ethereum.

Also, to avoid disruptive forks, Quantum supports a Decentralized Governance Protocol to safely modify blockchain parameters.

For the reasons given above (and others, such as their stellar team), the Quantum blockchain offers several key advantages over Ethereum.

Assumptions

In addition to my earlier assumptions, I will be making these additional assumptions:

  • You have Docker installed and a Qtum regtest environment running.
  • You are running the commands below from the directory containing the config files (e.g. node1_qtumd.conf) and the datadir sub-directories (e.g. node1_data).
  • If desired, you are able to create parameterized batch/shell scripts for the (rather long) Docker commands below.

When I use the ${PWD}environment variable (below), it is replaced by the current directory under Linux and macOS/OSX. On Windows, this is typically equivalent to %cd%.

With the -v parameter to docker run, it's important that an absolute path is given.

Step 1 - Are you ready?

Before we get started, let's make sure we have things in order. Let's first check that our 3 qtumd nodes are running:
$ docker ps -f name=qtumd
The command above should return 3 running Docker containers (i.e. qtumd_node1, qtumd_node2, and qtumd_node3).

Next, let's make sure we're at block 600 or higher, and that we have a positive balance in at least 1 wallet:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli getinfo
The above should return something like:

{
  "version": 140301,
  "protocolversion": 70016,
  "walletversion": 130000,
  "balance": 2000000.00000000,
  "stake": 0.00000000,
  "blocks": 600,
  "timeoffset": 0,
  "connections": 2,
  "proxy": "",
  "difficulty": {
    "proof-of-work": 4.656542373906925e-10,
    "proof-of-stake": 4.656542373906925e-10
  },
  "testnet": false,
  "moneysupply": 12000000,
  "keypoololdest": 1507588445,
  "keypoolsize": 100,
  "paytxfee": 0.00000000,
  "relayfee": 0.00400000,
  "errors": ""
}

Notice that blocks is at 600, and balance is 2000000.00000000. The 2000000.00000000 balance gives us many QTUM coins to play with...

If you run the same getinfo command on the other nodes (e.g. qtumd_node2 and qtumd_node3), the block number should be the same or higher (depending on how long you waited), but the balance will likely be 0, e.g.:
docker run -i --network container:qtumd_node2 -v ${PWD}/node2_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node2_data:/data cryptominder/qtum:latest qtum-cli getinfo
can return:

{
  "version": 140301,
  "protocolversion": 70016,
  "walletversion": 130000,
  "balance": 0.00000000,
  "stake": 0.00000000,
  "blocks": 605,
  "timeoffset": 0,
  "connections": 2,
  "proxy": "",
  "difficulty": {
    "proof-of-work": 4.656542373906925e-10,
    "proof-of-stake": 4.656542373906925e-10
  },
  "testnet": false,
  "moneysupply": 12100000,
  "keypoololdest": 1507588475,
  "keypoolsize": 100,
  "paytxfee": 0.00000000,
  "relayfee": 0.00400000,
  "errors": ""
}

This is fine.

Now, let's check if there are any existing smart contracts... run the following:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli listcontracts
You should see 5 contracts listed on the blockchain:

{
  "0000000000000000000000000000000000000083": 0.00000000,
  "0000000000000000000000000000000000000080": 0.00000000,
  "0000000000000000000000000000000000000081": 0.00000000,
  "0000000000000000000000000000000000000082": 0.00000000,
  "0000000000000000000000000000000000000084": 0.00000000
}

None of these contracts were deployed by us -- ignore them (at least for this tutorial).

Lastly, we'll be using qtumd_node1 as the node on which we will usually invoke qtum-cli, but I encourage you to try the other nodes too.

Step 2 - Pulling a few tools: solc and ethabi

For the Solidity compiler, we will be using the solc Docker image as described in the Solidity documentation. Pull the image using:
$ docker pull ethereum/solc

Check that it works with:
$ docker run --rm -v ${PWD}:/solidity ethereum/solc:stable --version
The command above should output:

solc, the solidity compiler commandline interface
Version: 0.4.17+commit.bdeb9e52.Linux.g++

Notice that we used -v ${PWD}:/solidity . The /solidity directory within the image is set to the working directory, so that's why we're mapping our current (absolute path) there.

You can run the following command to get help with solc:
$ docker run --rm -v ${PWD}:/solidity ethereum/solc:stable --help

We'll also need the ethabi command-line tool. I created a Docker image for it at https://hub.docker.com/r/cryptominder/ethabi/ .

Pull the ethabi image using:
$ docker pull cryptominder/ethabi

Check that it works with:
$ docker run --rm -v ${PWD}:/ethabi cryptominder/ethabi:latest --help
The above should give you:

Ethereum ABI coder.
  Copyright 2016-2017 Parity Technologies (UK) Limited

Usage:
    ethabi encode function <abi-path> <function-name> [-p <param>]... [-l | --lenient]
    ethabi encode params [-v <type> <param>]... [-l | --lenient]
    ethabi decode function <abi-path> <function-name> <data>
    ethabi decode params [-t <type>]... <data>
    ethabi decode log <abi-path> <event-name> [-l <topic>]... <data>
    ethabi -h | --help

Options:
    -h, --help         Display this message and exit.
    -l, --lenient      Allow short representation of input params.

Commands:
    encode             Encode ABI call.
    decode             Decode ABI call result.
    function           Load function from json ABI file.
    params             Specify types of input params inline.
    log                Decode event log.

Notice again that we used the -v ${PWD}:/ethabi option in docker run. The /ethabi directory within the image is set to the working directory, so that's why we're mapping our current (absolute path) there.

Ok, we're now ready to really get started...

Step 3 - Compile the smart contract

We'll re-use the exact same smart contract that is shown at https://ethereum.org/greeter .

Create the following file, named: helloworld.sol:

contract mortal {
    /* Define variable owner of the type address */
    address owner;

    /* This function is executed at initialization and sets the owner of the contract */
    function mortal() { owner = msg.sender; }

    /* Function to recover the funds on the contract */
    function kill() { if (msg.sender == owner) selfdestruct(owner); }
}

contract greeter is mortal {
    /* Define variable greeting of the type string */
    string greeting;

    /* This runs when the contract is executed */
    function greeter(string _greeting) public {
        greeting = _greeting;
    }

    /* Main function */
    function greet() constant returns (string) {
        return greeting;
    }
}

Compile the Solidity code by running:
$ docker run --rm -v ${PWD}:/solidity ethereum/solc:stable --optimize --bin --abi --hashes -o /solidity --overwrite /solidity/helloworld.sol
You'll likely see the following output:

/solidity/helloworld.sol:6:5: Warning: No visibility specified. Defaulting to "public".
    function mortal() { owner = msg.sender; }
    ^---------------------------------------^
/solidity/helloworld.sol:9:5: Warning: No visibility specified. Defaulting to "public".
    function kill() { if (msg.sender == owner) selfdestruct(owner); }
    ^---------------------------------------------------------------^
/solidity/helloworld.sol:22:5: Warning: No visibility specified. Defaulting to "public".
    function greet() constant returns (string) {
    ^
Spanning multiple lines.
/solidity/helloworld.sol:1:1: Warning: Source file does not specify required compiler version!Consider adding "pragma solidity ^0.4.17
contract mortal {
^
Spanning multiple lines.

Since this is just an example smart contract, you can safely ignore these warnings.

After running solc, you should now see the following additional files in your directory:

greeter.abi
greeter.bin
greeter.signatures
mortal.abi
mortal.bin
mortal.signatures

We'll be using all 3 of the greeter files soon.

Congratulations -- you've just compiled a smart contract!

Step 4 - Deploy the smart contract

In this step, we'll be deploying the smart contract onto our 3-node regtest Qtum blockchain.

But first, a small administrative detour...

Using the node/wallet that has a positive balance (from Step 1), let's designate a specific address that will own the smart contract. We'll use the getaccountaddress RPC command to do this and call the account for this address greeter_owner:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli getaccountaddress greeter_owner
This should return a Quantum address (e.g. qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5, but yours will surely be different) and if you run the same command again (with the same account name), you'll get back the same Quantum address.

You'll notice the Quantum address above begins with a lowercase q -- this distinguishes the address from a Quantum mainnet address (which starts with a capital Q).

You can view the accounts (and their balances) in your wallet by running:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli listaccounts
You should see an entry with: "greeter_owner": 0.00000000 -- which means the account greeter_owner has a balance of 0.00000000.

Take note of the QTUM address that was returned above (e.g. qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5 above, for me) -- we'll be using it often.

Ok, let's get on with deploying the contract...

The greeter.bin file that we generated in Step 3 contains the binary code (in hexadecimal) of the greeter contract that will be executed on Qtum's Ethereum VM.

You'll notice that the greeter contract has the following constructor:
function greeter(string _greeting)

Since the greeter contract constructor takes one (string) parameter, we'll want to provide this parameter to the smart contract when we deploy it. In order to do so, we'll need to get the binary representation of the string parameter we wish to give. This is where the ethabi tool is helpful. Run the following command to encode the "Hello World!" string:
$ docker run --rm -v ${PWD}:/ethabi cryptominder/ethabi:latest encode params -v string 'Hello World!' --lenient
It should output:
0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000

The above is essentially a binary (hexadecimal) representation of the Hello World! string. Feel free to provide your own greeting (NOTE: since this will be stored on the blockchain, larger strings will cost more to store).

Now, we'll need to concatenate the contents of greeter.bin with the output above -- on macOS/OSX and Linux, this can be done using:
$ cat greeter.bin && docker run --rm -v ${PWD}:/ethabi cryptominder/ethabi:latest encode params -v string 'Hello World!' --lenient
I won't reproduce the long string that the above outputs here, but it should start with:
6060604052341561000f57600080fd5b ...
... and end with the output got for the Hello World! parameter encoding.

To deploy the smart contract, we'll be using the createcontract RPC command. It takes the following arguments:

createcontract "bytecode" (gaslimit gasprice "senderaddress" broadcast)
Create a contract with bytcode.

Arguments:
1. "bytecode"  (string, required) contract bytcode.
2. gasLimit  (numeric or string, optional) gasLimit, default: 2500000, max: 40000000
3. gasPrice  (numeric or string, optional) gasPrice QTUM price per gas unit, default: 0.0000004, min:0.0000004
4. "senderaddress" (string, optional) The quantum address that will be used to create the contract.
5. "broadcast" (bool, optional, default=true) Whether to broadcast the transaction or not.

The bytecode is the long string (starting with 6060604052341561000f57600080fd5b). For the gasLimit, we'll use the default of 2500000. For the gasPrice we'll use 0.00000049. And for senderaddress, we'll use the Quantum address (e.g. qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5, for me). We won't set a value for broadcast since we'll be using the default value.

The command is therefore:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli createcontract 6060604052341561000f57600080fd5b6040516103173803806103178339810160405280805160008054600160a060020a03191633600160a060020a03161790559190910190506001818051610059929160200190610060565b50506100fb565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100ce565b828001600101855582156100ce579182015b828111156100ce5782518255916020019190600101906100b3565b506100da9291506100de565b5090565b6100f891905b808211156100da57600081556001016100e4565b90565b61020d8061010a6000396000f300606060405263ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b58114610047578063cfae32171461005c57600080fd5b341561005257600080fd5b61005a6100e6565b005b341561006757600080fd5b61006f610127565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100ab578082015183820152602001610093565b50505050905090810190601f1680156100d85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614156101255760005473ffffffffffffffffffffffffffffffffffffffff16ff5b565b61012f6101cf565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101c55780601f1061019a576101008083540402835291602001916101c5565b820191906000526020600020905b8154815290600101906020018083116101a857829003601f168201915b5050505050905090565b602060405190810160405260008152905600a165627a7a723058209a62630a1678b0014fdfe901ed4f21cd251e9b7863cfccbf79b1870bcc2e1de100290000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000 2500000 0.00000049 qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5

After running this command, we'll likely get en error saying: Sender address does not have any unspent outputs. This is because we have no UTXOs for this address. We'll fix this by sending 3 QTUM from our wallet to that specific address:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli sendtoaddress qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5 3
The command above will return a transaction id (e.g. 7d84a578a1d56ca71b1ea85b2028c34cce03bafe65dd782d3d8e41884eefa471).

After a few moments (e.g. for at least 1 block to be generated), use the listunspent RPC command to verify that the address (e.g. qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5, for me) has unspent transaction outputs (UTXOs):
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli listunspent 1 9999999 [\"qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5\"]
The above should output something like this:

[
  {
    "txid": "5b9b376deef5c10e31acca1f2ff0be64179878048de753e20c2a04901fbf689b",
    "vout": 0,
    "address": "qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5",
    "account": "greeter_owner",
    "scriptPubKey": "76a914002edb387c05038b700f97ce9dc40e305805c8df88ac",
    "amount": 3.00000000,
    "confirmations": 1,
    "spendable": true,
    "solvable": true
  }
]

Notice the last parameter to listunspent (i.e. [\"qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5\"]) is a JSON array with the double-quotes (") escaped with \.

Now that the address has an unspent output (i.e. an UTXO), we can re-run the createcontract command above. It should now output something like the following:

{
  "txid": "85d3c46886790cc164291500f3ed6bed20792c307b666a6fc490bd16c800c148",
  "sender": "qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5",
  "hash160": "002edb387c05038b700f97ce9dc40e305805c8df",
  "address": "fd648ac3e7f89fd049507602d3d025cc90000606"
}

Congratulations again - you've just deployed a smart contract!

Step 5 - Inspecting the deployed smart contract

Referring back to the output of createcontract (in the previous step), you'll notice that the sender is the same as the Quantum address that we gave. The hash160 value is simply a hash of the sender, and you can use the fromhexaddress and gethexaddress Quantum RPC commands to go from one form to the other.

The address field contains the address of the smart contract on the blockchain. If you run listcontracts again (like we did in step 1) -- you should see a new entry, e.g.:
"fd648ac3e7f89fd049507602d3d025cc90000606": 0.00000000,
The contract address is shown here too.

Now, let's use the getaccountinfo RPC command to retrieve information about our newly deployed smart contract (NOTE - use the address that you received from createcontract in Step 4, instead of fd648ac3e7f89fd049507602d3d025cc90000606 that I'm using):
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli getaccountinfo fd648ac3e7f89fd049507602d3d025cc90000606
The above will output something like:

{
  "address": "fd648ac3e7f89fd049507602d3d025cc90000606",
  "balance": 0,
  "storage": {
    "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": {
      "0000000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000002edb387c05038b700f97ce9dc40e305805c8df"
    },
    "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": {
      "0000000000000000000000000000000000000000000000000000000000000001": "48656c6c6f20576f726c64210000000000000000000000000000000000000018"
    }
  },
  "code": "606060405263ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b58114610047578063cfae32171461005c57600080fd5b341561005257600080fd5b61005a6100e6565b005b341561006757600080fd5b61006f610127565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100ab578082015183820152602001610093565b50505050905090810190601f1680156100d85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000543373ffffffffffffffffffffffffffffffffffffffff908116911614156101255760005473ffffffffffffffffffffffffffffffffffffffff16ff5b565b61012f6101cf565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101c55780601f1061019a576101008083540402835291602001916101c5565b820191906000526020600020905b8154815290600101906020018083116101a857829003601f168201915b5050505050905090565b602060405190810160405260008152905600a165627a7a723058209a62630a1678b0014fdfe901ed4f21cd251e9b7863cfccbf79b1870bcc2e1de10029"
}

From the above output, we can see that the smart contract has a balance of 0, and that there are stored 2 items...

So, what is stored there? You'll notice that our smart contract had 2 variables (in helloworld.sol):

  1. owner (inherited from mortal)
  2. greeting (declared in greeter)

As you might guess, the first stored item (i.e. indexed by 0000000000000000000000000000000000000000000000000000000000000000) stores the owner address. You'll recognize that it contains the hash160 address of the sender. This means that our sender is the owner of this smart contract.

What about the second stored item (i.e. indexed by 0000000000000000000000000000000000000000000000000000000000000001)? This is pretty much hexadecimal for Hello World!. You can use an online tool such as http://www.rapidtables.com/convert/number/hex-to-ascii.htm to convert it to ASCII, or run:
$ echo 48656c6c6f20576f726c64210000000000000000000000000000000000000018 | xxd -r -p
to see that it will display Hello World!.

Step 6 - Invoking the smart contract

In this step, we'll be calling the greet function in our greeter smart contract.

To do this, we'll need to get the encoding of the greet function, and we can use the ethabi tool for this:
$ docker run --rm -v ${PWD}:/ethabi cryptominder/ethabi:latest encode function /ethabi/greeter.abi greet
The command above should return: cfae3217.

Alternatively, we can just use the greeter.signatures file that we generated when we used the --hashes option of solc in Step 3, e.g.

$ cat greeter.signatures
contains:

cfae3217: greet()
41c0e1b5: kill()

The ethabi tool would be useful if we wanted to encode parameters to a function. However, since this contract has no functions that takes a parameter (aside from the constructor), we can rely on the greeter.signatures file.

Ok, so the greet function has an signature of cfae3217.

Let's now use qtumd_node2 to invoke the greet function of this smart contract. We'll invoke the greet function using the callcontract RPC command which takes the following parameters:

callcontract "address" "data" ( address )

Argument:
1. "address"          (string, required) The account address
2. "data"             (string, required) The data hex string
3. address              (string, optional) The sender address hex string
4. gasLimit             (string, optional) The gas limit for executing the contract

For the address parameter, we'll use the address that was returned when we deployed the smart contract (e.g. fd648ac3e7f89fd049507602d3d025cc90000606, for me). For the data, it's simply the encoding for the greet function -- i.e. cfae3217. We can leave out the last 2 parameters.

Our command (using qtumd_node2) is therefore:
docker run -i --network container:qtumd_node2 -v ${PWD}/node2_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node2_data:/data cryptominder/qtum:latest qtum-cli callcontract fd648ac3e7f89fd049507602d3d025cc90000606 cfae3217
The above will return something like the following:

{
  "address": "fd648ac3e7f89fd049507602d3d025cc90000606",
  "executionResult": {
    "gasUsed": 22737,
    "excepted": "None",
    "newAddress": "fd648ac3e7f89fd049507602d3d025cc90000606",
    "output": "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000",
    "codeDeposit": 0,
    "gasRefunded": 0,
    "depositSize": 0,
    "gasForDeposit": 0
  },
  "transactionReceipt": {
    "stateRoot": "a5891d6126e68e032b640c71a75da231e1f0e159cd7dfc21c1451b4d9db8e349",
    "gasUsed": 22737,
    "bloom": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "log": [
    ]
  }
}

We're most interested in the output value. We'll use the ethabi tool to decode it, i.e.:
$ docker run --rm -v ${PWD}:/ethabi cryptominder/ethabi:latest decode params -t string 0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20576f726c64210000000000000000000000000000000000000000
will output:

string Hello World!

Bravo! You've now invoked a smart contract.

Step 7 - Clean-Up Time & callcontract vs. sendtocontract

The other function in the smart contract is kill. Before we invoke that function, I first wanted to talk about two similar commands in the qtum-cli: callcontract and sendtocontract.

callcontract vs. sendtocontract

The differences between these two commands are nicely documented elsewhere, but I'll repeat it here:

  • callcontract - This will interact with an already deployed smart contract on the Qtum blockchain, with all computation taking place off-chain and no persistence to the blockchain. This does not require gas.
  • sendtocontract - This will interact with an already deployed smart contract on the Qtum blockchain. All computation takes place on-chain and any state changes will be persisted to the blockchain. This allows tokens to be sent to a smart contract. This requires gas.

In the previous step, we used the callcontract RPC command to call greet since we didn't need to persist anything on the blockchain (i.e. we just retrieved a stored value). With the kill smart contract function, we'll be making changes to the state of the blockchain by removing the smart contract address. NOTE: The smart contract address will still exist on the blockchain (i.e. you can look it up with a block explorer), but it will no longer be indexed as an account.

Time to kill

As we just learnt, we must use the sendtocontract RPC command for the kill function. It has the following syntax:

sendtocontract "contractaddress" "data" (amount gaslimit gasprice senderaddress broadcast)
Send funds and data to a contract.

Arguments:
1. "contractaddress" (string, required) The contract address that will receive the funds and data.
2. "datahex"  (string, required) data to send.
3. "amount"      (numeric or string, optional) The amount in QTUM to send. eg 0.1, default: 0
4. gasLimit  (numeric or string, optional) gasLimit, default: 250000, max: 40000000
5. gasPrice  (numeric or string, optional) gasPrice Qtum price per gas unit, default: 0.0000004, min:0.0000004
6. "senderaddress" (string, optional) The quantum address that will be used as sender.
7. "broadcast" (bool, optional, default=true) Whether to broadcast the transaction or not.

For the contractaddress, we'll once again use the smart contract address (i.e. fd648ac3e7f89fd049507602d3d025cc90000606, for me). For the data, we'll use the hex code for the kill function -- which we saw earlier is 41c0e1b5 (e.g. in greeter.signatures).

Using qtumd_node1, our command is:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli sendtocontract fd648ac3e7f89fd049507602d3d025cc90000606 41c0e1b5

If we call the getaccountinfo RPC command on the contract address, we should find that it no longer exists....:
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli getaccountinfo fd648ac3e7f89fd049507602d3d025cc90000606
... oh, oooops... it still exists!

What happened? If you look at the kill function inherited by the greeter contract, you'll see that it checks to ensure that the sender is also the owner. As such, the execution did nothing useful.

Before we simply set the senderaddress, you also need to check that the sender address has at least 1 UTXO, otherwise you'll get another Sender address does not have any unspent outputs message. It is important to know that we lost the UTXO that we created earlier when we did a createcontract call. Refer back to the sendtoaddress and listunspent RPC commands from Step 4 your to check your UTXOs and/or add another 3 QTUM to our address. In a future tutorial, I'll discuss how gas refunds are processed (which will also come back as UTXOs).

With the sender address now having at least 1 UTXO, we need to make sure to set the senderaddress in our sendtocontract call. Let's try that again using amount of 0 (since the function is not identified as payable in Solidity), a gasLimit of 250000, a gasPrice of 0.00000049, and setting the value of senderaddress appropriately (i.e. qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5, for me):
$ docker run -i --network container:qtumd_node1 -v ${PWD}/node1_qtumd.conf:/home/qtum/qtum.conf:ro -v ${PWD}/node1_data:/data cryptominder/qtum:latest qtum-cli sendtocontract fd648ac3e7f89fd049507602d3d025cc90000606 41c0e1b5 0 250000 0.00000049 qHaMHfbUC9sxqYNVgVEAyxD2sXf9bLc8f5

Now if we call getaccountinfo again, we'll see that it returns an error saying: Address does not exist. Success!

This completes our coverage of Quantum's version of the Ethereum Greeter Tutorial as found at https://ethereum.org/greeter , simply using qtum-cli, solc, and ethabi.

References / Further Reading

For much of the content in this tutorial, I've relied on documentation provided by Jordan Earls, the lead developer for Quantum. In particular, I found the following helpful:

What's Next?

In upcoming tutorials, I'll be discussing many other topics, including:

  • Ethereum VM Logging.
  • Data types (e.g. mapping, struct, etc.)
  • Payable contracts and functions.
  • Taking a closer look at selfdestruct.
  • Gas estimation and gas refunds.
  • Using an SPV client.
  • Creating a token.
  • Calling another contract.
  • Global variables (e.g. msg, tx, block)
  • ... and more.

In the meantime, you can also expect to hear about advancements from the Quantum team.

If you need help, please use the comment section below, or look for me on the Qtum subreddit. I'm also active on Quantum's Slack (although Slack invitations are closed as the Quantum team prepares to move to another collaboration/messaging platform).

Thank You!

If you found this tutorial useful, and you are so inclined, my Quantum tip wallet address (running on my Raspberry Pi 3) is: QUa3yA8ALfQyM5eEb9sDPRWkX6sSurMs6D .

I appreciate your support!

Sort:  

Brilliant! Awesome tutorial. We miss you cryptominder! Please come back ;)

In Step 4 after creating the smart contract we do not get a message saying "Sender address does not have any unspent outputs." Instead the contract is deployed but under a completely different sender address and thus hash address. From here I'm not able to ever delete this contract even when using the sender address in the kill operation.

I'm banging my head on this trying to understand it. We need you cryptominder, you're our only hope. ;)

I waited a while and for some reason the contract is now killed when I use the newly generated sender address. However, each time I create a contract I still get a brand new address that is not greeter_owner even though I've passed the greeter_owner address to createcontract. Not sure why it's not deployed under greeter_owner.

Hi , am from China, your article is very good.
i want to translate it and share your idea with people around me.
is that possible ?
Thank you so much for your sharing.

Congratulations @cryptominder! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Do not miss the last post from @steemitboard:
SteemitBoard World Cup Contest - The results, the winners and the prizes

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Hey Awesome tutorials, can we republish these tutorials on Coinmonks?

https://medium.com/coinmonks

Congratulations @cryptominder! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!