Comparing Parity’s ink! with Solidity: EVM VS Substrate programming
In this series, I’ll be contrasting EVM chains and Substrate-based chains to clarify underlying misconceptions and misunderstandings associated with this subject matter.
The core focus of this article is on the technical (programming) aspect of both chains, but I’ll throw some light on a few other rudimentary concepts associated with them.
Substrate, developed by Parity technologies, is an open-source framework that offers a sort of “SDK” that can be used to build custom Blockchains.
Fun fact: Polkadot is the most prominent Blockchain developed using the Substrate framework at the core; being termed as the “Polkadot Runtime Environment”.
The core language for developing smart contracts on Substrate layers is Rust.
Some notable chains developed using Substrate are Polkadot, Aleph Zero, Apron Labs, Aventus, e.t.c.
On the other hand, the Ethereum Virtual Machine or EVM, initially created by Vitalik Buterin, is a program (or software) that runs smart contracts and computes the Ethereum network’s state each time a new block is added to the chain. The EVM lies upon Ethereum's hardware and node network layer.
Smart contracts on the EVM are developed using Solidity. Some Blockchains compatible with or capable of running the EVM are Binance Smart Chain (BSC), Polygon, Avalanche, e.t.c.
First of all, I’ll like to outline the core differences between the two chain types in a tabular checklist format.
In my honest opinion, the Substrate framework is an amazing innovation by Parity technologies and holds the key to modern, ideal and futuristic Blockchain networks.
Having spotted the core differences between both infrastructures, I’ll be diving into comparing ink! (Rust) and Solidity.
Note that Substrate and EVM are not Blockchains.
Solidity
Solidity is an object-oriented, high-level language for developing smart contracts. Designed as a curly-bracket language type, it is made to target the Ethereum Virtual Machine (EVM). Its syntax and design pattern are influenced by C++, Python and JavaScript.
A typical Solidity smart contract looks as thus (from the Solidity documentation):
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address payable public beneficiary;
uint public auctionEndTime;
// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change.
// By default initialized to `false`.
bool ended;
// Events that will be emitted on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// Errors that describe failures.
// The triple-slash comments are so-called natspec
// comments. They will be shown when the user
// is asked to confirm a transaction or
// when an error is displayed.
/// The auction has already ended.
error AuctionAlreadyEnded();
/// There is already a higher or equal bid.
error BidNotHighEnough(uint highestBid);
/// The auction has not ended yet.
error AuctionNotYetEnded();
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// Create a simple auction with `biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `beneficiaryAddress`.
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
auctionEndTime = block.timestamp + biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() external payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
if (block.timestamp > auctionEndTime)
revert AuctionAlreadyEnded();
// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() external returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
// msg.sender is not of type `address payable` and must be
// explicitly converted using `payable(msg.sender)` in order
// use the member function `send()`.
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() external {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
if (block.timestamp < auctionEndTime)
revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}
ink!
ink! is an embedded Domain Specific Language (eDSL) developed by Parity technologies to write smart contracts in Rust for Blockchains built upon the Substrate framework. ink! contracts are compiled into WebAssembly and can be deployed to any Substrate-based chain.
An example smart contract from the ink! docs can be seen below:
#[ink::contract]
mod flipper {
/// The storage of the flipper contract.
#[ink(storage)]
pub struct Flipper {
/// The single `bool` value.
value: bool,
}
impl Flipper {
/// Instantiates a new Flipper contract and initializes
/// `value` to `init_value`.
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
Self {
value: init_value,
}
}
/// Flips `value` from `true` to `false` or vice versa.
#[ink(message)]
pub fn flip(&mut self) {
self.value = !self.value;
}
/// Returns the current state of `value`.
#[ink(message)]
pub fn get(&self) -> bool {
self.value
}
}
/// Simply execute `cargo test` in order to test your contract
/// using the below unit tests.
#[cfg(test)]
mod tests {
use super::*;
#[ink::test]
fn it_works() {
let mut flipper = Flipper::new(false);
assert_eq!(flipper.get(), false);
flipper.flip();
assert_eq!(flipper.get(), true);
}
}
}
Having seen the syntactic differences between both programming languages, it’s appropriate to know the differences in terms of behaviour and execution as well.
Comparing ink! and Solidity
- ink! is capable of running on any WASM (WebAssembly) Virtual Machine, while Solidity is designed specifically for EVM-compatible networks.
- Programs written in ink! are encoded in WebAssembly, while those in Solidity are encoded in EVM Byte code.
- Multiple constructor functions can be written in ink!, whereas only a simple constructor function must be written in Solidity.
- ink! smart contracts are written with overflow protection by default, while Solidity doesn’t cater for overflows at its core (one has to manually cater for situations that would lead to overflows).
- ink! programs can be written with tools that support Rust, whereas, Solidity has a custom toolkit.
- Both programming languages are designed with Semantic versioning.
- Both programming languages have metadata; i.e. Application Binary Interface (ABI).
- Storage size is dynamic (variable) for ink!, whereas, Solidity defines storage in 256 bits.
Rust is an ideal smart contract language; take this from me.
Kudos to Parity tech for developing a framework this revolutionary!
Check out some of the biggest networks running on Substrate below (this isn’t a complete list):
I strongly believe that you have seen the differences between both of these programming languages and that you’d be willing to get started developing in ink! today (Solidity is cool too, but ink!, due to the power that Rust has, is a better option for your next Blockchain project, compared to Solidity).
Ceteris paribus, I hope you enjoyed reading this article.
Kindly check me out on Twitter and drop a follow 💚
Thanks for reading up to this point of this article, fren!
New to trading? Try crypto trading bots or copy trading