Deploy a programmable contract

The Universal Ledger supports programmable contracts that can be deployed in a network to automate and enforce agreements amongst interested participants.

This tutorial shows developers the steps necessary to develop, deploy, and interact with a programmable contract in a Universal Ledger network.

Before you begin

To complete this tutorial you will need:

  • In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

  • A Universal Ledger user account with the ROLE_CONTRACT_CREATOR. This account will become the contract owner.

  • One or more Universal Ledger user accounts with the ROLE_CONTRACT_PARTICIPANT. It can be the same account as the contract owner.

  • Optionally, Set up the Universal Ledger CLI to be able to sign and submit transactions on behalf of these user accounts.

Universal Ledger roles can be assigned to a User Account when created by their Account Manager, at the time when its CreateAccount transaction is submitted, or later modified through an AddRoles transaction if the account already exists. For experimentation purposes, you can also use the Universal Ledger CLI to Manage accounts.

Set up your environment

To simplify the setup, this tutorial has been written for the default environment provided in a Cloud Shell session. You might need to modify these commands if you are using a different environment.

The gculpyc compiler takes source code written in the GCULpy language and produces bytecode for the Universal Ledger.

Run each of the following commands to pull the gculpyc Docker image, define an alias for running the binary, and confirm that the binary works.

docker pull us-docker.pkg.dev/gcul-artifacts/images/client/gculpyc:preview
alias gculpyc="docker run --rm -i --user $(id -u):$(id -g) \
    --volume .:/workspace --workdir /workspace \
    us-docker.pkg.dev/gcul-artifacts/images/client/gculpyc:preview"
gculpyc --help

Write your contract

GCULpy is the language used to write contracts for the Universal Ledger. It is a statically typed subset of Python, optimized for clear, auditable, and comprehensible contract logic. This design prioritizes writing secure code and restricts unexpected or unsafe behaviors. For more details see The GCULpy language reference.

Because GCULpy is a strict subset of Python, you can continue to use your preferred integrated development environments (IDEs) along with your existing workflows and development practices.

As an example, your code could look something like:

import gcul

class Counter(gcul.Contract):
    """Example contract implementing a counter."""

    value: int

    def increment(self) -> None:
        """Increments the counter value by 1."""
        self.value += 1

Copy this sample code and save it into a file named counter.py.

Test locally

In the near future, developers will have access to a local simulation environment. Provided as part of the gcul Python module, it is designed to provide the necessary functionalities for natively simulating a Universal Ledger network within a Python environment. As such, you will be able to run contracts locally and write unit tests using your preferred testing frameworks, ensuring the reliability and correctness of your contracts before deployment.

Compile the contract

Compile the preceding contract source code into bytecode using the following gculpyc command:

gculpyc --source_file counter.py --output_file counter.bin

Deploy the contract

Deploy the contract to a Universal Ledger network by submitting a CreateContract transaction signed by a user account holding the ROLE_CONTRACT_CREATOR.

If using the Universal Ledger CLI, you can do this by running the command:

ul-cli contracts create \
    --alias counter-contract \
    --sender OWNER_ALIAS \
    counter.bin

Replace the following:

  • OWNER_ALIAS: the alias of a user account with the ROLE_CONTRACT_CREATOR.

When the transaction is finalized, the output of this command will include the ID of the newly deployed contract. For example:

Contract created: 1:CTR:005XvYfiSm3913Jwv4y8HVQucStJ2Ev15Sar6A1kNNX10

Invoke a contract method

Once a contract is deployed, any user account with the ROLE_CONTRACT_PARTICIPANT can submit an InvokeContractMethod transaction to invoke any of the public methods in the contract.

If using the Universal Ledger CLI, you can do this by running the command:

ul-cli contracts invoke \
    --alias counter-contract \
    --method-name increment \
    --sender PARTICIPANT_ALIAS

Replace the following:

  • PARTICIPANT_ALIAS: the alias of a user account with the ROLE_CONTRACT_PARTICIPANT.

Read the contract state

To conclude this tutorial, you can submit a QueryAccount request to read and verify the state of the contract stored on the ledger. This is the same API method used to query and retrieve the details of any Universal Ledger account.

Using the Universal Ledger CLI, you can run:

ul-cli accounts describe --alias counter-contract

This should confirm that the counter value is now set to 1, producing an output such as:

Account: 1:CTR:005XvYfiSm3913Jwv4y8HVQucStJ2Ev15Sar6A1kNNX10
Contract account details:
  Owner: 1:USR:XCL:022uF6cVkTJBaa6pViqTuYqP4455jnRbRc4bWannZGg0b
  Contract fields:
    value: int64_value:1

  Balances:
    None

What's next