Transforming FunC into FunCtional with Haskell: How Serokell Won the Telegram Blockchain Competition

You've probably heard that Telegram is set to launch its blockchain platform Ton.But you might have missed the news that not long ago, Telegram announced a competition for the implementation of one or more smart contracts for this platform.

The Serokell team, with extensive experience in developing large blockchain projects, couldn't stay on the sidelines. We delegated five team members to the competition, and just two weeks later, they took first place under the (un)subtle random nickname Sexy Chameleon. In this article, I'll share how they achieved this. We hope that within the next ten minutes, you'll at least read an interesting story, and at most find something useful that you can apply in your own work.

But let's start with a brief immersion into the context.

The competition and its conditions

So, the main tasks for participants were the implementation of one or more of the proposed smart contracts, as well as suggestions for improving the TON ecosystem. The competition ran from September 24 to October 15, and the results were announced only on November 15. Quite a long time, considering that during this period Telegram managed to conduct and announce the results of contests for design and application development in C++ for testing and assessing the quality of VoIP calls in Telegram.

We selected two smart contracts from the list provided by the organizers. For one, we used the tools distributed with TON, while the other was implemented in a new language developed by our engineers specifically for TON and integrated into Haskell.

The choice of a functional programming language is not accidental. In our corporate blog we often discuss why we consider the complexity of functional languages to be a significant exaggeration and why we generally prefer them over object-oriented ones. By the way, there is also the original of this article..

Why we decided to participate at all

In short, because our specialization is unconventional and complex projects that require special skills and often have scientific value for the IT community. We strongly support open-source development and work on its promotion, as well as collaborate with leading universities in Russia in the fields of computer science and mathematics.

The interesting tasks of the competition and our involvement in the beloved Telegram project were motivating in themselves, and the prize pool served as an additional incentive. 🙂

Researching the TON blockchain

We closely monitor new developments in blockchain, artificial intelligence, and machine learning, and we strive not to miss any significant releases in each of the fields we work in. So by the time the competition started, our team was already familiar with the ideas from the TON white paper. However, before starting with TON, we had not analyzed the technical documentation or the actual source code of the platform, so the first step was quite obvious — a thorough examination of the official documentation at the website and in P.P.S. from the translator.

By the time the competition began, the code had already been published, so to save time, we decided to look for a guide or a summary written by users. Unfortunately, this yielded no results — besides the assembly instructions for the platform on Ubuntu, we couldn't find any other materials.

The documentation itself turned out to be well-structured, but some parts were difficult to read. Quite often, we had to return to specific points and switch from high-level descriptions of abstract ideas to low-level implementation details.

It would have been simpler if the specifications didn’t contain detailed implementations at all. Information about how the virtual machine represents its stack is more distracting for developers creating smart contracts for the TON platform than it is helpful.

Nix: Building the project

At Serokell, we are big fans of Nix. We build our projects for it and deploy them using NixOps, and all our servers have it installed. . By default, a trimmed version of postmarketOS is preinstalled, designed for testing basic subsystems. The software environment can be loaded directly from the SD card without the need to carry out a firmware update.This ensures that all our builds are reproducible and work on any operating system where Nix can be installed.

Therefore, we started by creating a Nix overlay with an expression for building TON. With it, compiling TON is incredibly simple:

$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && make

Note that you do not need to install any dependencies. Nix will magically take care of everything for you, whether you are using NixOS, Ubuntu, or macOS.

Programming for TON

Smart contract code in the TON Network runs on the TON Virtual Machine (TVM). TVM is more complex than most other virtual machines and has some very interesting features, such as its ability to work with continuations and data links.

Moreover, the team from TON has created three new programming languages:

Fift — a universal stack-based programming language, reminiscent of Forth. Its superpower is the ability to interact with TVM.

FunC — a programming language for smart contracts that resembles C and compiles into another language — Fift Assembler.

Fift Assembler — a Fift library for generating binary executable code for TVM. Fift Assembler does not have a compiler. It is an embedded domain-specific language (eDSL).

Our contest entries

Finally, it's time to take a look at the results of our efforts.

Asynchronous payment channel

A payment channel is a smart contract that allows two users to send payments off-chain. This saves not only money (no fees), but also time (you don't have to wait for the next block to be processed). Payments can be as small as needed and occur as often as required. At the same time, the parties do not have to trust each other, as the fairness of the final settlement is guaranteed by the smart contract.

We found a quite simple solution to the problem. The two parties can exchange signed messages, each of which contains two numbers — the total amount paid by each participant. These two numbers act as vector clocks in traditional distributed systems and establish a 'happened-before' order on transactions. Using this data, the contract can resolve any potential conflict.

In fact, to implement this idea, just one number would be enough, but we kept both so that we could make a more user-friendly interface. Additionally, we decided to include the payment size in each message. Without it, if a message is lost for some reason, while all amounts and the final settlement would be correct, the user might not notice the loss.

To validate our idea, we searched for examples of using such a simple and concise payment channel protocol. To our surprise, we found only two:

  1. Description a similar approach, only for a one-way channel.
  2. Tutorial, which describes the same idea as ours, except without explaining many important details, such as overall correctness and conflict resolution procedures.

It became clear that it makes sense to describe our protocol in detail, paying particular attention to its correctness. After several iterations, the specification was ready, and now you can also take a look at it.

We implemented the contract in FunC, and the command-line utility for interacting with our contract was completely written in Fift, as recommended by the organizers. We could have chosen any other language for our CLI, but we were curious to try Fift to see how it performs in practice.

Honestly, after working with Fift, we saw no compelling reason to prefer this language over popular and widely used languages with developed tooling and libraries. Programming in a stack-based language is quite unpleasant since you have to constantly keep track of what is where in the stack, and the compiler does not assist with this.

Therefore, the only justification we see for the existence of Fift is its role as a host language for the Fift Assembler. But wouldn't it have been better to embed the TVM assembler into some existing language rather than inventing a new one for this essentially singular purpose?

TVM Haskell eDSL

Now it's time to talk about our second smart contract. We decided to develop a multi-signature wallet, but writing yet another smart contract in FunC would be too dull. We wanted to add a twist, and that twist became our own assembly language for TVM.

Like Fift Assembler, our new language is embeddable; however, instead of Fift, we chose Haskell as the host, which allowed us to fully utilize its advanced type system. When working with smart contracts, where even a small error can be very costly, we believe that static typing is a significant advantage.

To demonstrate what the TVM assembler built into Haskell looks like, we implemented a standard wallet on it. Here are a few things to note:

  • This contract consists of a single function, but you can use as many as you want. When you define a new function in the host language (that is, in Haskell), our eDSL allows you to choose whether you want it to become a separate subroutine in TVM or simply be embedded in the call site.
  • Like in Haskell, functions have types that are checked at compile time. In our eDSL, the input type of a function is the stack type that the function expects, and the result type is the stack type that will result after the call.
  • The code has annotations stacktype, describing the expected stack type at the call site. In the original wallet contract, these were just comments, but in our eDSL, they are actually part of the code and are checked at compile time. They can serve as documentation or assertions that help developers identify issues in case the stack type changes when modifying the code. Naturally, such annotations do not affect runtime performance since no TVM code is generated for them.
  • This is still a prototype, written in two weeks, so there is still much work to be done on the project. For example, all instances of classes that you see in the code below should be generated automatically.

Here’s what the implementation of a multisig wallet looks like in our eDSL:

main :: IO ()
main = putText $ pretty $ declProgram procedures methods
  where
    procedures =
      [ ("recv_external", decl recvExternal)
      , ("recv_internal", decl recvInternal)
      ]
    methods =
      [ ("seqno", declMethod getSeqno)
      ]

data Storage = Storage
  { sCnt :: Word32
  , sPubKey :: PublicKey
  }

instance DecodeSlice Storage where
  type DecodeSliceFields Storage = [PublicKey, Word32]
  decodeFromSliceImpl = do
    decodeFromSliceImpl @Word32
    decodeFromSliceImpl @PublicKey

instance EncodeBuilder Storage where
  encodeToBuilder = do
    encodeToBuilder @Word32
    encodeToBuilder @PublicKey

data WalletError
  = SeqNoMismatch
  | SignatureMismatch
  deriving (Eq, Ord, Show, Generic)

instance Exception WalletError

instance Enum WalletError where
  toEnum 33 = SeqNoMismatch
  toEnum 34 = SignatureMismatch
  toEnum _ = error "Uknown MultiSigError id"

  fromEnum SeqNoMismatch = 33
  fromEnum SignatureMismatch = 34

recvInternal :: '[Slice] :-> '[]
recvInternal = drop

recvExternal :: '[Slice] :-> '[]
recvExternal = do
  decodeFromSlice @Signature
  dup
  preloadFromSlice @Word32
  stacktype @[Word32, Slice, Signature]
  -- cnt cs sign

  pushRoot
  decodeFromCell @Storage
  stacktype @[PublicKey, Word32, Word32, Slice, Signature]
  -- pk cnt' cnt cs sign

  xcpu @1 @2
  stacktype @[Word32, Word32, PublicKey, Word32, Slice, Signature]
  -- cnt cnt' pk cnt cs sign

  equalInt >> throwIfNot SeqNoMismatch

  push @2
  sliceHash
  stacktype @[Hash Slice, PublicKey, Word32, Slice, Signature]
  -- hash pk cnt cs sign

  xc2pu @0 @4 @4
  stacktype @[PublicKey, Signature, Hash Slice, Word32, Slice, PublicKey]
  -- pubk sign hash cnt cs pubk

  chkSignU
  stacktype @[Bool, Word32, Slice, PublicKey]
  -- ? cnt cs pubk

  throwIfNot SignatureMismatch
  accept

  swap
  decodeFromSlice @Word32
  nip

  dup
  srefs @Word8

  pushInt 0
  if IsEq
  then ignore
  else do
    decodeFromSlice @Word8
    decodeFromSlice @(Cell MessageObject)
    stacktype @[Slice, Cell MessageObject, Word8, Word32, PublicKey]
    xchg @2
    sendRawMsg
    stacktype @[Slice, Word32, PublicKey]

  endS
  inc

  encodeToCell @Storage
  popRoot

getSeqno :: '[] :-> '[Word32]
getSeqno = do
  pushRoot
  cToS
  preloadFromSlice @Word32

You can find the complete source code of our eDSL and the wallet contract with multi-signature in this repository. And more detailed information was provided by our colleague Georgy Agapov.

Conclusions about the competition and TON

In total, our work took 380 hours (including familiarization with the documentation, meetings, and actual development). Five developers participated in the contest project: CTO, team lead, blockchain platform specialists, and software developers in Haskell.

We found the resources to participate in the contest without difficulty, as the spirit of the hackathon, close teamwork, and the need for rapid immersion in new technology aspects are always exciting. A few sleepless nights spent achieving maximum results under limited resources are compensated by invaluable experience and great memories. Moreover, working on such tasks is always a good test of the company's processes, as achieving truly worthy results is extremely difficult without well-tuned internal collaboration.

Beyond the poetry: we were impressed by the amount of work accomplished by the TON team. They managed to build a complex, beautiful, and most importantly, functional system. TON has proven itself to be a platform with great potential. However, there is still much to be done for this ecosystem to grow, both in terms of its use in blockchain projects and in terms of improving development tools. We are proud to now be a part of this process.

If you have any questions after reading this article or have ideas on how to apply TON to address your challenges, contact us — we are happy to share our experience.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster