How to Create, Test and Deploy Tezos Smart Contracts?

tezos smart contracts

Blockchain technology is rapidly gaining traction as more and more companies are diving into its uses. From developing applications on existing blockchain platforms to building new blockchain platforms altogether, innovators are finding ways to make the use of blockchain technology more viable.

The Tezos blockchain platform was created as a “new-generation” decentralized platform. Founded by Kathleen Breitman and Arthur Breitman, Tezos is a decentralized, open-source blockchain platform to execute P2P transactions and deploy smart contracts. Even though it sounds very much like its counterparts NEO, Ethereum, ICON, etc., a few significant features make it very different, and probably the most preferred choice for building dApps and deploying smart contracts today. Tez or Tezzie (XTZ) is the native cryptocurrency of the Tezos blockchain platform.

Tezos has features like:

  1. Self Amendment Protocol – A self-amending cryptographic ledger designed to avoid hard forking and enable automatic platform updates.
  2. On-Chain Governance – Token holders can participate in protocol governance. The election cycle gives a systematic procedure to enable stakeholders to agree on the proposed amendments in the protocol.
  3. Liquid Proof of Stake Consensus Mechanism – A chain-based Proof of Stake consensus algorithm, also known as Liquid PoS. Bakers are responsible for creating and endorsing blocks and stake a portion of their own capital to incentivize honest behavior.
  4. Michelson Language – A low-level, stack-based programming language to write smart contracts on the Tezos blockchain and designed to facilitate formal verification.

Tezos enables its users to create smart contracts, which are small programs stored and executed on the blockchain. Smart contracts in Tezos are unique as they are written in Michelson. With the help of formal verification, Tezos makes smart contracts more dependable and secure.

This article is focused on Tezos smart contracts and will answer the following questions:

  1. What are the various components of Tezos smart contracts?
  2. How to create a Tezos smart contract?
  3. How to test a Tezos smart contract?
  4. How to deploy and interact with a Tezos smart contract?

Let’s begin by learning about the various components of Tezos smart contracts.

What are the various components of Tezos smart contracts?

As the Tezos blockchain platform has unique features, the smart contracts it deploys also have many components that you must learn about, to seamlessly create and deploy Tezos smart contracts.

The important components of Tezos smart contracts are:

  1. Contract Type
  2. Transactions
  3. Storage Fees
  4. Code
  5. Fees
  6. Intra-Transaction Semantics
  7. Inter-Transaction Semantics

Let’s discuss these components in detail.

Contract Type

Presently, the Tezos ledger has two types of accounts which can hold tokens and be transaction destinations:

  1. Implicit Account – A non-programmable account whose tokens can be spent and delegated by a public key. Its address is the public key hash, starting with tz1, tz2, or tz3.
  2. Smart Contract – A programmable account with a unique hash, depending on its operation (starting with KT1) and transactions to it, can provide data.

According to Tezos, “a safe way to think about this is to consider that implicit accounts are smart contracts that always succeed to receive tokens, and does nothing else.”

Tezos uses stateful accounts instead of unspent outputs. Those accounts are generally known as contracts when they specify executable code. Also, since an account is a type of contract, even with no executable code, both are generally known as contracts.

Each contract has a “manager.” In the case of accounts, the managers are the owners of the account. Managers can spend funds associated with the contracts if the contract is labeled as spendable.

A contract is formally represented as:

type contract = {
counter: int; (* counter to prevent repeat attacks *)
manager: id; (* hash of the contract ‘s manager public key *)
balance: Int64.t; (* balance held *)
signer: id option; (* id of the signer *)
code: opcode list; (* contract code as a list of opcodes *)
storage: data list; (* storage of the contract *)
spendable: bool; (* may the money be spent by the manager? *)
delegatable: bool; (* may the manager change the signing key? *)
}

Transactions

A transaction refers to a message sent from one contract to another. Transactions can be sent:

  • from a contract, if they are signed by using the manager’s key.
  • programmatically by code executing in the contract.

When a transaction is received, the amount is added to the destination contract’s balance. Then, the destination contract’s code is executed, which can:

  • use the parameters passed to it.
  • read and write the contract’s storage.
  • change the signature key.
  • post transactions to other contracts.

A transaction’s counter is responsible for preventing replay attacks. The counter increases by one after the transaction is applied, which prevents the transaction’s reuse. A transaction is invalid if the contract’s counter is not equal to the transaction’s counter.

A transaction message is represented as:

type transaction = {
amount: amount; (* amount being sent *)
parameters: data list; (* parameters passed to the script *)
(* counter (invoice id) to avoid repeat attacks *)
counter: int;
destination: contract hash;
}

Storage Fees

Storage inflicts a cost on the network. So, a minimum fee of XTZ 1 is charged for each increased byte in the storage. It is withdrawn from the contract’s balance.

Code

With its design inspired by Forth, Scheme, ML, and Cat, the Michelson language is stack-based, with:

  • high-level data types.
  • primitives.
  • strict static type checking.

Michelson is the domain-specific language to write smart contracts on the Tezos blockchain, which doesn’t have any variables. A Michelson program is an instructions’ series running in sequence. Each instruction:

  • receives the stack resulting from the previous instruction as an input.
  • rewrites the input for the next instruction.

The stack contains:

  • immediate values
  • heap-allocated structures

There are also various other high-level languages available for programming Smart Contracts for Tezos, which can later be compiled into Michelson. These languages intend to simplify the development experience so that the focus is more on the content of Smart Contracts rather than the implementation.

Currently, options in development include:

  • LIGO
  • SmartPy
  • Morley/Lorentz

Fees

Though the Tezos system handles transactions similarly to Ethereum, they handle fees very differently. Tezos simplifies the construction by imposing a hard cap on the number of steps for which programs can run. Execution can be broken down into multiple steps, and numerous program transactions can be used to fully execute a program if the hard cap is too tight.

As Tezos is amendable:

  • caps can be changed in the future.
  • advanced primitives can be introduced as new operation codes.

The signature key may also be changed by issuing a signed message requesting change if the account permits.

Intra-Transaction Semantics

We learned earlier that there are two types of accounts – Implicit Accounts and Smart Contracts. From the context of Michelson, these accounts are indistinguishable, which is why Tezos asks you to consider that implicit accounts are just smart contracts that always receive tokens successfully.

Smart Contracts also keep a piece of storage along with their tokens. Both are ruled by a particular logic which a Michelson program specifies.

A transaction to a smart contract will provide:

  • an input value
  • some token (optionally)

In return, the smart contract can:

  • modify its storage
  • transfer its tokens

The Michelson program receives a stack as input, which contains:

  • an input value
  • storage space

It must return a stack which contains:

  • list of internal operations it wants to emit
  • new contents of the storage space

A Michelson program, however, can also fail explicitly because of:

  • using a specific operation code
  • something wrong that couldn’t be caught by the type system, like gas exhaustion

At the contract level, a little bit of polymorphism can be used with a lightweight system of named entrypoints. The contract can be called with an entrypoint name and an argument instead of an input value. These two components are transformed automatically to an input value.

Inter-Transaction Semantics

An operation included in the blockchain is a sequence of “external operations.” It is signed as a whole by a source address.

There are three kinds of operations:

    1. Transactions – for transferring tokens to implicit accounts, or transferring tokens and parameters to a smart contract (or a specific entrypoint of a smart contract)
    2. Originations – to create new smart contracts from its Michelson source code, an initial amount of tokens and initial storage contents transferred from the source
    3. Delegations – to assign the source’s tokens to another implicit account’s stake without transferring any tokens

Smart contracts emit “internal operations,” which are run in sequence after the external transaction completes, as in the following schema for a sequence of two external operations.

Smart contracts called by internal transactions can also emit internal operations in turn. The internal operations of a given external operation’s interpretations use a queue, as in the following example, also with two external operations.

Now that you understand Tezos smart contracts’ components, let’s move on to learn how to create, test, and deploy smart contracts.

How to Create, Test and Deploy a Tezos Smart Contract?

Here, we will show you how to create, test, and deploy a Tezos smart contract with the help of an example. We are developing a generic liquidity pool contract to enable users to deposit and withdraw Tezos tokens without any restrictions. In our example, user John will deposit 10 tez to the contract, and then user Katy will withdraw 5 tez.

How to create a Tezos Smart Contract?

We are going to write the contract in the LIGO language and then later compile it to Michelson.

Step 1: Install LIGO CLI

To compile and test the contract, we need to install the LIGO CLI. After installing it, we can start writing the contract by opening the following in our editor:

v1-public-pool.ligo

For our contract, we can create a folder and file with the following commands:

$ mkdir tezos-defi && cd tezos-defi
$ touch v1-public-pool.ligo

Even though the current version of Tezos supports a unique entry point for each contract, we can use arguments indicating the operations we want to perform to work around this limitation. Here, we will illustrate two operations – Deposit and Withdraw. In our contract, we will represent them with a variant type, like the following:

type entry_action is
| Deposit
| Withdraw

Also, the storage will be a simple record with a liquidity field of the tez type, which will represent the funds in our pool.

type finance_storage is record
liquidity: tez;
end

Here, we are using tez for declarations and mtz for literals, as LIGO had an issue with mixing them up.

After this, we’ll work on defining the entry point.

Step 2: Define the Entry Point

The method chosen to listen to external communication is referred to as the entry point for a contract in Tezos. It receives two arguments:

  • The parameters for the method.
  • The contract storage.

Also, it has a fixed signature for the:

  • response
  • operation list
  • storage

In the contract entry point, the parameter’s type will be entry_action, which we already defined as follows:

type entry_action is
| Deposit
| Withdraw

For this parameter, the value must be one of the values defined by either the Deposit or the Withdraw operation, while the method’s body will be empty. The output will be delegated according to the operation’s value, and the storage will be propagated in both cases.

function main (const action: entry_action; var finance_storage:
finance_storage): (list (operation) * finance_storage) is
block {
skip
} with case action of
| Deposit(param) -> depositImp(finance_storage)
| Withdraw (param) -> withdrawImp(finance_storage)
end;

After defining the entry point, we will set up the deposit operation.

Step 3: Set Up the Deposit Operation

The deposit operation takes the amount sent to the transaction origin. If the amount is zero, it will fail.

As we’re going to modify the storage, we’ll define it as var.

function depositImp(var finance_storage: finance_storage)
: (list(operation) * finance_storage) is
block {
if amount = 0mtz
then skip //fail(“No tez transferred!”);
else block {
finance_storage.liquidity := finance_storage.liquidity + amount;
}
} with(noOperations, finance_storage)

We will return noOperations as the first component in the return value and will define this in the contract’s global scope. This defines a list initialized with nil.

const noOperations: list(operation) = nil;

After setting up the deposit operation, we’ll set up the withdrawal operation.

Step 4: Set Up the Withdraw Operation

The “Withdraw” operation enables users to withdraw a fixed tez amount from the liquidity pool. To make the transfer to the sender, whether the contract has enough tez or not is checked by validation. We’ll use the global “sender” from LIGO, which refers to the transaction’s origination account.

If the validation succeeds:

  • contract storage will revert
  • liquidity pool will remain unchanged

function withdrawImp(var finance_storage: finance_storage): (list
(operation) * finance_storage)
block {
const withdrawAmount: tez = 1000000mtz;
var operations: list(operation) := nil;
if withdrawAmount > finance_storage.liquidity
then skip //fail(“No funds to withdraw!”)
else block {
const receiver: contract(unit) = get_contract(sender);
const payoutOperation: operation = transaction(unit, withdrawAmount,
receiver);
operations:= list
payoutOperation
end;
finance_storage.liquidity := finance_storage.liquidity – withdrawAmount;
}
} with(operations, finance_storage)

After the operations are set up, the smart contract is created and it is now time to test it.

How to test Tezos Smart Contracts?

We have the full contract functionality, so now we can proceed to test it part by part. The functional approach of Tezos blockchain allows users to test smart contracts without deploying them. We can run and analyze the result by using LIGO’s dry-run instruction.

Let’s test a deposit by running the following command:

$ ligo dry-run v1-public-pool.ligo –syntax pascaligo main “Deposit(unit)”
“record liquidity = 0mtz; end”> tuple[ list[]
record[
liquidity -> 0tz
]
]

Liquidity is 0 because we have not sent any tez to the transaction. If skip is replaced with fail (“No tez transferred!”);, it will return an error and decline the transaction. To work, the –amount parameter needs to be set to a value greater than 0.

$ ligo dry-run v1-public-pool.ligo –syntax pascaligo –amount 1.55 main
“Deposit(unit)” “record liquidity = 0mtz; end”> tuple[ list[]
record[
liquidity -> 1550000tz
]
]

Note: 1 tez is equal to 1,000,000 mtz.

We can proceed just like earlier to test the withdrawal method. By initializing the storage with tez, the liquidity pool decreases.

$ ligo dry-run v1-public-pool.ligo –syntax pascaligo main “Withdraw
(unit)” “record liquidity = 10000mtz; end”> tuple[ list[
Operation(…bytes)
]
record[
liquidity -> 9000tz
]
]

In both cases, the method response has an identical structure, i.e., a list of operations and the storage.

After testing, we must compile the contract.

How to compile the contract?

Now, to deploy a smart contract to the Tezos Alphanet, the first step is to use the following LIGO instruction:

compile-contract

Use this instruction, as shown below:

# Compiling the contract and fixing line ending
$ ligo compile-contract v1-public-pool.ligo main | tr -d ‘\r’ > v1-publicpool.tz

Next, we will deploy our contract to provide the initial storage. However, as it is not possible to do so using the LIGO syntax, we will convert it to the Michelson language by using the following command:

$ ligo compile-storage v1-public-pool.ligo main “record liquidity = 0mtz; end” > 0

Now that we have deployed the contract, we can copy it to a Docker container by running the following command:

$ docker cp tezos-defi/v1-public-pool.tz alphanet_node_1_15080fa44b96:/home/tezos

Now it’s time to interact with Tezos.

Interacting with Tezos

We will create two accounts on the Alphanet – test, and test2 by using tezos-client through a public node. To interact with Tezos and avoid passing some arguments to configure the public node every time we want to run a command, we will now create an alias so that the following commands will be shorter.

We’ll enter the shell with the following command to do so:

$ ./alphaclient.sh shell

We will switch between ./alphaclient.sh shell and /tezos-defi working directory.

All the commands related to tezos-client should be performed inside ./alphaclient.sh shell. The other commands should run from the workspace.

The first step is to create an alias for tezos-client with our network configuration. The arguments are:

  • -A: 

    indicating the node’s address

  • -P: 

    indicating the node’s RPC port

  • -w:

    specifying the number of confirmation blocks the client has to wait for before considering
    an operation as included

# ./alphaclient.sh shell
$ alpha-client gen keys contractOwner
$ alpha-client gen keys katy

Both accounts will contain

 0 tez, so let's transfer tez 

from test2.

# ./alphaclient.sh shell
alpha-client transfer 10500 from test2 to contractOwner –burn-cap 0.257
alpha-client transfer 5 from test2 to katy –burn-cap 0.257

If you don’t specify the amount of

tez

available to burn as a fee, it will fail with the following message:

Fatal error:
The operation will burn 2.281 which is higher than the configured burn
cap (0).
Use ` –burn-cap 2.281` to emit this operation.

Now that we’ve interacted with Tezos as well, it is time to deploy the smart contract finally.

How to deploy the contract?

The deployment of a Tezos smart contract is called “origination.”

It represents the creation of an account that has a script attached to the smart contract. The address of contracts created via originations starts with KT1… (originated accounts) instead of implicit accounts with addresses beginning with tz1… (also tz2 or tz3).

To deploy the contract, we run the following command:

# ./alphaclient.sh shell
$ alpha-client originate contract v1-public-pool for contractOwner
transferring 0 from contractOwner running v1-public-pool.tz –init 0
–burn-cap 2.314

tezos-client provides us with the following command to list the address of the contracts deployed:

# ./alphaclient.sh shell
$ alpha-client list known contracts
> contract: KT1HkL7uH53X12CZeH8Jv9H2AYHrA9M9xDUt
Contract memorized as v1-public-pool.

Interacting with the contract

To make the contract functional, we must feed the contract with tez. So, we will deposit 10,000 tez using the contractOwner account.

The first step is to obtain the parameter to send to the contract’s entry point. We can get the Michelson code with the following command:

# /tezos-defi working directory
$ ligo compile-parameter v1-public-pool.ligo -s pascaligo main
“Deposit(unit)”
> (Left Unit)

We can now call the contract

# ./alphaclient.sh shell
$ alpha-client transfer 10000 from contractOwner to v1-public-pool
–arg “(Left Unit)” –burn-cap 0.004

We can also verify the result in multiple ways. For example, we can query the contract storage:

# ./alphaclient.sh shell
$ alpha-client get script storage for v1-public-pool
> 10000000000

We can also check that the contractOwner wallet has decreased by 10,000 tez.

# ./alphaclient.sh shell
$ alpha-client get balance for contractOwner
> 8044.90585

We can also use a block explorer.

Now, we can try the withdraw operation. This time the katy account will withdraw some tez from our liquidity pool. As we have transferred 5 tez to katy, it will be enough to pay transaction fees. To obtain the Michelson instruction, we run the following command:

X-INT ~/develop/tezos-defi-dev-experience/tezos-ligo [master|…4]
15:54 $ ligo compile-parameter v1-public-pool.ligo -s pascaligo main
“Withdraw(unit)”
(Right Unit)

Now, we call the withdrawal contract method.

# ./alphaclient.sh shell
$ alpha-client get balance for katy
$ alpha-client transfer 0 from katy to v1-public-pool –arg “(Right Unit)”
$ alpha-client get balance for katy

Conclusion

Tezos is a generic and self-amending crypto-ledger, so it is becoming one of the most popular blockchain platforms. Its unique features are attracting more and more people towards it. By following the right steps, one can conveniently deploy smart contracts on the Tezos blockchain platform.

Get in touch with our Tezos blockchain experts who can create and deploy Tezos smart contracts for your business.

Author’s Bio

Akash Takyar
Akash Takyar
CEO LeewayHertz
Akash Takyar is the founder and CEO at LeewayHertz. With the experience of building over 100+ platforms for startups and enterprise allows Akash to rapidly architect and design solutions that are scalable and beautiful.
Akash's ability to build enterprise-grade technology solutions has attracted over 30 Fortune 500 companies, including Siemens, 3M, P&G and Hershey’s. Akash is an early adopter of new technology, a passionate technology enthusiast, and an investor in AI and IoT startups.
Start a conversation by filling the form
Once you let us know your requirement, our technical expert will schedule a call and discuss your idea in detail post sign of an NDA.

All information will be kept confidential.

 Send me the signed Non-Disclosure Agreement (NDA)

Insights

How to Build a Tezos dApp?

Tezos is an open-source dApp and smart contracts platform. It is a self amending cryptographic ledger which uses the Delegated Proof of Stake (DPoS) consensus protocol.

read more