Smart Contract Programming Languages’ Trade-offs

Not all smart contract languages are created equally — Explore the tradeoffs of Solidity, Cairo, Rust, and Move’s language designs.

Rati Montreewat
SCB 10X OFFICIAL PAGE
10 min readDec 14, 2022

--

Photo by Fatos Bytyqi on Unsplash

I believe that some novice smart contract developers or even proficient web2 developers may be curious about how they choose the first smart contract programming language to learn. Now, there are many choices available, namely Solidity for EVM, Cairo for Starknet, Rust for Solona, Move for Aptos & Sui, etc.

The truth is that there are no absolute right or wrong answers. Every language has trade-offs. The real question is “are we aware of the differences when working on a specific smart contract programming language?”

Personally, I use this diagram to visualize basic programming languages’ features and their limitations.

Useful vs Safety Languages’ Trade-offs

In fact, this trade-off is not only specific to the smart contract context. It is applicable to any language.

Safety

Safety has two following characteristics:

  1. Progress: a well-typed program can be evaluated or a value
  2. Preservation: The evaluation steps maintain types.

Thus, once the types have been checked, they can be ignored, and the computation runs without errors.

An example of a safe programing language is “Haskell”. It is a general-purpose and purely functional programming language with a static & strong type system. It can be considered a mathematic language with the least syntax garbage. Its purity offers deterministic, immutability, and controlled side effects. Consequently, the programs written in Haskell are readable, reasonable, maintainable, modular, and testable. All of these, directly and indirectly, link to safety property.

Hence, Haskell is a great solution when prototyping a Domain Model or Domain Type due to its sophisticated Type-Inference engine. It allows us to perform a low-cost test drive development. This helps us to get quick iteration and feedback before putting great effort into real implementation on production in another language.

Usefulness ( Effectiveness)

To be more clear, you might be wondering why Haskell is just useful for academic & research work and not typically used in production ?!! Its functional style makes it useful for some work such as a compiler. Nonetheless, it does not make sense when dealing with some of the other industrial problems which need performance or efficiency like web servers, as these require state manipulation or side effects, and the imperative programming paradigm does a very good job on these. In other words, there are two models of computation (Lambda calculus for functional programming and Turing machine for imperative programming), and there is no best of the two worlds.

Importantly, I note that “Usefulness” in this context means “effective”. In particular, it has effect on the world. It changes states of something. I definitely does not mean that one language with use-fullness is superior than the others.

However, the world is moving forward to a functional paradigm. we can see some modern programming language is implementing features and design, based on functional programming. Examples are functors, monoids monads and etc.

In consequence, multi-paradigm programming languages (mixing between object-oriented (OOP) vs functional style) are gaining popularity. Specifically, it does not strictly require imperative, OOP, or functional programming. In Rust language, the programmer can, for instance, choose between a simple boiler-page-free macro or a powerful (but procedural) macro. Another example would be the Scala language. It is still a functional programming language, but less pure than Haskell. Nevertheless, it also supports OOP, and it is practical enough to solve real-world industry problems.

What about useful languages for industrial production? Examples include: Java, Ruby, Python, Javascript, Typescript, Rust, Scala and etc.

This can be represented by the following diagram:

*Noted that this diagram is an ordinal scale. It shows significant order but unknown differences, and no “zero” scale exists.

I note that this chart is borrowed and modified from Simon Peyton Jones (a contributor of Haskell language).

Someone makes an interesting point that “Usefulness” is subjective. Many experienced developers might, for example, argue that Typescript is more useful than Javascript. In this context, I point out that a program written in Typescript, however, needs more time to define types, given that there are no 3rd libraries developed before. Hence, Javascript might be more useful.

Again, I repeat that the chart does not imply that one language is better than the others. Actually, they can not be judged. I does not mean that Java is ,in practice, more useful than Rust. It is just a metaphor to compare between functional and imperative paradigm.

Moving on to the smart contract programming language context…

According to the diagram, the usefulness, however, is more relevant to blockchain’s unique features, such as trustlessness and decentralization. Precisely, smart contract programs should be written to do what traditional programs can not solve.

Different languages have different features. This means that those languages also have different corresponding traded security to different degrees, depending on each language.

In this chart, there are four smart contract languages to compare. They are Solidity, Cairo, Rust, and Move.

Solidity

Solidity is the first mainstream Turing-complete programming language, enabling Ethereum Virtual Machine: EVM to do composable computation. — Plus, it is compiled and high-level language.

“Composability”, for example, enables anyone to combine existing deployed smart contract components. This allows us to innovate new complex systems by duplicating, modifying, and integrating those interoperable legos. Furthermore, smart contract application is often open-source, meaning developers can access their source code for free.

For traditional software, developers sometimes, on the other hand, have to develop some basic components from the ground up, and this costs money and time spent.

Notably, the program written in Solidity allows:

  • Ability to be compiled to Transparent byte code (no hidden web APIs) and ABI: Application Binary Interface (Standardized API structure )
  • No downtime, ensuring high availability
  • Basic State Machine functionality in EVM (state, access, update, etc)
  • Composable interfaces between different smart contract applications
  • Built-in gas and payment system (no dependence on 3rd payment parties)
  • Low-level performance access via Solidity Assembly

However, the primary trade-off for Composable Computation is security. Composability, to some extent, means that it can not be helped that external actors could deploy malicious smart contracts while hiding their identity. As well, those actors can not only access the smart contract application’s codebase, but they can also do stress tests without limitation.

Compared to traditional software, such attackers can not often observe the codebase when the software is closed-source. Moreover, if there is a bug, the maintainer still keeps offline during the bug discovery phase.

Thereby, Solidity could be classified as High Usefulness and Low Safety.

Cairo

Cairo is the first Turing-complete programming language, enabling layer 2 systems to do Provable Computation.

Provability, in this context, means that a proof can be generated on the layer 2 system (Prover, Starknet OS, Sequencer) to be verified on the layer 1 full node (via EVM) that the Cairo program output has been correctly computed. This allows scalability because the running time for generating proof is nearly linear, while that figure for verification is poly-logarithmic in the number of computations. Thus, the cost of on-chain computation does not change much with the computation size. In simple words, the computations occur on layer 2 rather than layer 1, and the full node on layer 1 use fewer computation resource to validate generated proofs. This is because there is no re-execution of computation in the verification process.

Prover is a separate process that receives the output of Cairo programs and then generates STARK proofs to be verified. The Prover submits the STARK proof to the verifier that registers the fact on L1.

StarkNet OS is Cairo-based OS and is essentially the program whose output is proven and verified . It updates the L2 state of the system based on transactions as inputs.

Sequencer (or Block Proposer): This special node executes the StarkNet OS Cairo program with the relevant inputs, proves the result using the provers, and eventually updates the network state on the StarkNet Core Contract

Composable Computation in EVM, on the other hand, requires a different computation model and then a different paradigm from Provable Computation. In fact, solidity developers use OOP and imperative programming as the main paradigm. Whereas, Cairo developers abstract what the computed output is acceptable. You may think of this paradigm as functional programming, but it is non-deterministic.

Particularly, the program written in Cairo interoperates the following paradigms :

  • Ability to create STARK-provable programs for general computation.
  • felt, a low-level field integer data type. Mathematically, it uses modular arithmetic where the modulo is a prime number. It is more efficient when doing Provable Computation than uint256 .
  • Support on a read-only non-deterministic memory

However, the trade-off for Provable Computation is as the same as Composable Computation. The reason is that Cairo is solidity-equivalence. This means that there exists a compiler so that a smart contract program written in Solidity could be compiled into the smart program written in Cairo. As a result, the Cairo ecosystem inherits many Ethereum standards (Ethereum Improvement Proposals: EIPs). It is, to some extent, easy for new developers to start developing if these developers have developed in the EVM ecosystem before. It is noted that the security risk is also inherited, but Cairo developers need more serious consideration for it.

Apart from common vulnerabilities found in solidity(e.g. re-entrance, parity and etc), Cairo introduces new arising attack vectors. For example, the Cairo language only has a single data type, Finite Field (aka felt ). Consequently, libraries to work with other data types are needed and this could potentially lead to security risks. Another interesting bug is the Under-constrained bug. In Cairo, the program describes the desirable result, and Hint abstracts how the prover could generate such results.

A hint is a block of code (not necessarily to be Cairo), that will be executed by the prover right before the next instruction

The issue occurs when there is an inappropriate assertion, so the constraint encoded in Cairo code allows a malicious prover to generate the undesired result. This is because those provers do not necessarily use developers’ hints!!.

To sum up, Cairo has far more security risk than Solidity and it can be represented by:

Rust

Rust is a compiled, system-level, and multi-paradigm programming language (concurrent, functional, and imperative style).

Specifically, Rust interoperates the following features:

  • Functional programming: closures, anonymous functions, iterators,
  • Traits, (interfaces In other languages, it is interfaces)
  • Lifetimes (handling references)
  • Zero cost abstractions
  • Meta-programming via macro system Asynchronous programming (async/await)

Practically, Rust developers can effectively manage memory and exploit parallel processing with high performance. A good example would be Solana which helps to achieve high scalability with high throughput.

What is more, it is known that many cryptography algorithms and zero knowledge-based applications are implemented, using Rust.

Nevertheless, blockchain usefulness (like trustlessness and decentralization) is traded since it is a generic programming language and is not limited to smart contract context. In other words, Rust is safer than other smart contract languages mentioned above.

In fact, Rust’s primary feature is Safety. Rust can provide strong safety guarantees, thanks to the borrowing checker feature. Once the code is compiled with adequate testing, the program will work with a safety guarantee. For instance, Rust make a good job for string processing, since it’s easy to write code that can’t overflow.

It is noted that although technical implementations unrelated to the code intention (eg. underflow) are minimized, developers still need to take care of potential flaws in either domain business or design levels.

As a consequence, Rust can be added here:

Move

Move is an interpreted, and OOP programming language. It is intentionally designed for the blockchain-based application.

Specifically, Rust emphasizes the following paradigms:

  • First-class Resources Management (assets are abstracted as a specific resource type system)
  • Verifiability (off-chain static verification tools)

Plus, Move is based on Rust. So, it allows high throughput, due to efficient memory management and parallel processing. It, notwithstanding, is interpreted language not compiled one. Generally speaking, the performance could be slower.

Regarding its Safety, the bugs from the compiler are eliminated, because Move is an interpreted language. Besides, some well-known smart contract vulnerabilities (e.g. re-entrance) are removed, thanks to the safe resource type system.

Despite this, developers need to be paranoid when programming Move smart contracts. As it is a very new language with a relatively small community, security practice is still immature and there are so many unknown logical bugs for developers to jump down rabbit holds.

So, Move could be considered as between Solidity and Rust as follows:

Final Thought

As aforementioned, trade-offs between useful vs safety are quite different across these programming languages. Although this trade-off is always true for both traditional and smart contract programming languages, the lifecycles for both those smart contract languages and relevant frameworks are much shorter than traditional ones. In addition, more and more new blockchain language arises every day, and there is zero way that a single person can be an expert in all languages in a short time. Therefore, choosing the right sustainable language which will last long is quite important. I hope that the diagram provided could be used as a guideline before digging deep into any language.

--

--