This article offers a comparison between two Web3 smart contract programming languages, sCrypt and Solidity, to help you decide which one you should use for building smart contracts in the Bitcoin or Ethereum Virtual Machine ecosystems, respectively.
Solidity
Solidity emerged as the first-ever programming language for smart contracts and remains the most extensively utilized language in the Web3 space due to its first-mover advantage. It serves as the primary language for developing applications today on Ethereum and
Ethereum Virtual Machine (EVM) compatible blockchains, including Binance Smart Chain and Tron.
sCrypt
sCrypt was developed with the aim of ensuring the utmost safety, security, and predictability in managing blockchain assets, while also equipping developers with the tools to create cutting-edge blockchain applications. Introduced 8 years following Solidity, sCrypt incorporated many lessons learned from the early years of Web3 application development, emphasizing the critical need for secure and reliable smart contracts. It is the primary smart contract language for Bitcoin Virtual Machine (BVM) based blockchains such as BTC, BSV, and MVC.
Overview: sCrypt vs. Solidity
Smart contracts control and manage billions of dollars’ worth of cryptocurrency and digital assets. Security flaws can lead to direct financial losses, particularly due to irreversible transactions and immutable code. Losses from hacks involving Solidity smart contracts have amounted to hundreds of millions of dollars over the years. These losses stem from a variety of vulnerabilities and attack vectors.
The Ethereum community has made significant efforts to enable developers to create secure smart contracts by providing security guidelines and compiling information on known attacks and vulnerabilities. However, despite these efforts, security issues persist, largely because the Solidity language inherently permits these vulnerabilities. In contrast, sCrypt effectively eliminates many of these security flaws right from the start.
sCrypt, being a modern language, learns from Solidity’s flaws. It adopts a distinctive approach in its design, focusing primarily on security and ensuring predictable behavior. The foundational principles of sCrypt were significantly influenced by analyzing prevalent Solidity vulnerabilities, leading to the development of a language specifically engineered with predictability and security as its primary objectives. sCrypt prioritizes security from the ground up. Its design was informed by a thorough review of the typical errors, vulnerabilities, and challenges encountered in smart contract development. The philosophy behind sCrypt emphasizes that if it’s possible to maintain functionality and expressiveness while greatly reducing the likelihood of human mistakes, such measures should be implemented at the language level.
Establishing the right mental frameworks for smart contracts and blockchain systems is crucial: complexity introduces a higher likelihood of bugs, leading to further complexity in attempts to fix those bugs. This cycle can trap us in a continuously escalating complexity spiral, as witnessed in Solidity. Tackling these challenges at the programming language level can prevent this compounding complexity.
Below is a high-level comparison between these two languages:
Decidability
sCrypt is considered decidable, since an sCrypt smart contract always terminates and halts in a finite number of steps, for any given input. Put simply: it is guaranteed that sCrypt contract execution will end.
Solidity supports unbounded loops, recursion, and dynamic function calls, which makes it impossible to decide if a Solidity smart contract will halt or not.
sCrypt disallows unbounded loops and recursion at language level. Surprisingly, this is achieved without sacrificing expressivity, meaning Bitcoin smart contracts are still Turing-Complete.
The fact that sCrypt is decidable ensures that developers (and their tools) can more straightforwardly understand and accurately predict how sCrypt contracts will behave, no matter what the input is. In many situations, particularly with high-stakes transactions or critical systems, this predictability is highly valued.
Decidability also allows for formal verification by complete static analysis of the entire call graph of a given smart contract. Formal verification is a popular method in security testing and auditing, using mathematical proofs to verify that specific properties of the contracts will consistently hold true or false under all conditions. Doing so to Solidity is incredibly difficult, due to the explosion of available paths.
Reentrancy
Reentrancy occurs when a smart contract makes a call to another contract, which then makes a call back to the original contract, effectively re-entering the initial logic. For instance, Contract A calls Contract B, waiting for a specific condition to be fulfilled before it updates its state. Meanwhile, Contract B calls back into Contract A. If your contract design isn’t meticulously crafted, reentrancy can cause your code to behave in unintended ways, whether due to a bug or as an exploit by a malicious entity. It could enable an attacker to execute multiple withdrawals before the contract updates its internal ledger, exposing the contract to various risks, including the potential for the total token balance to be siphoned off, as was infamously the case with the DAO hack.
Solidity permits reentrancy and has recently introduced an optional [noReentrancy] guard that developers can choose to implement or not. Ultimately, the responsibility is on the developers to use this feature.
In contrast, sCrypt does not permit reentrancy at all, eliminating this risk by design.
Overflows and underflows
In Solidity, overflows occur when a calculation exceeds the maximum value that can be stored, while underflows happen when a calculation falls below the minimum value. Such events can throw smart contracts into chaos, and attackers may deliberately trigger them in contracts that are poorly coded. Typically, this results in the contract becoming inoperable or being depleted of its tokens.
sCrypt guards against overflows and underflows by using only big numbers called bigint, a data type that can store integers with an arbitrarily large number of digits.
Learning Curve and Developer Tooling
sCrypt is an embedded Domain Specific Language (eDSL) based on TypeScript. It is strictly a subset of TypeScript, so all sCrypt code is valid TypeScript. TypeScript is chosen as the host language because it provides an easy, familiar language (JavaScript), but with type safety. There’s an abundance of learning materials available for TypeScript and thus sCrypt, including online tutorials, courses, documentation, and community support. This makes it relatively easy for beginners to start learning. It also has a vast ecosystem with numerous libraries and frameworks (e.g., React, Angular, Vue) that can simplify development and integration with Web2 applications.
Solidity presents a steeper learning curve, especially for those new to blockchain concepts. It requires a solid understanding of specific security practices and the Ethereum ecosystem. While there are growing resources for learning Solidity, including official documentation, tutorials, and courses, the pool is orders of magnitudes smaller compared to TypeScript.
Debugging Solidity code can prove to be difficult because of the limited availability of advanced debugging tools. This limitation hampers the ability to diagnose and fix issues, particularly in the case of complex contracts. sCrypt, being TypeScript, enjoys many modern debuggers such as in Visual Studio Code. It is supported by any TypeScript IDEs, including Visual Studio Code, WebStorm, Vim and Atom.
Pure
A pure function is a specific type of function that has the following characteristics:
- _Deterministic Output: _The output of a pure function is solely determined by its input values. Given the same input, a pure function will always return the same output.
- Stateless: A pure function does not maintain or require any internal state to compute their output.
- _No Side Effects: _A pure function does not cause any observable side effects in the outside world. This means it does not modify any external state, global variables, or data outside its scope, nor does it depend on any external state. Pure functions align closely with mathematical functions like quadratic (y=ax²+bx+c) or trigonometric (y=sin(x)). They allow for greater predictability, easier testing, and more efficient code optimization by compilers.
All sCrypt functions declared as public are pure and boolean, evaluating to true or false. A function returning true will always return true, regardless of where, when, and how it is run. Its return is only determined by the calling transaction and arguments, which are both local. This means if a function is true when tested locally, it will be deemed true by miners when the enclosing transaction is broadcast. We do not even need to run a local node (e.g., regtest) to test it, since it can be evaluated without accessing a miner network.
In Solidity, functions are impure and capable of interacting with the blockchain through various actions, including reading from and altering the contract’s state, transferring ether, and invoking other contracts. The only exception is functions marked as pure. This impurity and unpredictability can make the code harder to test, debug, and reason about. Impure functions that alter contract states or perform transactions can be exploitable points for attackers.
Postconditions
Postconditions in programming are conditions or predicates that must hold true immediately after the execution of a program or a specific block of code, such as a function or method. They are a key concept in the design by contract methodology, where they are used to specify and document the expected state of a system after a piece of code executes. Postconditions help in ensuring that a program behaves as expected and can be used for debugging, testing, and formal verification purposes.
Let’s consider a function that increments a given number by one. To illustrate postconditions in JavaScript, a simple check is included after the function’s execution to ensure the postcondition holds true.
function incrementByOne(number) {
const result = number + 1;
return result;
}
// Function to check postcondition
function checkPostcondition(input, output) {
if (output !== input + 1) {
throw new Error('Postcondition failed: The output is not one greater than the input.');
}
}
// Example usage
const inputNumber = 5;
const incrementedNumber = incrementByOne(inputNumber);
// Check the postcondition
checkPostcondition(inputNumber, incrementedNumber);
console.log('Incremented number:', incrementedNumber);
In the context smart contract, postconditions protect developers and users from bugs and abuse.
In sCrypt contracts with state update, the new state has to be passed in when the calling transaction is constructed off chain, which is verified in the smart contract on chain. We are guaranteed the contract enters the expected and correct new state after the call.
In Solidity, only the action of how a state is updated is specified. The new state is generally not passed in and one could only hope the contract is updated to the new expected state, making it unpredictable and error-prone.
sCrypt verifies, while Solidity computes. The former is more secure and reliable.
Composition over inheritance
sCrypt emphasizes using composition instead of inheritance. This approach implies that sCrypt smart contracts do not engage in inheritance as observed in languages such as Solidity. Consequently, there’s no need to navigate through intricate class hierarchies or deal with contracts that carry implicit behaviors from their ancestors. sCrypt mandates explicitness and deliberate composition in its design. Without the concept of inheritance, developers are required to clearly define the actions of their code. This characteristic enhances the predictability and transparency of contracts and simplifies the audit process.
Top comments (0)