Tezos (Part 1): Creating, Deploying, and Interacting with a Contract

Protofire.io
Protofire Blog
Published in
9 min readOct 18, 2019

Learn how to perform key operations on Tezos, including creating, testing, compiling, deploying, and interacting with smart contracts.

Getting started with Tezos | Part 1 | Part 2 | Part 3

In this blog post, we will demonstrate how to create, deploy, and interact with a contract on the Tezos blockchain. Specifically, we will develop a generic liquidity pool contract that will enable users to deposit and withdraw Tezos tokens (tez) without any restriction. In our example, user Alice will deposit 10 tez to the contract, and then user Bob will withdraw 5 tez without making a prior deposit.

Note: We encourage developers to read the documentation of the LIGO language before proceeding with this tutorial.

Creating a contract

We are going to create a contract equivalent to this one written in Solidity. The final version of the contract can be found in this GitHub repo. The contract will be written in the LIGO language and then compiled to Michelson.

In order to compile and test the contract, we will need to install the LIGO CLI. You can check out the installation procedure in the official documentation. (Note: We encourage developers to use the vscode editor and the plugin developed by the LIGO team.)

Now, we can start to write the contract. We can create a folder and the file for our contract with the following commands. Note: open v1-public-pool.ligo in your editor to start writing the contract.

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

The current version of Tezos only supports a unique entry point for each contract (though this will change in the upcoming Babylon upgrade). The recommended workaround for this limitation is to use an argument, indicating the operation we want to perform.

The two operations we will exemplify in this tutorial are Deposit and Withdraw. In our contract, they will be represented with a variant type as shown below.

type entry_action is
| Deposit
| Withdraw

The storage for our contract will be a simple record with a liquidity field of the tez type, representing the funds that our pool contains.

type finance_storage is record 
liquidity: tez;
end

Note: At the time this tutorial was written, LIGO had an issue with tez and mtz getting mixed up. However, LIGO’s team is working on fixing it. In this guide, we use tez for declarations and mtz for literals.

Defining an entry point

The entry point for a contract in Tezos refers to the method selected to listen to external communication. It always receives two arguments: the parameters for the method and the contract storage. It also has a fixed signature for the response, operation list, and storage.

In the contract entry point, the parameter will be of the entry_action type, which we already defined. The value for this parameter must be one of the values defined by either the Deposit operation or the Withdraw operation. The body of the method will be empty. The output will be delegated depending on the value of the operation. In both cases, the contract storage is propagated.

The Deposit operation

The Deposit operation will take the amount sent to the Tx. If the amount is 0, it should fail as shown in the code comments below. (As this tutorial was written, LIGO had an issue with zero-value deposits. The issue will be resolved in the next release, but that is why we are skipping the action if the condition is true. If the amount is bigger than 0, we will increase the liquidity.)

We will define the storage as var (not const), because we are going to modify the storage.

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. We have to define this in the global scope of the contract. This defines a list initialized with nil.

const noOperations: list(operation) = nil;

The Withdraw operation

The Withdraw operation allows a user to withdraw a fixed amount of tez from the liquidity pool. The validation checks if the contract has enough tez to be transferred to the sender. We are using the global “sender” from LIGO, which refers to the account that originated the transaction. If the validation succeeds, the amount of the liquidity pool contract will decrease, and a transaction to the sender will be created and returned as the first argument of the function result. If the transaction fails, the contract storage will be reverted, and the liquidity pool will remain unchanged.

Testing the contract

Now that we have the full contact functionality, we can proceed to test it part by part. Thanks to the functional approach of the Tezos blockchain, we can test smart contracts without deploying them to a network. By using the dry-run instruction that LIGO provides, we can run and analyze the result.

Let’s test a deposit by running the command below.

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

Note that liquidity remains 0, because we haven’t sent any tez to the transaction. If we replace the skip with fail (“No tez transferred!”);, it will return an error, and the transaction will be declined. To make it work we need to set the --amount parameter 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.

To test the Withdraw method, we can proceed just as before. This time, we will initialize the storage with tez, and the liquidity pool will decrease.

$ 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 the same structure: a list of operations and the storage.

Compiling the contract

Now, it’s time to deploy a smart contract to the Tezos Alphanet.

The first step is to use LIGO’s compile-contract instruction as shown below.

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

Next, we deploy our contract to provide the initial storage. However, it is not possible to do so using the LIGO syntax, so we will convert it to the Michelson language. Luckily, LIGO provides a command to compile it.

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

That’s all we need to deploy a contract.

Now that we have the contract deployed, 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

Note: Replace a container name with the one running in your machine, you can get it by running docker ps.

Interacting with Tezos

In the previous blog post, we created two accounts on the Alphanet — test and test2 — using tezos-client through a public node. Each time we wanted to run a command, we had to pass some arguments to configure the public node. This time, we will create an alias, so the following commands will be shorter.

To do so, enter the shell with the following command.

$ ./alphaclient.sh shell

From now on, we will be switch between ./alphaclient.sh shell and /tezos-defi working directory. As a general rule, all the commands related to tezos-client need to be performed inside ./alphaclient.sh shell, and all the other commands need to be run from the workspace. To better illustrate this, we will put a comment at the top of each code sample.

To continue with this tutorial, please read our previous blog post on Tezos basics and then run ./alphaclient.sh shell.

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

  • -A: indicates the node’s address
  • -P: indicates the node’s RPC port
  • -w: specifies how many confirmation blocks the client has to wait before considering an operation as included
# ./alphaclient.sh shell$ alpha-client gen keys contractOwner$ alpha-client gen keys bob

Both accounts will contain 0 tez. Let’s change that by transferring tez from test2.

# ./alphaclient.sh shellalpha-client transfer 10500 from test2 to contractOwner --burn-cap 0.257alpha-client transfer 5 from test2 to bob --burn-cap 0.257

If you do not specify the amount of tez available to be burned as a fee, it will fail with a message as shown below.

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.

Deploying the contract

The deployment of a Tezos smart contract is called “origination.” It represents the creation of an account, that has a certain script attached to the smart contract. Contracts created through originations have an address starting with KT1… (originated accounts) as opposed to implicit accounts with addresses starting with tz1… (also tz2 or tz3, depending on the elliptic curve used).

To deploy the contract, we have to 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

Note: If you want to try a mock operation, you can add the--dry-run parameter at the end of the instruction. This operation will print important information, like the transaction ID, the amount of tez the deployment cost was worth, the address of the new contract, etc. We encourage you to take a closer look at it.

tezos-client provides us with a 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

In order to make the contract functional, we need to feed the contract with tez. So, using the contractOwner account, we will deposit 10,000 tez.

The first step is to obtain the parameter that will be sent to the contract’s entry point. We can get the Michelson code with the command below.

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

Now, we can call the contract.

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

Next, we can verify the result in several ways. We can query the contract storage.

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

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

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

Or we can use a block explorer.

Now, we can try the Withdraw operation. This time the bob account will withdraw some tez from our liquidity pool. Remember that we have transferred 5 tez to bob, this will be more than enough to pay transaction fees.

To obtain the Michelson instruction, we need to run the following command.

Now, we call the Withdraw contract method.

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

Summary

Now that we’ve learned how to set up and configure tezos-client and deploy a smart contract, the next step is to add some complexity to the contract. In the upcoming blog post, we will restrict the Withdraw operation for only those accounts that have enough deposited tez.

--

--

Protofire.io
Protofire Blog

We help token-based startups with protocol & smart contract engineering, high-performance trusted data feeds (oracles), and awesome developer tools (SDKs/APIs).