Programming

What is an appropriate type for smart contracts

27 September 2026 · 14 min read

What is an appropriate type for smart contracts

Smart contracts are revolutionizing how we conduct agreements, offering unparalleled transparency, immutability, and automation. These self-executing contracts, with the terms of the agreement directly written into code, operate on a blockchain network, eliminating the need for intermediaries. However, the burgeoning ecosystem of decentralized applications (dApps) and various blockchain platforms has made the question of what is an appropriate type for smart contracts increasingly complex. Choosing the right smart contract type is not a trivial decision; it requires a deep understanding of your project’s goals, the underlying blockchain technology, and the specific functionalities you aim to achieve. This guide will explore the critical factors and common types to help you make an informed choice.

Understanding the Core of Smart Contracts

At its heart, a smart contract is a piece of code that lives on a blockchain and executes automatically when predefined conditions are met. Unlike traditional contracts, which are based on legal text and human interpretation, smart contracts rely on cryptographic security and the immutable ledger of the blockchain. This inherent design provides several advantages, making them a powerful tool for various applications across industries.

The core characteristics that define smart contracts are crucial for understanding their potential and limitations. These include their immutability, meaning once deployed, the code cannot be altered; their decentralization, as they operate on a distributed network without a central authority; and their deterministic nature, ensuring that given the same inputs, the contract will always produce the same output. These features collectively enable trustless agreements, where parties can interact with confidence, even without knowing each other.

According to IBM, smart contracts have the potential to streamline business processes, reduce costs, and minimize fraud across industries like finance, supply chain, and healthcare. For example, in a supply chain, a smart contract could automatically release payment to a supplier once goods are verified as delivered by an oracle. This level of automation and trust is unprecedented, but it also means that the initial design and choice of contract type are paramount, as errors or vulnerabilities can be extremely difficult to rectify post-deployment.

  • Immutability: Once deployed, the contract’s code cannot be changed. This offers security but demands meticulous pre-deployment auditing.
  • Decentralization: Operates on a distributed network, removing the need for central control and reducing single points of failure.
  • Automation: Executes automatically when predefined conditions are met, eliminating manual intervention.
  • Transparency: All transactions are recorded on the public ledger, visible to all participants.
  • Security: Protected by cryptographic principles of the blockchain, making them highly resistant to tampering.

Key Factors Influencing Your Smart Contract Choice

Selecting the most appropriate type for smart contracts involves a careful evaluation of several critical factors. The specific needs of your application will largely dictate the optimal choice, ranging from the blockchain platform to the complexity of the logic required. Ignoring these considerations can lead to inefficient, insecure, or even unusable contracts.

Firstly, the intended purpose of the smart contract is paramount. Are you creating a fungible token for a decentralized exchange (DEX), a non-fungible token (NFT) for digital art, a complex DeFi lending protocol, or an automated escrow service? Each of these use cases has distinct requirements regarding data handling, interaction patterns, and security considerations. For instance, a simple token contract might prioritize efficiency and low gas costs, while a DeFi protocol will require robust security measures and intricate financial logic.

Secondly, the choice of blockchain platform plays a significant role. Ethereum, with its extensive developer community and the Ethereum Virtual Machine (EVM), remains a popular choice for its flexibility and robust tooling, primarily using Solidity as its programming language. However, alternative platforms like Binance Smart Chain (BSC), Polygon, Solana, or Cardano offer different trade-offs in terms of transaction speed, cost, and consensus mechanisms. For example, Solana boasts higher transaction throughput, which might be critical for applications requiring high-frequency operations, while Cardano emphasizes formal verification for enhanced security. Understanding the nuances of each platform’s architecture and its smart contract capabilities is essential.

Finally, security requirements and the contract’s lifecycle management are vital. Given the immutable nature of smart contracts, vulnerabilities can be catastrophic. Projects dealing with significant financial value or sensitive data will require extensive security audits and potentially incorporate upgradeability patterns (e.g., proxy contracts) to allow for bug fixes or feature enhancements without breaking immutability. The complexity of the contract’s logic also influences the choice of programming language and design patterns, with simpler contracts being less prone to errors than highly intricate systems.

  • Application Purpose: Define the core function (e.g., token, marketplace, DeFi, supply chain).
  • Blockchain Platform: Consider Ethereum, Solana, Polygon, etc., based on scalability, cost, and ecosystem.
  • Security Needs: Evaluate the value at stake and the necessity for formal verification or extensive audits.
  • Scalability & Performance: Assess transaction volume and speed requirements.
  • Developer Ecosystem: Consider available tools, libraries, and community support for chosen languages.

The landscape of smart contract types is diverse, evolving rapidly with new innovations. While the underlying principles remain constant, specific patterns and standards have emerged for common functionalities. Understanding these will significantly aid in determining what is an appropriate type for smart contracts in various scenarios.

For instance, one of the most widely adopted smart contract types is the ERC-20 token standard on Ethereum. ERC-20 contracts are designed for fungible tokens, meaning each token is identical and interchangeable with another. They are the backbone of most cryptocurrencies built on Ethereum, utility tokens, and stablecoins. Developers leverage the defined functions (like transfer, approve, balanceOf) to create digital assets easily integrated into wallets, exchanges, and decentralized applications. This standardization has immensely contributed to the interoperability of the Ethereum ecosystem.

Conversely, for unique digital assets, the ERC-721 standard is the appropriate type for smart contracts. These Non-Fungible Tokens (NFTs) represent ownership of a singular, distinct item, such as digital art, collectibles, or even real-world assets tokenized on the blockchain. Each ERC-721 token has a unique identifier, making it non-interchangeable. The surge in popularity of digital art and gaming assets has propelled ERC-721 contracts into mainstream awareness, demonstrating their versatility beyond simple currency-like tokens.

Beyond tokens, complex smart contract systems form the core of Decentralized Finance (DeFi) protocols Question & Answer :

I’m wondering what is the best way to express smart contracts in typed languages such as Haskell or Idris (so you could, for example, compile it to run on the Ethereum network). My main concern is: what is the type that captures everything that a contract could do?

Naive solution: EthIO

A naive solution would be to define a contract as a member of an EthIO type. Such type would be like Haskell’s IO, but instead of enabling system calls, it would include blockchain calls, i.e., it would enable reading from and writing to the blockchain’s state, calling other contracts, getting block data and so on.

-- incrementer.contract main: EthIO main = do x <- SREAD 0x123456789ABCDEF SSTORE (x + 1) 0x123456789ABCDEF 

This is clearly sufficient to implement any contract, but:

  1. Would be too powerful.
  2. Would be very coupled to the Ethereum blockchain specifically.

Conservative solution: event sourcing pattern

Under that idea, a contract would be defined as fold over a list of actions:

type Contract action state = { act : UserID -> action -> state -> state, init : state } 

So, a program would look like:

incrementer.contract main : Contract main = { act _ _ state = state + 1, init = 0 } 

That is, you define an initial state, a type of action, and how that state changes when a user submits an action. That would allow one to define any arbitrary contract that doesn’t involve sending/receiving money. Most blockchains have some kind of currency and most useful contracts involve money somehow, so that type would be way too restrictive.

Less conservative solution: events + currency

We can make the type above aware of currencies by hardcoding a currency logic into the type above. We’d, thus, get something like:

type Contract action state = { act : UserID -> action -> state -> state, init : state, deposit : UserID -> Amount -> state -> state, withdrawal : UserID -> Amount -> state -> Maybe state } 

I.e., the contract developer would need to explicitly define how to deal with monetary deposits and withdrawals. That type would be enough to define any self-contained contract which can interact with the host blockchain’s currency. Sadly, such a contract wouldn’t be able to interact with other contracts. In practice, contracts often interact with each other. An Exchange, for example, needs to communicate with its exchanged Token contracts to query balances and so on.

Generalization: global state?

So, let’s take a step back and rewrite the conservative solution as this:

type Contract = { act : UserID -> Action -> Map ContractID State -> State, init : State } 

Under this definition, the act function would have access not only to the contract’s own state but the state of every other contract on the same blockchain. Since every contract can read each other’s state, one could easily implement a communication protocol on top of this, and, thus, such type is sufficient to implement arbitrarily interacting contracts. Also, if the blockchain’s currency was itself implemented as a contract (possibly using a wrapper), then that type would also be sufficient to deal with money, despite not having it hardcoded on the type. But that solution has 2 problems:

  1. Peeking at the other contract’s state looks like a very “hacky” way to enable communication;
  2. A contract defined this way wouldn’t be able to interact with existing contracts which aren’t aware of that solution.

What now?

Now I’m in the dark. I know I’m not in the right abstraction for this problem, but I’m not sure what it would be. It looks like the root of the problem is that I’m not able to capture the phenomenon of cross-contract communications properly. What concrete type would be more suitable to define arbitrary smart-contracts?

Before I answer the main question, I’m going to try to define a bit more precisely what it would mean to write code in Haskell or Idris and compile it to run on an Ethereum-like blockchain. Idris is probably a better fit for this, but I’m going to use Haskell because that’s what I’m familiar with.

Programming model

Broadly, I can envision two ways of using Haskell code to produce bytecode for a blockchain virtual machine:

  • A library that builds up EVM bytecode as Haskell data
  • A GHC backend that generates bytecode from Haskell code

With the library approach, constructors would be declared for each of the EVM bytecodes, and library code layered on top of that to create programmer-friendly constructs. This could probably be built up into monadic structures that would give a programming-like feel for defining these bytecodes. Then a function would be provided to compile this datatype into proper EVM bytecode, to be deployed to the blockchain proper.

The advantage of this approach is that no added infrastructure is needed - write Haskell code, compile it with stock GHC, and run it to produce bytecode.

The big drawback is, it is not easily possible to reuse existing Haskell code from libraries. All code would have to be written from scratch targeted against the EVM library.

That’s where a GHC backend becomes relevant. A compiler plugin (at present it would probably have to be a GHC fork, like GHCJS is) would compile Haskell into EVM bytecode. This would hide individual opcodes from the programmer, as they are indeed too powerful for direct use, relegating them instead to being emitted by the compiler based on code-level constructs. You could think of the EVM as being the impure, unsafe, stateful platform, analoguous to the CPU, which the language’s job is to abstract away. You would instead write against this using regular Haskell functional style, and within the restrictions of the backend and your custom-written runtime, existing Haskell libraries would compile and be usable.

There is also the possibility of hybrid approaches, some of which I will discuss at the end of this post.

For the remainder of this post, I will use the GHC backend approach, which I think is the most interesting and relevant. I’m sure the core ideas will carry over, perhaps with some modification, to the library approach.

Programming pattern

You will then need to decide how programs are to be written against the EVM. Of course, regular, pure code can be written and will compile and compute, but there is also a need to interact with the blockchain. The EVM is a stateful, imperative platform, so a monad would be an appropriate choice.

We’ll call this foundation monad Eth (although it doesn’t strictly have to be Ethereum-specific) and equip it with an appropriate set of primitives to utilize the full power of the underlying VM in a safe and functional style.

We’ll discuss what primitive operations will be needed in a moment, but for now, there are two ways to define this monad:

  • As a builtin primitive datatype with a set of operations

    -- Not really a declaration but a compiler builtin -- data Eth = ... 
    
  • Since much of the EVM resembles an ordinary computer, mainly its memory model, a sneakier way would be to just alias it to IO:

    type Eth = IO 
    

    With appropriate support from the compiler and runtime, this would allow existing IO-based functionality, such as IORefs, to run unmodified. Of course much IO functionality, such as filesystem interaction, would not be supported, and a custom base package would have to be supplied without those functions, to ensure code that uses them won’t compile.

Primitives

Some builtin values will need to be defined to support blockchain programming:

-- | Information about arbitrary accounts balance :: Address -> Eth Wei contract :: Address -> Eth (Maybe [Word8]) codeHash :: Address -> Eth Hash -- | Manipulate memory; subsumed by 'IORef' if the 'IO' monad is used newEthVar :: a -> Eth (EthVar a) readEthVar :: EthVar a -> Eth a writeEthVar :: EthVar -> a -> Eth () -- | Transfer Wei to a regular account transfer :: Address -> Wei -> Eth () selfDestruct :: Eth () gasAvailable :: Eth Gas 

Other basic functionality, including function calling, including deciding whether a call is a regular (internal) function call, a message call, or a delegate message call, will be handled by the compiler and runtime.

Type for smart contracts

We’re now up to answering the original question: What is the appropriate type for a smart contract?

type Contract = ??? 

A contract needs to:

  • Execute code on the EVM - return an action in the Eth monad
  • Call other contracts. We will define an Eth action to do this in a moment.
  • Take and return values, of type in and out
  • access information about its environment, including:
    • amount transferred in current transaction
    • current, sending, and originating accounts
    • information about the block

Therefore, an appropriate type may be:

newtype Contract in out = Contract (Wei -> Env -> in -> Eth out) 

The Wei parameter is informational only; the actual transfer occurs when the contract is called, and cannot be modified by the contract.

Regarding enviromental information, it is a bit of a judgement call to decide what should be passed as a parameter and what should be made available as primitive Eth actions.

Contracts can be called using a contract call primitive:

call :: Contract in out -> Wei -> in -> Eth out 

Of course this is a simplification; for example, it does not curry the input type. presumably, the compiler will generate unique actions for each visible contract, similar to Solidity. It may not even be appropriate to make this primitive available.

One additional detail: EVM supports constructors, EVM code that will be executed at time of contract creation, to allow enviromental information to be used. Thus, the type of a contract, as written by a programmer, would be:

main :: Eth (Contract in out) main = return . Contract $ \wei env a -> do ... 

Conclusion

I’ve omitted many details, such as error handling, logging/events, Solidity interop/FFI and deployment. Nontheless, I hope I have given a useful overview of programming models for functional languages against blockchain smart contract environment.

These ideas are not stricly Ethereum-specific; however, do be aware that Ethereum uses an account-based model, while both Bitcoin and Cardano use a Unspent Transaction Output (UTxO) model, so many details will differ. Bitcoin doesn’t really have a usable smart contract platform, while Cardano (whose smart contract functionality is in late tesing stages at time of writing) is programmed entirely in Plutus, which is a Haskell variant.

Rather than a strict library-based or backend approach to generating EVM bytecode, other more user-friendly approaches could be devised. Plutus, the Cardano blockchain language, uses a Template Haskell splice to embed on-chain Haskell code in ordinary Haskell, which is executed off-chain. This code is then processed by a GHC plugin.

Another intruiging idea would be to use Conal Eliot’s compiling to categories to extract and compile Haskell code for the blockchain. This also uses a compiler plugin, but the neccesary plugin already exists. All that is neccessary to define instances of relevant category-theorwtic typeclasses, and you get -Haskell-to-arbitrary-backend compilation for free.

Further reading

While writing this post, I referred heavily to the following references:

Other interesting resources: