What’s a smart contract?¶
So far, we’ve introduced how Bitcoin works, by using an analogy of a greenhouse and post office to introduce distributed systems and cryptography respectively.
We’ve learned that Bitcoin can be spent using UTXOs. But is that all Bitcoin can do? No!
Enter smart contracts. A smart contract is a piece of code that runs on a blockchain with persistence. Virtually every blockchain features a scripting language which is used to accommodate some degree of programming, and with Bitcoin, the scripting language is very limiting owing to Bitcoin’s philosophical grounding, so technically Bitcoin cannot natively accommodate smart contracts.
However, Bitcoin can be used to build several other systems on top of it which are far more complex which can. These approaches leverage UTXOs in the context of a conditional transaction (called a connector output), but you’re still going to need to off-ramp to a another platform in the form of a bridge similar to the way you might with a layer 2 on a different chain.
Scripting languages¶
When we were discussing Bitcoin before, we didn’t explain the minutae of how a transaction works in terms of the actual consumption of UTXOs. A transaction is actually a mixture of a the public key of the sender, the v and s values derived from the public-private key signing process, the UTXO inputs and outputs, and instructions for a scripting language. The scripting language has the power to manipulate a UTXO by referring to it. This scripting language is way more limited in nature than the languages we’re familiar with today.
This scripting language contains these operations:
Operation |
Description |
---|---|
OP_IF |
Execute statements if the top stack value is true. |
OP_NOTIF |
Execute statements if the top stack is false. |
OP_VERIFY |
Check that the top value in the stack is true, and if so, conclude the transaction as being valid. |
OP_CHECKLOCKTIMEVERIFY |
Concludes the transaction as being invalid if the top stack item exceeds the transaction’s nLockTime field. |
My selection is such a short list that I feel compelled to include a link to everything here: https://en.bitcoin.it/wiki/Script . Bitcoin actually has in total 200 operations.
You might notice that Bitcoin actually has branching in the scripting language form this selection, as well as the availability of another stack for more complex operations. It also has work involving clock time verification for the aforementioned hashlocking features. Pretty neat!
Unfortunately for our Bitcoin discussion, this is where the trail goes cold: we can’t use Rust to program Bitcoin native smart contracts, and the supported operations aren’t expressive enough that even if we could, it wouldn’t be as interesting. That’s not to pour cold water on Bitcoin smart contracts: sidechains like Rootstock and scaling technologies like Lightning are super expressive and we can do lots more there.
Stepping away from Bitcoin, let’s introduce another blockchain. This blockchain is similar to Bitcoin in how we can use it, but it has several distinctive characteristics that make it different. This blockchain is known as Ethereum.
Ethereum is a blockchain built on a concept called Proof of Stake, where instead of multiple computers working hard to solve puzzles to validate their inclusiveness in the history, instead miners lock up money that their proposed combination of blocks is correct. When a disagreement happens, the nodes that continue the history “slash” the money that the bad actor had in Ethereum. Slashing is the process of wiping clean. This is the process of staking and slashing, an economic security model that’s employed here for security.
Ethereum also features another process for block generation that’s more similar to the original greenhouse example, with a leader election as opposed to a constantly ongoing battle for the right to make the next block. Ethereum is an extremely expressive environment for building smart contracts. It has a turing complete programming language with a unique concept of storage, the ability for smart contracts to interact with each other, and a way for nodes to report the going ons about smart contracts to their users esaily.
With Ethereum, we can relatively easily build smart contracts. Some of these smart contracts allow people to lend money, buy and sell assets, arbitrate disputes with a court, communicate via social media like X, and much more. Bitcoin may be lacking in the smart contract department in this regard in that the work would need to be done off-chain (not on Bitcoin), but it’s by no means less functional. Bitcoin comparatively is intended to be a thin layer for other people to build expressive platforms on top of, and Ethereum strives to be more inclusive in the base technology of new verticals.
Virtual memory types¶
Ethereum’s scripting language comparatively is like the following. The selection is to illustrate the next example, and to show off the different approaches for state management:
Operation |
Description |
---|---|
SSTORE |
Stores a Ethereum machine word at a location given. |
SLOAD |
Loads a Ethereum machine word from a location. |
MSTORE |
Writes something to the scratch space. |
MLOAD |
Loads a word from the scratch space to the stack. |
JUMP |
Move the instruction pointer to a different code location. |
AND |
And boolean gate. |
OR |
Or Boolean gate. |
JUMPI |
Move the instruction pointer to a different code location, if the top of the stack is 1. |
EQ |
Set the top word on the stack to 1 if the two words are equal. |
GT |
Set the top word on the stack to 1 if the next word is greater than the previous. |
LT |
Set the top word on the stack to 1 if the next word is less than the previous. |
PUSH0 |
Push the number 0 to the stack. |
DUP1 |
Duplicate the top word of the stack. |
PUSH1 |
Pushes a 1 byte value to the stack. |
RETURNDATA |
Write to the read-only memory that a caller of this contract will have access to. |
RETURN |
Return from the calling context without error. |
REVERT |
Return from the calling context with an error, unwinding everything that took place here. |
Like Bitcoin, they’re both stack machines. Bitcoin’s differentiator in the realm of smart programmability could be surmised as a lack of persistence from the perspective of smart contracts: a series of operations with the scripting language in Bitcoin is short lived, and merely a more complex transaction. With Ethereum, you can write code then store it somewhere on-chain, and people can send it messages to interact with it. It has permanence so it can have its own concept of storage, and a more complex suite of operations.
Memory is transitive storage that exists for the contract’s life as its invoked in a transaction in the current execution frame. It’s similar to heap storage except you don’t ask for pages, you instead write/read to/from an offset.
Calldata is read-only memory that’s accessible by an offset and a length. When Bitcoin lets you leave a note to someone for something in a transaction, Ethereum uses that note as information for a smart contract that you may be invoking. It’s either data provided by the user when they invoked their transaction, or data sent by a contract to another contract during a transaction.
Returndata is a write-only storage type for setting what the calling
Why aren’t we spending memory access the same way that we do with UTXOs? Well, you’d be astute to recognise that from the previous chapter. Ethereum keeps things simple in this regard, and though that’s definitely possible (again, talking about Solana a bit later should be exciting), Ethereum also benefits from some other advantages to this approach. These approaches can include easy proof creation of storage. Actually, Ethereum does not use UTXOs at all, it literally has balances per account that it manipulates.
flowchart TD Contract((Contract)) -->|Scratch space for the smart contract's call during a transaction.| Memory((Memory)) Contract -->|Immutable access to data contained in a transaction of how the contract was invoked.| Calldata((Calldata)) Contract -->|Mutable key value addressed memory that lasts past the contract's invocation lifetime.| Storage Contract -->|Write-only data for storing what another contract should return when this contract is done working.| Returndata
This is an Ethereum smart contract that simply reads from a storage location, and if the storage is greater than 100 stops running with an error. Otherwise, it increases the number by 1, then saves it to storage:
PUSH0 // Push 0 on the stack.
SLOAD // Loads a word from the 0 key.
PUSH1 1 // Pushes the number 1 to the stack.
... TODO
I don’t expect you to grasp this at once, but I do want to imprint on your mind what this looks like. Ethereum’s storage system works according to a key-value trie, keys are used to find values in a map. So when we store things to memory, we store them in Ethereum-native words (these are 32 byte values) to keys addressable by Ethereum words.
Thankfully for normal people, Ethereum smart contracts are not implemented in the raw Ethereum Virtual Machine (EVM) code. This is the above implemented in Vyper, a Pythonic programming language:
# @version ^0.4.0
counter: uint256
@external
def fallback():
self.counter += 1
if self.counter > 100:
raise "uhoh!"
This is a bit different to the above, in that the code which has the functionality we desired must be invoked by someone calling our contract.
So what is someone “Calling” a contract anyway?
Calling is the process of sending a contract a note and having it return something to us, and giving them the power to refuse to continue and to unwind anything that took place once it was asked to do something. Contracts can call contracts by using a special operation that writes to calldata for another contract to read called CALL.
sequenceDiagram Transaction --> Contract: Sends a contract a message in the form of calldata. Contract --> Contract: Optionally calls another contract the same way a user would.
There are three operations related to calling:
Operation |
Description |
---|---|
CALL |
Write data to the calldata for another contract to read, stores our memory and storage for later reading, then start to run the other contract’s code before returning to our current contract. |
STATICCALL |
Do the same as CALL, but revert (break) with an error if the other contract writes to its storage. |
DELEGATECALL |
Writes data to the calldata for another contract, runs code from the other contract. |
Types of return behaviour from smart contracts¶
With Ethereum smart contracts, a contract can simply revert like the aforementioned. A revert prevents the program from continuing, and it also unwinds everything that was done after it was asked to begin for the first time. With the Bitcoin scripting language, a transaction can explicitly be concluded. But with the Ethereum Virtual Machine, you can only conclude the current context.
What do I mean by context? I mean what happens when you call into a smart contract, and it does its thing, before it finally concludes.