Here’s what went wrong:

A vulnerability was found in the part of PoWH’s implementation of ERC-20 that allows a person to “approve” another user to transfer tokens on their behalf. A malicious user could enable a second account to sell coins from the first account. However, the sold coins from the first account would be taken off the second account’s balance. The resulting (unsigned) integer underflow would leave the second account with an extremely large balance of PoWH Coins.

The flawed transfer functions

function transferFrom(address _from, address _to, uint256 _value) public {

var _allowance = allowance[_from][msg.sender];

if (_allowance < _value)

revert();

allowance[_from][msg.sender] = _allowance - _value;

transferTokens(_from, _to, _value);

}

The first function doesn’t have any immediate issues; its first four lines just make sure the caller is authorized to spend someone else’s coins, then it calls transferTokens(address _from, address _to, uint256 _value) to do the heavy lifting.

function transferTokens(address _from, address _to, uint256 _value) internal {

if (balanceOfOld[_from] < _value)

revert();

if (_to == address(this)) {

sell(_value);

} else {

int256 payoutDiff = (int256) (earningsPerShare * _value);

balanceOfOld[_from] -= _value;

balanceOfOld[_to] += _value;

payouts[_from] -= payoutDiff;

payouts[_to] += payoutDiff;

}

Transfer(_from, _to, _value);

}

transferTokens has some unusual behavior, though. Specifically, if it’s called with a _to address belonging to the contract itself, it processes the transfer as a sale. Even so, this isn’t a problem yet.

function sell(uint256 amount) internal {

var numEthers = getEtherForTokens(amount);

// remove tokens

totalSupply -= amount;

balanceOfOld[msg.sender] -= amount;



// fix payouts and put the ethers in payout

var payoutDiff = (int256) (earningsPerShare * amount + (numEthers * PRECISION));

payouts[msg.sender] -= payoutDiff;

totalPayouts -= payoutDiff;

}

Here’s where things get hairy. At first glance you may not notice, but the _from argument is no longer being passed down the chain. Instead, the sell function assumes msg.sender to be the seller. Unfortunately, if sell is invoked by transferTokens it’s possible that msg.sender 's balance is now being drained of coins that it doesn’t own. While that would usually make people upset, if an empty second account used to make this transfer, and only one PoWHCoin is sent, the second account’s balance will underflow to 1.1579E77 or 2^256 — 1 .

Once in possession of an account with the maximum possible balance, it’s not too difficult to turn into Ethereum. (The actual attacker messed up once, though: they transferred their entire balance into the contract in an attempt to sell it. Unfortunately for them, that underflowed their balance right back around to maximum again.) On the second attempt, they turned only 100 of their 115 quattuorvigintillion PoWH into Ethereum, circumventing PoWH Coin’s all-or-nothing cashout system by transferring the smaller sum into the contract. Then, they issued one last request, to withdraw their 866 ETH.