Enabling Smart Contract Interaction in Tezos with CPS

With code samples, this blog post demonstrates how to execute an entry point in a counter contract and set a value in contract storage to be used later.

Inter-contract invocations

Naturally, smart contract development involves a variety of scenarios where the contracts need to interact with each other. To establish such communication, developers can make use of a continuation-passing style and the LIGO language.

A continuation-passing style (CPS) is a programming style, where control is passed in the form of a continuation. The basic idea is that a function in CPS takes an extra argument — a function to be called to provide the return value, when the CPS function has ended. Having computed the resulting value, the CPS function calls the continuation entry point with the computed value as an argument.

To invoke a smart contract entry point, it is necessary to emit a transaction operation. For this purpose, we are going to rely on the following three functions provided by LIGO:

  • Tezos.transaction allows for transferring tez to an account or run code of some other smart contract.
  • Tezos.get_contract_opt gets a smart contract from a specific address. If no smart contract is found, or the contract doesn’t match the type, None is returned. This function allows for using pattern matching.
  • Tezos.get_entrypoint_opt gets a smart contract from an address and an entry point. To pass an entry point as a parameter, we need to write it in the %entrypoint form. If no smart contract is found, or the contract doesn’t match the type, None is returned, so we can use the pattern matching, too.

We are going to employ these three functions across all the examples of smart contract interaction below.

A one-way invocation via a proxy contract

In this one-way inter-contract invocation example, we will learn how to execute an entry point in a counter contract using an intermediate proxy contract .

The whole logic will be encapsulated in the proxy contract , where the proxy implementation will delegate the calls to a counter contract . This way, we enable the separation of concerns and gain access control over a counter contract .

You can access the source code of the above-mentioned examples in this GitHub repo. It is important to note that the types of the contract must always be the same.

A two-way inter-contract invocation

In this two-way inter-contract invocation example, an inspector contract will set a value in the storage of a sender contract. This value will be accessible for all the invocation methods.

First, a sender contract calls an entry point of an inspector contract . In its turn, the inspector contract calls an extra argument — a smart contract with an entry point that has some value as an argument. Finally, the inspector contract sets the value as an argument in the sender contract’s storage.

You can access the source code of the above-mentioned examples in this GitHub repo. It is important to note that the types of the contract must always be the same.

Things to consider

While CPS enables us to perform inter-contract invocations, there are some things to keep in mind.

Tezos has two ways to authenticate transfers emitters in smart contracts:

  • SENDER returns the address, which called the current smart contract
  • SOURCE returns the address, which created the initial operation, ultimately leading to the smart contract call

A common recommendation is for SENDER to check who called the contract.

When dealing with asynchronous patterns , a sender contract is not blocked while waiting for an inspector contract to finish. This means, you will not get the information instantly.

You also have to be aware of gas consumption. Gas cost is typically determined by the size of both the code and storage, so keeping the storage small and writing small contracts is a good practice. Since inter-contract calls involve more than a single contract, it can be a challenge to keep gas costs low.

In some cases, it makes sense to pass record parameters — rather than a long list of parameters — when you invoke an entry point of another smart contract. Code readability can serve as a litmus test in such situations. By passing record parameters, you can also encapsulate the related parameters, thus reducing the total parameter count to any entry point. For example, you can chain different steps of calculations as separate functions, which accept input from the previous contract and return a result to be used in the next contract.

For more security recommendations, you can read two informative blog posts by the Tezos team about problems with concurrency and DeFi/ECR-777 attacks.

You can try out the scenarios shared in this blog post, and we encourage you to add your own examples to this GitHub repository, so the whole community benefits, too.

You can find the original post in medium.

3 Likes