Introduction to Solidity (and Blockchains in General) For Non-Programmers
Well, you've found this blog post, so the assumption would be made that you know what Solidity is roughly used for. But, just in case you got here by random, Solidity is a smart contract programming language intended to be used to compile bytecode that runs on the Ethereum Virtual Machine.
Well, that's a lot of words. What the heck is a smart contract? What's this Ethereum Virtual Machine (EVM)? To answer, we're gonna need a lot more words.
Here's a bit of a summary of how this all came about . . . (You may skip this if you already know some blockchain history and fundamentals.)
A Blockchain 101 of Sorts
A lot of geeks and nerds had been trying to make digital versions of paper money since the nineties. What's digital money—what were they trying to make? To understand, note that real life paper money has this property that you can hold all of your money in a wallet and when you make a purchase, you give someone some of the money from the wallet. This is fundamentally different from how those plastic card transactions work. Those work by giving a card number to the vendor, and trusting that the vendor takes the appropriate amount out of your account, with your bank as the intermediary that you also trust. It is equivalent to giving the vendor your wallet and hoping to get it back with all the contents you were supposed to. When using a card, the vendor could take out more than they said they would, or sell your card information—and another entirely different concern with this whole system is your bank could be hacked or be doing shady stuff. The dream of digital cash was that it would function like paper money—you give the money to the vendor, and they don't get access to your wallet or bank account.
There were a few systems proposed and implemented to try to enable digital (remote, at a distance) transactions analogous to paper money where the recipient doesn't have to be trusted as they don't get access to an owner's funds, but these systems shared a commonality in that they still required a third party to either manage balances or authenticate transactions. In short, the digital cash thus far still required a digital bank—a third party that had to be trusted.
In 2008, an anonymous dude with a fake name — possibly a neighbor's ;) — came along and proposed (and implemented in 2009) a version of digital cash that did not require a digital bank. This is Bitcoin.
Bitcoin in Brief
Bitcoin is actually a fairly simple system, conceptually. I'll refer you to the whitepaper. If you already know how all those internet pirates manage to thwart all the big movie companies, then Bitcoin is readily understandable to you. Bitcoin manages to avoid reliance on a trusted third party because it is peer-to-peer. The way that internet piracy works, where all the little pirates all keep files of music, movies, and television shows and broadcast them to eachother, is the same way that Bitcoin works. Only, instead of some HBO show that they are distributing, it is a list of 'cash' balances.
But what are the accounts and balances in Bitcoin? Like most of the digital cash systems from the nineties, Bitcoin uses cryptography.
Public-key cryptography within Bitcoin is how the accounts are managed. You may think of it somewhat like a username and password. The public key is your 'username', and the private key is your 'password'. Now, typically in public-key cryptography (which is just the academic term for this type of system), you pick the 'password' (the private, secret key) first as the most random as possible number, and this private key is used to create the public key using some mathematical "trapdoor" function. (We may later on explore trapdoor functions for illustrative purposes with a simpler cryptosystem, RSA, in a future post.) Basically, with a trapdoor function, the produced public key is derived from the private key, but no one but the private key holder knows what the private key is.
Unlike your username/password account combination, you can use your private key to make other things that other people who only know your public key can verify was produced by someone in possession of your private key (which, if it was really random and you practiced good SecOps, would only be you). Using your private key to make something that only the private key can produce, is called 'signing', and the resulting 'thing' is called a 'digital signature'. Digital signatures take an input—typically called a message—and act on the message in a way that uses the private key to produce an output that can be verified as produced by the private key that produced the public key. A user wishing to 'send' or transact some bitcoin signs a standardized message with the transaction information, and if the signature and message are valid, all the little digital money pirates all update their records with the new balances.
Bitcoin has some other elements in the design for validation of transactions, using timestamps and a scheme called Proof-of-Work to protect against the Bitcoin network from attacks (lies about transactions and balances). A bunch of transactions are verified together in a 'block' (or batch), and then any new transactions get put in a new block. The new block says which block came before it, and this process goes on indefinitely. Because transactions can be grouped together differently (make different blocks), Bitcoin, according to its whitepaper, is considered to be the longest chain of blocks. This "longest-chain-IS-Bitcoin" principle rests on the assumption that the majority of the Bitcoin network is honest (not lying about transactions). Bitcoins are created from thin air only when a block is validated and given to the validator (validators that validate with Proof-of-Work are called 'miners').
Whew, that's quite a bit to explain, huh? Well, that's largely because Bitcoin is interdisciplinary. It draws from crytography, computer science, economics, and madness. There's plenty of other resources that go in depth and I advise you to seek them out. No particular recommendations from me here so you're forced to do your own research. ;) Onto the next thing!
Introducing Ethereum
Essentially, after Bitcoin's arrival, lots of other nerds started trying to make other things with Bitcoin. That is, just not using it for solely digital cash transactions, but for also representing ownership of real-world and other digital assets. After a few of these type of projects trying solve specific problems with Bitcoin popped up, some people noted the limitations of Bitcoin, as it was not designed for these things, and came up with a more general solution that could be used for virtually any purpose. This is Ethereum.
Ethereum shares many similarities with Bitcoin. Ethereum, too, has a whitepaper (eveything in this space does! Whole lotta reading!). Omitted from the above Bitcoin section for brevity are 'addresses'. Both Bitcoin and Ethereum share public-key cryptography as a core aspect, and both use the public keys to create another thing that is called an 'address'. Addresses are derived from the public key and are what are actually used to keep balances of bitcoin or ether. Transactions are from one address to another, but are only considered valid as long as they are digitally signed by the holder of the private key that is the pair to the public key of which the spending address is derived. That's a hard sentence to follow, I know. Here's a rephrasal: For some digital token at an address derived from some public key, it can only be 'spent' (transferred) to another address if the person who is trying spend the digital token proves that they know the private key that produced the public key.
While both are peer-to-peer networks using public-key cryptography to manage account-balances, Ethereum differs in that its transactions can also invoke code. Let me phrase that another way: Ethereum is programmable. Another way: The Ethereum network is essentially a world computer; this is the Ethereum Virtual Machine. Code can be put on the Ethereum network and triggered by accounts. Rules for whatever system is sought is put into code by programmers and then deployed to the network, where it is executed on the Ethereum Virtual Machine (EVM).
To accomplish this goal of a programmable, peer-to-peer platform, some other elements are part of the Ethereum's design, such as 'gas', a mechanism that prevents the possibility of code getting stuck running forever. Gas is an analogue to gas in the real world. You put gas in your car, and your car can drive until it runs out. In the real word, gas is used for the combustions in your car's engine, but in the Ethereum world, gas is used for each computational step in your code. Gas is supplied before the execution of some code. (It should be noted that there's a limit to the amount of gas that can be used in a transaction.) What happens when an interaction with a contract runs out of gas is that any changes that might have been made are reverted.
If all this sounds crazy, that's because it is. :)
Smart Contracts
So, what the hell is a smart contract? Well, we're in the cryprocurrency world. These cryptosystems deal with digital token analogues to cash, and Ethereum provides a platform for programming rules for interactions with these digital representations. Other representations can be coded up, arbitrary data can be stored in and retrieved from the blockchain, and it's all initiated by accounts and requires some balance of 'Ether' to pay the gas for the transaction. Since the code on the Ethereum network deals with smart, programmed rules that are only executed by consenting parties (and is typically financial) and enforced by the EVM which runs the code, and since, in the legacy world, transactions made in accordance to written-out rules agreed to by two consenting parties enforced by law are called contracts, it made sense to call these things on the Ethereum network smart contracts. That, and Nick Szabo (whom I suspect ol' 9000 tried to trick the world into thinking was the most plausible candidate for SN) coined the term in '96.
That's the end of the history and fundamental concepts of all this stuff. Now we'll get ready to start thinking in terms of Contract-Oriented Programming!
Solidity
Solidity code is not the code that runs on the EVM.
Life without Solidity
The code for an arbitrary contract that runs on the EVM is in the form of bytecode, which is most commonly seen in hexadecimal representation.
Ah, that word, 'hexadecimal'. What does it mean? Well, what's 'decimal'? That's the numbering system you're probably most familiar with, where we use 10 characters (0–9) to represent values. When representing values larger than nine, we just use multiple characters from the same set. An example of this is '13', which if we were to represent in countable dots, would be •••••••••••••
. In hexadecimal, we have more than 10 characters to represent values with. We have 0
, 1
, 2
, 3
, 4
, 5
, 6
, 7
, 8
, and 9
— all characters you are familiar with — but, we extend those characters with A
, B
, C
, D
, E
, and F
, making a total of 16 characters. When writing numbers in hexadecimal, it is convention to preface with 0x
(it'd be too damn confusing otherwise). So, thirteen in hexadecimal can be written as 0xD
or 0xd
(casing does not matter in hexadecimal). (There is another representational system called binary that we'll explore in a section or post on the topic of Boolean logic.)
Now, a 'byte' is any two hexadecimal characters. A 'nibble' is half a byte, or just one hexadecimal character. Bytecode is just a bunch of bytes, and it typically looks like this:
0x42B7E6190000000000000000000000000000000000000000000000000000000000000539000000000000000000000000000000000000000000000000000000000000E298
(This is just a contrived example of what bytecode looks like in general—bytecode for full, sophisticated contracts is much longer; this example doubles to subtly set the stage for a explanation/tutorial much later on for crafting raw transactions where it will be explained that the function signature is the first four bytes, and any arguments to functions are padded to 32 bytes in total. You can ignore this comment, I'm not sure why I'm including it as it just makes this more difficult to follow . . . ¯\_(ツ)_/¯ )
The bytecode that runs on the EVM corresponds to the EVM opcodes. Scroll down and take a look at Appendix H of the Ethereum yellow paper. You see how incomprehensible that is? Yeah, it's almost just as incomprehensible to programmers too, which is why it is very rare to write bytecode directly. Ethereum developers use higher-level programming languages that compile into bytecode that conforms to those specifications.
NOTE: Solidity is the most popular language for these days, but there are others: Viper, Serpent, Mutan, and LLL.
Life With Solidity
With Solidity, it is possible to write code for smart contracts that looks like this:
contract ExampleContract {
string public owner;
function ExampleContract() {
owner = "Rick Sanchez";
}
}
One immediate thing to observe is that this is more readable than however it would appear in bytecode. This code is simple: it is a definition of a contract named 'ExampleContract', and when this contract is deployed to the Ethereum network, it sets its 'owner' property to 'Rick Sanchez'. Anyone could then, as long as they have the interface to this contract, ask it who its 'owner' is, and find out that it is 'Rick Sanchez'.
Let's add to this contract definition a little bit, since Solidity is a programming language. For demonstration purposes, let's say we want to make this contract work in a way that the current owner of the contract can change who the owner is. Let's first extend the contract so that we can give the contract a name and it tells us if the name is the name of it's owner (that way we don't have to check ourselves).
contract ExampleContract {
string public owner;
function ExampleContract() {
owner = "Rick Sanchez";
}
function isOwner(string _name) public constant returns (bool) {
bool isOwner = (owner == _name);
return isOwner;
}
}
NOTE: There're much more concise ways to write this in Solidity, and this isn't yet a good example of a way to actually maintain ownership of a contract. This is to first show the language in digestible pieces. We are showing the features of the language before articulating them.
So, what we have done is create a function named isOwner()
that takes in a string of characters, performs an equality check between the passed in name and the contract's owner, and returns the result. Pretty simple. Terminology: if we want to use a function that a contract provides, we 'call' or 'invoke' or 'execute' it (all synonyms, though 'call' has a special meaning in Ethereum that we'll clarify later).
Now, how might we change the owner of the contract?
contract ExampleContract {
string public owner;
function ExampleContract() {
owner = "Rick Sanchez";
}
function isOwner(string _name) public constant returns (bool) {
bool isOwner = (owner == _name);
return isOwner;
}
function changeOwner(string _newOwner) public {
owner = _newOwner;
}
}
Well, now we've added a new function. This latest addition is aptly named changeOwner()
. When called, the contract takes in a new string and changes its 'owner' property to the new string's value.
But, now we may have introduced unintentional behavior! It is also here that I propose the term that should be used for these unintended behaviors and exploitable behaviors: loopholes. We have a loophole in our contract. (In normal programming, it is conventional to call these 'bugs', even though bugs should really only refer to those strange circumstances where your code isn't working when it should and it turns out there was a literal bug crawling around in your computer.)
The loophole in this contract is that anyone can call this function on our deployed contract and change its owner. We'll want some way to check if the person that wants to change it is the current owner, and let them change it when they are. If not, we'll simply just not do anything.
But before we try coding it up, let's note that we still have to determine a way to ensure that whoever is calling the contract is the current owner. Right now, all we have is a string of the owner's name—and that's it. Alright, let's continue with this bad ownership contract.
contract ExampleContract {
string public owner;
function ExampleContract() {
owner = "Rick Sanchez";
}
function isOwner(string _name) public constant returns (bool) {
bool isOwner = (owner == _name);
return isOwner;
}
function changeOwner(string _yourName, string _newOwner) public {
if (isOwner(_yourName)) {
owner = _newOwner;
}
}
}
Now, we've modified the contract's changeOwner()
method ('method' is fancy term for 'function' I'll slip into on occasion). The new method takes in two strings, the first is intended to be the name of the person requesting the change in ownership, and the second being the new owner. The new function even uses another function of the contract! This is almost what we want for the final solution, except the loophole here is anyone could ask the contract what the current owner's name is first, then call the function and provide the current owner's name and a new owner, and the contract's owner would be changed.
We just aren't thinking with cryptography in mind yet! Remember, this is a cryptosystem! To review some of the stuff in the earlier Blockchain 101 sub-sections, recall that transactions are messages signed by someone in possession of the private key that is the pair to the public key that is known. And recall that the token, or ether, is transferred from one address to another. Now I'll tell you something maybe new to you: all the transaction information for the current interaction with a contract is actually available to us in Solidity as global variables.
Variables, Types, and Scopes
What's a global variable? First, let's just cover variables.
If you've ever had an algebra math class, then you actually know the term 'variable'. In programming, we use that term, and it has the same meaning. You might notice, remembering back to your algebra days, that the term function was also used. It has roughly the same meaning in programming, and this is more apparent if we look at a mathematical example. (Remember that 'integers' are negative and positive whole numbers.)
NOTE: If you've never had an algebra course, you're likely in over your head here, and should go watch some KhanAcademy videos and come back. In a pinch, I guess it'd suffice to say a 'variable is something that can vary in value'.
Here's a function that adds 2
to whatever value it is given:
function f(int x) public returns (int) {
return (x + 2);
}
As a mathematical function, it would be written as f(x) = x + 2
. The above code is written with Solidity's syntax, but note that it would have to be inside a contract definition to be runable. Let's note some of the differences between the Solidity code and the formula. The code has more information than the formula. The formula only deals with one type, 'numbers' (and it never specifies the type, because, as a math formula, we can assume that 'x' is a number). Solidity has multiple types, with two you've seen now being strings and integers. Solidity is a compiled language; it compiles into bytecode. With compiled languages that support multiple types, it is common to have to specify the type for each variable and returned value so that the compiler (in our case, the program that converts Solidity-code into bytecode) knows how other variables and operators (like the +
symbol) are supposed to interact with eachother.
Now that we have 'variable' pinned down, what's the 'global' part in 'global variable' mean? Well, in programming, there is often the concept of 'scope'. Typically, we have a local scope and a global scope. The local scope is where variables inside blocks of code (such as inside a function—blocks of code are denoted with curly braces, with the opening '{
' being the start of a block and the closing '}
' being the end) have values and meanings that other variables in the scope can access, but these values are not accessible from the global scope. The variables in the global scope are visible inside the local scope.
Here's a concrete example, as I'm afraid that it's too abstract to follow otherwise. NOTE: These examples are syntactically similar to Solidity for simplicity, but not actually 'Solidity'.
int x = 12;
function example(int y) public returns (int) {
return x + y;
}
function secondExample(int z) public returns (int) {
// This function produces an error
return y + z;
}
So, in the first example()
function, the local variable is y
and the global variable is x
. This function simply adds whatever number y
is to x
, which happens to be 12
. The code for this function is free of errors.
The secondExample()
has an error, and that is that it as attempting to use a y
variable, possibly because the programer of it was thinking of the first function. This code would not compile. No variable y
has been defined in the secondExample()
function. If a y
variable were defined in the function, then everything would be a'ight:
function thirdExample(int z) public returns (int) {
int y = 19;
return y + z;
}
Note that we can use the same variable name inside different functions. We can even use the same name as a global variable inside our local scope, but note that we would no longer be able to access the global variable. This is called 'shadowing'.
int q = 23;
function shadowExample(int w) public returns (int) {
var q = 27;
return w + q;
}
Now 'var' is a special keyword that will be explained later, but it is used to tell the compiler, "Ayy, I'm fine with whatever type you're gonna make this variable." In some cases, the compiler can infer the type of a variable.
In the above shadowExample()
function, the returned, resulting value is whatever value is given as the parameter plus 27
. The global q
variable is shadowed, but this does not change the value of the global q
variable for other functions that might be using it. Shadowing is generally best avoided.
Global Variables
There's a list of global variables for you to leaf through in the official Solidity documentation, but the one that you absolutely must know is msg.sender
. This global msg
variable is accessible at any point in Solidity contract definitions (unless you shadow it) and its sender
property is almost always the address of whoever directly initiated the current, most recent interaction with the contract. These global variables are not Solidity-specific, as they are specified in that nasty yellow paper.
With msg.sender
, we can fix our 'ExampleContract' to keep track of its owner in a better way. We no longer will be using a string of the owner's name for keeping track of ownership. Remember, every interaction is caused by a transaction, and every transaction is between two addresses and initiated by one party in possession of a secret key, and that no one else can "pretend" to know the private key, and that Solidity can access the address of who initiated the transaction.
With all that in mind (yes, it is a lot—it is recommended to read it over and over again, starting from the beginning as necessary, until it is understood and internalized), here is an improved 'ExampleContract':
contract ExampleContract {
address public owner;
function ExampleContract() {
owner = msg.sender;
}
function isOwner(address _account) public constant returns (bool) {
bool isOwner = (owner == _account);
return isOwner;
}
function changeOwner(address _newOwner) public {
if (isOwner(msg.sender)) {
owner = _newOwner;
}
}
}
The immediate difference that should be noticeable is the replacement of the string type with the address type. All the functions of this contract have been modified in some form or another.
When a Solidity contract is deployed to the Ethereum network, any function (but there can only be one) bearing the same name as the contract is automatically executed. This is called the 'constructor'. In the constructor for our new 'ContractExample', we are setting the contract's 'owner' property to be the address of whoever created the contract. We could alternatively hard-code an address:
function ExampleContract() {
owner = 0xE16bD51697Ec9987952696D0D6Ef3761fE08DADd;
}
But, we can do this better:
function ExampleContract(address _firstOwner) {
owner = _firstOwner;
}
Constructors can be given parameters like any function, so we can make contracts that have initial states that can vary without having to write different contracts with each initial values that we want the different instances to have. This was also an excuse to put in an address I own in case anyone wants to leave me a fat tip ;).
Another difference between this version of the 'ExampleContract' and the one prior to it is the that changeOwner()
is now back to only one parameter, as we don't have to trust that people are telling us their real names now that we're doing ownership based on addresses and because transactions can only be initiated by someone in possession of the private key that is the pair to the public key corresponding to the sending address.
Conclusion
In this post, we cursorily covered the history of blockchains, provided a summary of smart contracts, and saw what Solidity looks like, why it's useful, and even started to understand some of the features of the language.
I suppose I've written way too many words, but hopefully some of them have clarified some things or introduced you to new things. There's plenty of resources out there documenting many of these things better than I can do, but if you have found some good resources that you'd like to share, please leave them below for others to see.
Tune in sometime soon for more Ethereum and Solidity bloggin'. I think the next post will be about contract interfaces, and it'll include an idea to make things a lil' easier.