Tezos tools: automated contract set up

March 21, 2018
automation cryptocurrency tezos michelson blockchain smart contracts applications oracles golang

July 30th 2018: Tezos betanet has now launched, and this article doesn’t reflect the current state of the project. You can still read through it, but the code examples won’t work as is, and will need various changes. Up to date documentation for the betanet is available [here][21]. You can change the address to access docs for other branches like [zeronet][22].

A word about different testnets: there is usually a zeronet running, which follows new developments closely and an alphanet which is more stable. The real network is currently called betanet (transactions persist to the main network). The alphanet is currently obsolete.

If we were to do everything by hand, we would have to:

  1. originate a placeholder redirector contract
  2. originate the provider contract, using the placeholder address
  3. originate the real redirector contract
  4. initialize the provider contract with the real redirector address

This quickly becomes a chore, so we’d like to streamline the process. We’ll write a script that automates the whole set up. We can use any programming language that we are comfortable with, I’ll use golang (it’s easy to read even if you don’t normally use it).

One command executed by the golang script might look like this:

// originate oracle contract

c = exec.Command("./alphanet.sh", "client", "originate", "contract", oracleContract, "for", oracleIdentity, "transferring", "1000", "from", "money", "running", "container:oracle.tz", "-init", fmt.Sprintf(`(Pair "%s" Map)`, oracleKey))
c.Env = append(c.Env, "ALPHANET_EMACS=true")
b, err = c.CombinedOutput()
if err != nil {
    fmt.Println("error:", string(b))
    return
}
if *debug {
    fmt.Println(string(b))
}

oracleAddress, err := parseAddress(string(b))
if err != nil {
    log.Fatal(err)
}

The os/exec package lets our script execute other commands. We create a command with the Command function, and environment variables can be set for each command by modifying the Env field. After we have configured the command, we can run it with Output or CombinedOutput if we want the error output as well as the standard output.

I’m using hardcoded aliases for the contracts, but they could be easily passed as command line options. A convenient way to do this in golang is to use the flag package. We just have to define the flags as global variables, and call flag.Parse in main. Here we use the v and debug options to optionally provide verbose and debug output.

var debug = flag.Bool("debug", false, "print debug information")
var verbose = flag.Bool("v", false, "print out more information")

func main() {
    flag.Parse()
    ...
}

We can check whether the script is being executed in the proper directory.

if *verbose {
    fmt.Println("Checking for alphanet.sh.")
}

if _, err := os.Stat("alphanet.sh"); os.IsNotExist(err) {
    fmt.Println("Please navigate to the folder with your alphanet.sh and run the script there.")
    return
}

We can keep track of previous runs with a config file, and suffix a counter to differentiate different versions of contracts. Like so: last suffix: 35 => new suffix: 36, new alias: providerContract36. This lets us compare current versions of the contracts and identities to old ones, should we accidentally introduce some bugs.

//get suffix from previous run
b, err := ioutil.ReadFile(".setup_automated_insurance_suffix")
if err != nil {
    log.Fatal(fmt.Errorf("trying to read suffix file: %v", err))
}
scanner := bufio.NewScanner(bytes.NewReader(b))
if !scanner.Scan() {
    log.Fatal(fmt.Errorf("reading suffix string: no string found"))
}
s := scanner.Text()

//check that s contains digits only
for _, c := range s {
    if !unicode.IsDigit(c) {
        log.Fatal(fmt.Errorf("suffix contains non-digit characters"))
    }
}

//increment suffix and write back to file
old, err := strconv.Atoi(s)
if err != nil {
    log.Fatal(fmt.Errorf("converting suffix: %v", err))
}
n := strconv.Itoa(old + 1)

err = ioutil.WriteFile(".setup_automated_insurance_suffix", []byte(n+"\n"), 0644)
if err != nil {
    log.Fatal(fmt.Errorf("writing new suffix: %v", err))
}

oracleIdentity := "oracleIdentity" + n
oracleContract := "oracleContract" + n
providerIdentity := "providerIdentity" + n
providerContract := "providerContract" + n
redirectorContract := "redirectorContract" + n
placeholderRedirectorContract := "placeholderRedirectorContract" + n

With the verbose output, we can print out the important addresses and keys after everything is successfuly originated.

if *verbose {
    fmt.Println("Done.")
    fmt.Println("Run the automated insurance server with the following information.")
    fmt.Println("Provider secret key:")
    c = exec.Command("./alphanet.sh", "client", "show", "identity", providerIdentity, "-show-secret")
    c.Env = append(c.Env, "ALPHANET_EMACS=true")
    b, err = c.CombinedOutput()
    if err != nil {
        fmt.Println("error:", string(b))
        return
    }
    fmt.Println(strings.TrimSpace(string(b)))
    ...
}

Let’s see how it looks:

$ sudo ./setup_automated_insurance -v
Checking for alphanet.sh.
Detecting previous runs.
Suffix for this run is 67.
Writing suffix to file.

Generating oracle identity.
Originating oracle contract.
Generating provider identity.
Originating placeholder redirector contract.
Originating provider contract.
Originating redirector contract.

Signing the address of the redirector contract.
Address: TZ1euyR24UPiqWMnYytYnxYqUiBzJqonTzDL
Signature: 1f3a039ff67c710b323c4c7fb36e14b3969662acf22e3f34beea2622eb552d14311519087ed2e0768ae815ce23e8a8e5c524776c178b3f5d715e14408e0a4507
Initializing provider contract.
The initialization is done by sending a transaction to the provider contract, that contains the address and the signature above.

Done.

Run the automated insurance server with the following information.
Provider secret key:
Hash: tz1Q7M5z678oDYYDi1M3EWCh8b743f1mTqQc
Public Key: edpkufQkd1swmzzW5kp2ukKX7YCF4vKWQojhtB9srL9XoP83EcncYD
Secret Key: edskS5ZrzjBoeNwwSNeHBfK8CwNKkWPPnPn4qHHNNJjmkjTzkg9TehUdUNocD2VDWZ67i4q8ZyuTaGHnvHppCeye8XE2uxqLjr
Provider contract:
TZ1ZWPfLqw7J6DtMTR5VhpU1a69J16CzWM8j

Run the oracle server with the following information.
Oracle secret key:
Hash: tz1arEBRbytcpGjTK3H8nHBb9yiFL8AgkWpC
Public Key: edpkuLSaWrchVnRdPSoyhpHFwPe7ZDew1q2BF5XYjRM1GfPsksvV3E
Secret Key: edskRt19GijznKDHZ3bzsqnQBaXWoEEXJCDWh4RXY6mZLhVa8tYTxxPsFLR6CoNtnHch2kC1PoLpxoA6w23mHtjb44sUjGFFDg
Oracle contract:
TZ1spSLXLNNj8DV7XKLcoecWaMR7PUXGcWPu

Note that as of March 27th, it’s best to try to use the zeronet rather than the alphanet. This means you’ll need to compile a node from source and some of the older alphanet guides won’t be accurate – you should come join the matrix chat if you run into any issues.

A recent guide to setting up a zeronet node is available at this community maintained documentation repository. It’s a guide for Debian, but it worked fine on my Ubuntu as well.

The next few articles will be about Liquidity and we’ll explore how it can help us build more complex contracts.

In other news, I have started an email list - you can subscribe at the main page of my website. I’ll let you know when a new article is published and occasionally send you essays about Tezos smart contract development and applications.

tzsuite: event driven library for Tezos

October 2, 2018
golang tezos michelson cryptocurrency blockchain smart contracts applications oracles automation

Getting started with Liquidity 2: authentication

July 31, 2018
easy liquidity cryptocurrency tezos michelson blockchain smart contracts

Getting started with Liquidity: coinflip

May 27, 2018
easy liquidity cryptocurrency tezos michelson blockchain smart contracts