Tezos tools: automated contract set up

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

The problem

The last few articles have been about an automated insurance system. It’s composed of several contracts that have to be originated in specific order, which makes it annoying to experiment with small changes. Tracking down and fixing bugs also involves many changes.

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).


The script

Our script will call the alphanet.sh just like we would if we originated the contracts manually. Then we will parse the results and continue with the next command. We will have to check for errors and handle them as well.

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


Where to get it

The script is available at my git. You can use it to easily deploy my automated insurance system on your own servers. It needs to have access to working alphanet.sh and contract source files. The contracts and the servers for the automated insurance and the oracle are in my git as well, and are explained in previous articles.

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.

Tezos changes: zeronet update

April 15, 2018
cryptocurrency tezos michelson blockchain smart contracts applications oracles

Demo: automated insurance

February 8, 2018
demo cryptocurrency tezos michelson blockchain smart contracts applications oracles golang react web

Tezos applications: automated insurance

January 12, 2018
cryptocurrency tezos michelson blockchain smart contracts applications oracles golang