Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
Introduction
With the rise of blockchain based crypto currencies, smart contracts have become increasingly more popular.
Crypto startups are utilizing smart contracts to create intelligent agreements that can be executed in a “trustless” environment. Smart contracts take out the middleman and execute on contract terms automatically, based on rules put in place. Although smart contracts harbor a lot of promise, we have seen a worrying history of hacked smart contracts, where hundreds of millions of dollars worth of crypto currencies have been lost or stolen.
My growing interest in crypto currencies and blockchain based technology has led me to research and investigate these hacks, to gain a better understanding of how they were hacked.
This article is part 1 of a 3 part series, where we will dive deep into the most infamous hacks. We will study how strokes of genius, or subtle mistakes caused earthquakes in the crypto world.
Aside: This article is based on the amazing lecture of Leonid Beder at Blockchain Academy — How to Not Destroy Millions. I highly recommend watching this talk, and all other materials on the Kin Ecosystem Foundation Youtube channel.
Smart Contracts
A smart contract is a set of promises, specified in digital form, including protocols within which the parties perform on these promises — Nick Szabo, 1996
Let’s break down this definition term by term.
Contract
An example of a common contract is a sales contract.
The seller promises to deliver the goods in exchange for the buyer.
The buyer promises to pay the desired price.
Digital form
Digital form means that the contract has to be programmed in machine readable code. We want the contract to be deterministic in nature, thus providing the same outcome based on the same input, every single time.
Protocols
For example, say the parties agree that the purchased good is to be paid in Bitcoin. The obvious protocol of choice would then be the Bitcoin protocol.
Smart
this just sounds cool.
Popular implementation of smart contract programming languages
Ethereum
Implements a nearly Turing-complete language on its blockchain, a prominent smart contract framework. We say nearly Turing-complete b/c in order to be completely Turing complete we need unlimited computational power.
Bitcoin
Implements a Turing-incomplete scripting language that allows the creation of limited smart contracts, such as:
- multisignature accounts
- payment channels
- escrows
- time locks
- atomic cross-chain trading
- oracles
Solidity by Example
Solidity is an OOP language for writing smart contracts (mostly?) on Ethereum. We’ll start with a simple example of a smart contract named Greeter, which will:
- Initialize with a greeting message (eg., “Hello World!”)
- Provide a method to read/query the greeting message.
- Provide a method to write/modify the greeting message.
- For these examples we will use the official online Remix IDE found @ https://remix.ethereum.org
It is best practice at the moment to specify explicitly which version of solidity we are using, because we don’t know which bugs can be introduced in future releases.
Solidity has a similar syntax to JS. We are defining a contract named Greeter. Additionally, we have a public state variable of type string called greeting and two methods (constructor and setGreeting).
Fundamentals: Execution Model
- Every smart contracts needs to be compiled to byte code before it can be deployed to the network. Deployment means being stored on the Ethereum network. This can be a production, test or private network.
- Every transaction or message call in Ethereum is executed by the Etherum Virtual Machine (EVM) on every Ethereum node. (miner or just a full node).
- Since every smart contract is running on the EVM, and every single operation is executed simultaneously by every node in the network — there should a mechanism to limit the resources used by each contract. The mechanism that limits resources in Ethereum is gas.
- Every operation executed has a deterministic (yet sometimes hard to predict) cost measured in units of gas. It’s hard to determine gas costs because conditional control flows can change which operations get executed in run time. Image the following program
if (condition is true) {
// do something super simple which is very cheap in gas terms
} else {
// do something really complicated which will be very expensive!
}
- Every gas unit consumed by a transaction must be paid for in Ether, based on a gas/Ether price which is set by the sender of the transaction. Each sender determines what he’s willing to pay. The idea is the more important or urgent it is to you, the more you’d be willing to spend.
- Transactions have also a gas limit parameter that is an upper bound on how much gas the transaction can consumer
- Why is the gas limit parameter needed? We have an upper bound b/c it isn’t always clear how much the execution of a transaction will cost, and we want to cap how much Ether we spend on it!
Let’s upload the contract to the Remix IDE, create and deploy it.
Remix can work against main net and test nets. It’s really convenient to work against an Ethereum simulation. When using the simulation we can pretend to have millions of Ether, and not wait on Ethereum transactions to be processed which make development a lot faster. Running smart contracts via Remix is completely free, so I recommend you give it a try at home :)
Once the contract is deployed we see it in the Deployed Contracts section in the box (see the arrow!). Notice that we can easily change the constructor argument and redeploy to the network.
Fundamentals: Special Variables and Functions
There are special variables and functions which always exist in the global namespace. Here are the ones we’ll discuss today
- msg.sender (Ethereum address): sender of the message (current call). Thsi belongs to the original caller of the transaction. This can either be a users account or the address of another smart contract. The Ethereum security model guarantees that msg.sender cannot be faked.
- msg.value (unit): number of wei sent with the message. wei is the smallest unit of Ether (think cents to dollars.) So this specifies how much ether we are sending with the function for buying, transacting, etc...
Fundamentals: Function and State Variables Visibility
In Solidity, there are four types of visibilities:
For functions:
- external: can be called externally from other accounts / contracts. It is important to note that we cannot call this code from our script. We would need to create an additional transaction which would invoke this externally. This is very good for methods that need to be publicly accessible but you want to avoid using them in your own code.
- public (default): can be called by everyone.
- internal: can only be called internally from our smart contract or a smart contract that inherits from our smart contract.
- private: can only be called internally and only from the contract itself.
For state variables:
- public: can be accessed by everyone. Solidity automatically generates a getter for a public variable.
- internal (default): can only be accessed internally and only from the contract itself.
- private: can only be accessed internally and only from the contract itself.
Fundamentals: Function Modifiers
Modifiers can be used to modify the behavior of functions. This lets us add functionality before, after or even around the invocation of the function. (Logging for example)
Modifiers are very similar to before/after/around/hooks in other programming languages.
For example, let’s augment our Greeter smart contract to:
- Have an owner (in our case, the deployer of the smart contract)
- Make sure that only the owner can further modify the greeting
We’re going to see this pattern a lot, in the coming examples as well as in the wild. Additionally, let’s add a condition that says only the owner can change the greeting.
It is important to note that require is a predicate in Solidity. If its evaluation returns false, then an exception is thrown, and all changes done in contract are completely rolled back.
The underscore is equivalent to a yield. This basically means execute in this place the remaining code of the function.
Fundamentals: Fallback Function
A smart contract can have exactly one unnamed function.
This function is invoked when a call to the contract is made and none of the other functions match the given function identifier. A common use for fallback functions is when Ether is being transferred to the contract. This triggers the invocation of the fallback function. So, in order to set up an account to be able to receive Ether the bare minimum (in most cases) is setting this function up.
Fundamentals: Payable Modifier
In order to receive Ether, every function must be marked as payable. This is to protect us from accidentally sending ether to a contract that doesn’t expect it.
- When sending Ether as part of a function call, the function must be marked as payable.
- When sending Ether directly to a contract, its fallback function must be marked as payable.
If no such function exists, the contract cannot receive Ether through regular transactions.
Events Example
This is a fancy way to do some logging in Ethereum.
Aside: We have a function called Donate that logs returns and event. This could be useful for the case of having an off-chain client that reads the loggings and manages all the donations.
We have a function called donate which is payable (we added the payable modifier). We also have a function called troll which is not payable, so sending Ether should fail in this case (payable modifier is missing).
Let’s deploy this contract to Remix so that we can verify the results of sending money via each of these methods.
In the gif above we can see that invoking the donate method results in a transaction where a value of 5 wei is transferred, and when we invoke the troll method no money is transferred.
We can see in Reward that we have a mapping between address and a uint. We use an owner concept here so our owner can call the setReward method, deciding that a specific address is supposed to get some amount of a magic token. Additionally, we have the claimReward method which is publicly accessible by everyone. This method gives you the reward that belongs to your address, and then emit an event for the reward.
Can anyone spot a problem with this?
Notice that when we declare the rewards mapping, it is a map from address to unsigned integer.
We are subtracting here without checking that the necessary funds are available! So any scenario where we reach a negative value in rewards will result in an underflow.
Aside: An underflow, will change what should be a negative value to an extremely large positive one. Check out the link, for a further explanation on how underflows work.
Consider the following example. A reward claim of 500 units is made. Now, our hacker will try and claim 1000. In theory, this shouldn’t work because we only have 500 left. After running this transaction the claimer will have an infinite amount of coins!
Bugs: Overflow / Underflow
- Solidity can handle up to 256 bit numbers
- Overflow is when a number gets incremented above its maximum value. So adding 1 to 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF results in 0.
- Underflow is the inverse case, when the number is unsigned, decrementing will underflow the number. This will work the other way around, where subtracting 1 from 0x00000000000000000000 results in 0xFFFFFFFFFFFFFFFFFFFFF.
Mitigation #1: Test for Correctness
- Test for correctness before performing any operation. A quick fix for this is to implement a check to verify that the claim is smaller than the current account balance.
Mitigation #2: SafeMath
- Always use the (de-facto) standard SafeMath library.
- Find nice, relatively stable, smart contracts in OpenZeppelin’s Github repo.
- What the library does is implement mathematical operations in a safe way, such that if an overflow occurs it rolls back the transaction. Assert is very similar to revert it rolls back bad transaction.
- In the example below we have imported SafeMath, injected it for use on unit256, and therefore the library will revert any transactions that cause an underflow on line 30.
Can you spot an error in the Charity contract? Let’s break it down. We have a payable fallback function and a withdrawal function where the owner can withdraw his funds. You can imagine that at some point the owner may want to withdraw his funds so he can do something with them.
Now, let’s take a look at the setOwner method. It’s using the owner paradigm. It has no privacy. In Solidity functions are public by default! Anyone can change the owner, thus transferring the funds to himself / herself.
Hack Scenario:
Alice deposits 1000 wei for this example. Bob tries to withdraw with but fails because he is not the owner of the charity. Now, Bob calls into setOwner, changing the owner to himself. Bob takes all the funds. All this because someone forgot to define visibility!
Mitigation #3: Always Define Visibility!
- Always define visibility
- Limit function visibility when possible
Conclusion
This has been a primer on the Solidity language, and we have examined some of the simple, dangerous mistakes that can be made during smart contract development.
The next post in this series will dive into infamous, genius and nuanced hacks, that have so far drained hundreds of millions of dollars worth of crypto in the past several years.
If you’re looking for resources to ramp up on the blockchain world, I highly recommend (again) checking out the Kin Ecosystem Youtube channel, as it has a wonderful inventory of high quality, technical discussions and lectures. Huge thanks to Leonid Beder for building this lecture and teaching it at Blockchain Academy!
If this post was helpful, please subscribe and click the clap 👏 button below to show your support! ⬇⬇
You can follow me on Instagram, Linkedin and Medium.
How to Not Destroy Millions in Smart Contracts (Pt. 1) was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.