a method, or by suspending via wait().
If we view smart contracts through
the lens of monitors and monitor invariants, then the re-entrancy vulnerability looks very familiar. An external
call is like a wait() suspension, because even though there is no explicit
lock, the call makes it possible for a
second program counter to execute that
contract’s code concurrently with the
first program counter. The DAO-like
contract shown here implicitly assumed
the invariant that each client’s entry in
the balance table reflects its actual balance. The error occurred when the invariant, which was temporarily violated
between Lines 3 and 5, was not restored
before giving up the (virtual) monitor
lock by making an external call.
Here is why the distributed computing perspective is valuable. When
explained in terms of monitors and
monitor invariants, the re-entrancy
vulnerability is a familiar, classic concurrency bug, but when expressed in
terms of smart contracts, it took respected, expert programmers by surprise, resulting in substantial disruption and embarrassment for the DAO
investors, and required rolling back
troublesome but technically legal
transactions and proceeding as if they
had never taken place.
6
Smart contracts as read-modify-write operations. The ERC20 token
standard28 is the basis for many recent
initial coin offerings (ICOs), a popular
way to raise capital for an undertaking without actually selling ownership.
The issuer of an ERC20 token controls
token creation. Tokens can be traded
or sold, much like Alice’s Restaurant’s
coupons discussed earlier. ERC20 is
a standard, like a Java interface, not a
particular implementation.
As illustrated in Figure 3, an ERC20
token contract keeps track of how many
tokens each account owns (the balances
mapping at Line 3), and also how many
tokens each account will allow to be
transferred to each other’s account (the
allowed mapping at Line 5). The approve() function (Lines 9–13) adjusts
the limit on how many tokens can be
transferred at one time to another account. It updates the allowed table (Line
10), and generates a blockchain event
to make these changes easier to track
(Line 11). The allowance() function
queries this allowance (Lines 14–16).
tion that allows an investor to withdraw funds. First, the function extracts the client’s address (Line 2),
then checks whether the client has
enough funds to cover the withdrawal
(Line 3). If so, the funds are sent to the
client through an external function
call (Line 4), and if the transfer is successful, the client’s balance is decremented (Line 5).
This code is fatally flawed. In June
2016, someone exploited this func-
tion to steal about $50 million in funds
from the DAO. As noted, the expres-
sion in Line 3 is a call to a function in
the client’s contract. Figure 2 shows
the client’s code. The client’s contract
immediately calls withdraw() again
(Line 4). This re-entrant call again tests
whether the client has enough funds
to cover the withdrawal (Line 3), and
because withdraw() decrements the
balance only after the nested call is
complete, the test erroneously passes,
and the funds are transferred a second
time, then a third, and so on, stopping
only when the call stack overflows.
This kind of re-entrancy attack may
at first glance seem like an exotic hazard introduced by a radically new style
of programming, but if we change our
perspective slightly, we can recognize
a pitfall familiar to any undergraduate
who has taken a concurrent programming course.
First, some background. A monitor
is a concurrent programming language
construct invented by Hoare15 and Brin-ch Hansen.
11 A monitor is an object with
a built-in mutex lock, which is acquired
automatically when a method is called
and released when the method returns.
(Such methods are called synchronized
methods in Java.) Monitors also provide a wait() call that allows a thread
to releases the monitor lock, suspend,
eventually awaken, and reacquire the
lock. For example, a thread attempting
to consume an item from an empty buffer could call wait() to suspend until
there was an item to consume.
The principal tool for reasoning
about the correctness of a monitor
implementation is the monitor invariant, an assertion that holds whenever
no thread is executing in the monitor.
The invariant can be violated while a
thread is holding the monitor lock, but
it must be restored when the thread release the lock, either by returning from
Figure 4. An incorrect atomic decrement
operation.
class Counter {
private int counter;
public void dec() {
int temp = counter
temp = temp – 1;
counter = temp;
}
…
}
Figure 3. ERC20 Token example.
contract ERC20Example {
// Balances for each account
mapping(address => uint256) balances;
// Owner of account approves the transfer of an amount to another account
mapping(address => mapping (address => uint256)) allowed;
// other fields omitted
...
// Allow spender to withdraw from your account, multiple times, up to the amount.
function approve(address spender, uint amount)public returns (bool success) {
allowed[ msg.sender][spender] = amount; // alter approval
Approval( msg.sender, spender, amount); // blockchain event
return true;
}
function allowance(address tokenOwner, address spender)public returns(uint
remaining){
return allowed[tokenOwner][spender];
}
function transferFrom(address from, address to, uint tokens)public(boolsuccess){
balances[from]= balances[from].sub(tokens);
allowed[from][ msg.sender]= allowed[from][ msg.sender].sub(tokens);
balances[to]= balances[to].add(tokens);
Transfer (from, to, tokens);
return true;
}
... // other functions omitted
}