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.
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:previewalias 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.binDeploy 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.binReplace the following:
OWNER_ALIAS: the alias of a user account with theROLE_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_ALIASReplace the following:
PARTICIPANT_ALIAS: the alias of a user account with theROLE_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-contractThis 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
- Explore The GCULpy language reference.
- Learn about other transactions you can submit to a network.