Smart Contract Head to Head — Ethereum vs. Flow
Developers looking to write smart contracts have a couple of solid choices, but which one is currently better ... and why?
Since the introduction of smart contract technology, Solidity has been the coding language of choice for smart contract developers.
However, if you’re a Solidity developer, you already know it has drawbacks. Among other flaws, major security concerns can arise from the simple mishandling of certain data types, and there are no built-in access controls.
A new smart contract language developed for the Flow blockchain, Cadence, learns from Solidity’s oversights and natively solves many of its inherent problems. And if you already know Solidity, it’s straightforward to learn!
This article introduces the Cadence smart contract language, details how it improves over Solidity, and then walks through a side-by-side comparison of both languages on some common smart contract examples. By the end, you should be comfortable with Cadence and ready to get started on Flow!
About Cadence
Cadence is the programming language that powers the Flow blockchain, which was originally created by Dapper Labs to support large-scale crypto games and NFT projects. (You’ve probably heard of NBA Top Shots—one of their most successful projects.) Its syntax might be familiar, since it was inspired by popular modern languages such as Swift, Rust, and Kotlin. Cadence is statically and strongly typed, with a resource-oriented approach and capability-based security model.
All of this means that Cadence is highly optimized for digital asset creation and management.
The most substantial innovation Cadence introduces is its resource-based paradigm. This approach makes it significantly easier to create, track, and manage a user’s assets. Instead of relying on a central public ledger as the source of truth (as Ethereum does), assets are tied directly to a user’s account storage. So an asset created as a resource, such as an NFT, can only exist in one location at a time. This ensures they only have one owner—either an externally owned account or a smart contract.
How Cadence Improves Over Solidity
Cadence improves over Solidity in many ways. Let’s look at three examples—small coding mistakes, security and access control, and contract deployment.
Those small coding mistakes
Some of the biggest issues with Solidity usually stem from the smallest mistakes. For example, initializing a variable with just a default value—although convenient at times—can lead to unexpected results if that variable is not changed. And fixed-range data types can lead to potential underflow or overflow situations, which would be disastrous if that data type represents a monetary value.
In Cadence, variable values must be set upon initialization, removing any unwanted results from default values. Additionally, integers in Cadence are automatically checked for underflow or overflow conditions, whereas you would need to inherit OpenZeppelin’s safe math library or use a version higher than 0.8 with Solidity.
Security and access control
When it comes to security and access control, Solidity requires you to create custom modifiers or inherit other security-based smart contracts, but also has many functions that are public by default.
With Cadence’s capability-based security model, accounts can only perform functions they have access to. This means Cadence has access control fundamentally built into the language itself. Also, methods defined on resource objects in Cadence cannot be susceptible to reentrancy attacks, something Solidity developers have to be keenly aware of when creating the flow of their logic.
Smart contract deployment
When issues in Solidity smart contracts are found, developers are unable to fix them without deploying an entirely new contract. And even then, the vulnerable contract still exists. Developers must ensure their user base switches over to the new contract.
In Cadence, smart contract upgradeability is built in and transparent. When the code is declared secure and final, the contract can be made immutable by removing the keys from the owner of the smart contract.
Overall, Cadence is a safer and more secure smart contract language that leaves less room for error.
Now let’s look in detail at the differences between smart contracts written in Solidity and Cadence. We’ll walk through a simple Hello World contract and then a more complicated NFT implementation.
Simple Contract
Let’s start with an all-time classic. We’ve all written “Hello World” in multiple languages, so it’s an easy intro to Cadence.
Let’s go through it step by step.
Contract Definition
First, we have the contract definition. The obvious difference is that the Cadence contract has an access control modifier: in this case, pub. This modifier ensures that everyone in the Flow network can access the contract, the default behavior for Solidity contracts.
However, in Cadence, we could also set the access control to access(account)
. This limits contract access to the account that deployed that contract. Here we already see a major difference between Flow and Ethereum. We don’t simply deploy contracts to the Flow blockchain; we deploy them to our account storage. On the Flow blockchain, each account is initialized with storage, where resources and structures can be stored. This storage has its own permissions, which allows us fine-grained control over who can execute the methods of our contract.
Contract Variables
The next line defines a string variable scoped to our contract. Semicolons are optional in Cadence, and a let
keyword is used to define the variable.
Cadence has two types of variables—mutable and immutable. Variables created with let
are immutable, or otherwise known as constants; we can only set them once, and they cannot be changed for the lifetime of the contract. We define mutable variables (ones that can be changed) with the var
keyword.
In this case, we set the variable value in the init method, because Cadence ensures that this method is only called once for every contract deployment.
Methods
The Cadence equivalent to Solidity’s constructor
is the init
method. This method is called exactly once—at the time a contract is deployed.
Inside the init method, we set the value for our greeting variable. While Solidity writes to contract variables by default, Cadence writes to local variables and requires you to use the self-object to access contract variables. This decision protects against you accidentally writing to a contract variable when making a typo.
The second method of our contract returns the greeting variable. In both Cadence and Solidity, we have to declare access for the method to be public, and both languages require us to define the return type. In this case, it’s a string.
But Solidity requires us to be more low-level here. It requires us to explicitly tell it where the string is located. It also makes us mark the functions as view, so that we don’t accidentally modify the state of the blockchain.
Cadence, on the other hand, doesn’t need that low-level control, since it is strongly and statically typed. Potential errors are caught before the program is run on-chain, making the whole method declaration more readable by removing redundant keywords.
NFT Contract
Next, let’s look at a basic NFT contract from both languages:
Since both languages have different approaches to this example, let’s look at them separately: first walking through the Solidity example, then Cadence.
Solidity
In Solidity, an NFT is basically a list of IDs. You must keep track of these IDs inside the smart contract and increment them manually to ensure uniqueness. Solidity doesn’t know anything about NFTs or their uniqueness. It’s just a list of IDs mapped to their owners, all managed manually within the contract. This leaves room for error if the ID incrementation is improperly handled, potentially resulting in multiple NFTs having identical IDs.
In the example, the NFT doesn’t have any additional data attached, but you could add another mapping ID to a URI. The contract ensures every newly minted NFT is mapped to an owner's address.
This is a simple example, of course. Usually, you would need to extend several interfaces to get a remotely secure NFT contract and features like metadata used to attach the well-known JPGs to your NFT, but the basic mechanisms are the same.
Cadence
Now, let’s look at the Cadence version and how it improves upon this Solidity example.
Resource Definition
The Cadence example starts with a resource type called NFT
. Notice the @ symbol in front of NFT
? This symbol is required, as it ensures the usage and behavior of resource types will remain explicit.
We can create instances from a resource, and it can have attributes just like a struct. The difference from a regular struct is that a resource is a special type that handles ownership in addition to the data it stores.
Inside the resource type NFT
, there is an id
field. The id
field is an integer, UInt64
, and it is a unique id given to each NFT
resource. This id
will be different for each NFT resource, meaning the resource cannot be duplicated or combined. Next, the id
field is initialized using the init
function.
Similar to how Rust’s borrow checker ensures only one function can modify a variable, Cadence ensures the same for its resources.
Resource Creation
When we create a new resource, we have to move it around storage locations. Our smart contract will not run properly if we leave the resource as is, so this forces us to be deliberate with resources and their storage locations. This form of control also ensures that resources never get lost or accidentally deleted; they can only be in one location at a time.
When we call the mint function, it will create a new instance of our NFT
resource. This function returns a resource with a type of NFT
and takes in the field, id
, from the resource that was defined earlier. The create
keyword is a bit like the new operator in object-oriented programming, creating a new resource. The <-
, or move-operator, makes it explicit that this resource isn’t available at the source after we called it.
Storing the Resource
The self.account
variable will point to the account we used as a deployment target for our contract. As we learned before: smart contracts aren’t deployed in a global namespace on the Flow network, but in special on-chain storage that belongs to your account. So, the smart contract knows which account it’s been deployed to and can use that information in its methods.
In this example, we use the account’s save
method. In the final init
function, we move the resource into the save methods first argument and tell it which path inside the account should store our NFT.
Since our NFT is a resource, no entity on the Flow network can copy it; we don’t have to keep track of its uniqueness explicitly.
Summary
Cadence is a fresh take on smart contract programming languages, optimized for asset creation and management. It’s a modern alternative that mitigates the deficiencies of Solidity through such means as forced management of variables and resources, security and access controls at a foundational level, and the ability to upgrade smart contracts before making them immutable. Cadence opens you to the possibilities of the Flow ecosystem, and incorporates many features from languages like Rust.
So, if you are a developer looking to write smart contracts in a fundamentally safer and more secure language, Cadence is an excellent option. To learn more, check out the Cadence Documentation and Flow Developer Portal.
Have a really great day!