A Mental Model For Smart Contracts
The concept of a ‘smart contract’ is not a new one. With the recent explosion of blockchain technologies, the term has entered common usage.
A smart contract is piece of code that runs as part of a blockchain. We can use a smart contract to define rules surrounding the creation, ownership and transfer of various forms of ‘value’ on the blockchain. This can be digital assets such as cryptocurrency, tokens, or even kitties.
The code defining a smart contract can be deployed to the blockchain many times. When a contract is deployed, it receives an address that uniquely identifies its code and state. Its fields and methods are public and accessible to anyone.
A smart contract might sound abstract or confusing, even to experienced developers. In this article I will attempt to present a way of thinking about smart contracts that brings them in line with more traditional programming concepts.
Code and Data
If you abstract away the underlying implementation details of the blockchain, a smart contract is a piece of immutable application code that lives forever¹.
Along with this immutable code is mutable state. A smart contract defines its state using variables. These variables represent various data structures such as mappings and primitive types.
Consider this Solidity code:
contract DepositHolder {
address owner; struct Deposit {
uint amount;
uint withdrawalBlock;
} mapping(address => Deposit) deposits; // Constructor
function DepositHolder(address newOwner) {
owner = newOwner;
} function makeDeposit(uint withdrawalBlock) public payable returns(bool success);
}
Several types define the DepositHolder
contract’s state:
- A primitive type
address
- A new data structure composed of other primitive types,
Deposit
- A mapping data structure
DepositHolder
also contains two methods:
- A constructor,
DepositHolder
, used to define the initial values of the state - The method
makeDeposit
, which defines ways in which the state can be modified
This combination of code and data can be thought of as an object. The pattern of creating an object that can only have a single instantiation is known as a singleton. The lifecycle of a smart contract can be viewed as that of a singleton object.
Context
In addition to the internal state defined by a contract, there is also contextual state available to the contract during its execution. In Solidity these are variables which exist in the global namespace to provide information about the blockchain. This contextual data can be divided into two different types:
- External environment context — Context about the underlying blockchain
- Method invocation context — Context surrounding the method invocation
External Environment Context
These fields are part of the external execution environment. Their reason to change comes from an external source that is out of control of the contract and its user. Some examples include:
Balance — We can imagine ‘balance’ as being a slice of the global balance state of all accounts and contracts², like a bank balance. It is not a property of the contract itself, because it can be modified outside of the contract, although it can also be modified by the contract itself³.
Block — Block contains information about the current block, eg. block height, difficulty, and coinbase. It can change between every method invocation and is not a property of the contract itself. ‘Block’ exposes implementation details of the underlying blockchain⁴.
Method Invocation Context
A method is invoked with parameters. Methods also have context surrounding their invocation. In Solidity, these values are available as global state variables as part of the message sent to the contract. Their reason to change comes from an external source generated by either a user or another contract.
Message contains information such as the sender of the current call, the value sent, and the call data. Rather than think about these fields as global state variables, we can model them as though additional parameters are being passed into the method. For example, a method that uses value
and sender
:
public Deposit(string name) {
owner = msg.sender;
balances[owner] = msg.value;
}
can instead be thought of as one that receives three parameters:
public Deposit(uint value, address sender, string name) {
...
}
Conclusion
These ideas are not new — the docs for Solidity themselves state “Contracts in Solidity are similar to classes in object-oriented languages.” Hopefully this article provides some more depth to the idea that that the mental model for a smart contracts maps quite well to a regular OOP object, with a few idiosyncrasies surrounding lifecycle, state, and context.
Footnotes
- Unless the contract has implemented the ability to suicide. In Solidity, this is done with a call to
selfdestruct()
. - In a UTXO model, there is no concept of an account. Instead we can consider a contract’s balance to be the value of the unspent outputs that are available for it to spend.
- With certain constraints. Balance cannot be incremented internally, but can be incremented externally by another contract or account, eg. as part of a method invocation. If it is decremented, another contract’s (or account’s) balance in the global state must be incremented.
- This could be considered a leaky abstraction, but because a smart contract is so dependent on implementation details of the underlying blockchain, we allow it.