Recap Challenge 1 - 5

Challenge 1 - Basic Token Contract

Status: live at 0xBe56…889f, but not robbed yet.

The contract allows people to buy tokens for 1 finney (0.001 ETH / $0.01) each:

function () {
    address sender = msg.sender;
    if(tokenBalanceOf[sender] != 0) {
        throw;
    }
    tokenBalanceOf[sender] = msg.value / tokenPrice; // rounded down
    notifySellToken(tokenBalanceOf[sender], sender);
}

Users can also get their tokens refunded:

function refund() noEther {
    address sender = msg.sender;
    uint256 tokenBalance = tokenBalanceOf[sender];
    if (tokenBalance <= 0) { throw; }
    tokenBalanceOf[sender] = 0;
    sender.send(tokenBalance * tokenPrice);
    notifyRefundToken(tokenBalance, sender);
}

Thus far, no one has been able to redeem more ether than they put in. You still have six weeks to try to rob the 9.5 ETH from this contract.

More information can be found in the original article.

In an effort to better understand cross-chain replay attacks, I drained this contract on the ETC chain while leaving it intact on the ETH chain.

Challenge 2 - Transfer Tokens

Status: live at 0x08d6…2abf, but empty after I robbed it myself.

I added the following function that allows users to transfer tokens to another user:

function transfer(address recipient, uint256 tokens) noEther {
    address sender = msg.sender;

    if (tokenBalanceOf[sender] < tokens) throw;
    if (tokenBalanceOf[recipient] + tokens < tokenBalanceOf[recipient]) throw; // Check for overflows
    tokenBalanceOf[sender] -= tokens;
    tokenBalanceOf[recipient] += tokens;
    notifyTranferToken(tokens, sender, recipient);
}

When I wrote this code, I expected that if there was any vulnerability, it would be in the new transfer function. But as explained in Challenge 3 below, the vulnerability was in a different function. However, if you believe there’s also a vulnerability in this transfer() function, I suggest you try to rob Challenge 3, which has the exact same transfer() function.

More information can be found in the original article.

Challenge 3 - Vulnerability Fixed

Status: live at 0x80f1…0e57 , but not robbed yet.

The following function in Challenge 2 turned out to be vulnerable:

function withdrawEtherOrThrow(uint256 amount) {
    bool result = msg.sender.call.value(amount)();
    if (!result) {
        throw;
    }
}

Functions are public by default. Since I mistakenly assumed the above function was private, I didn’t write any checks for it. That allowed anyone to drain the entire contract simply by calling withdrawEtherOrThrow().

Fortunately for me, I realized my mistake several days later, ran to my computer, and robbed it myself. Someone else also discovered my mistake, so if I had waited only half an hour longer, I would have been too late.

The fix was simple enough. All I had to do was add private to the function definition:

function withdrawEtherOrThrow(uint256 amount) private {
    bool result = msg.sender.call.value(amount)();
    if (!result) {
        throw;
    }
}

To date, this new contract hasn’t been robbed.

More information can be found in the original article.

Challenge 4 - Segregate User Funds

Status: live at 0x4B90…10eF, but not robbed yet.

When the DAO was hacked, the hacker — commonly referred to as The Attacker — managed to withdraw more than they put in. I want to make this less likely to occur by keeping user funds in segregated contracts. The hope is that if a hacker somehow completely drains their own contract, they can’t also drain other people’s funds.

The main DaoChallenge contract creates and keeps track of DaoAccount contracts, one for each user:

mapping (address => DaoAccount) private daoAccounts;

function createAccount () noEther returns (DaoAccount account) {
    address accountOwner = msg.sender;
    address challengeOwner = owner; // Don't use in a real DAO

    // One account per address:
    if(daoAccounts[accountOwner] != DaoAccount(0x00)) throw;

    daoAccounts[accountOwner] = new DaoAccount(accountOwner, challengeOwner);
    return daoAccounts[accountOwner];
}   

Each DaoAccount then takes care of buying tokens and refunds:

function () onlyOwner returns (uint256 newBalance){
    uint256 amount = msg.value;

    // No fractional tokens:
    if (amount % tokenPrice != 0) {
        throw;
    }

uint256 tokens = amount / tokenPrice;

    tokenBalance += tokens;

return tokenBalance;
}

function refund() noEther onlyOwner {
    if (tokenBalance == 0) throw;
    tokenBalance = 0;
    withdrawEtherOrThrow(tokenBalance * tokenPrice);
}

There’s no way to make refund() return more than what’s in this DaoAccount, and this protects other DaoAccount instances and DaoChallenge. Of course, there might be a vulnerability in DaoAccount that allows an attacker to drain every single instance, one by one.

So far, that hasn’t happened.

More information can be found in the original article.

Challenge 5 - Segregated Funds Usability

Status: live at 0xae06…5d67, but not robbed yet.

The contract in Challenge 4 was very tedious to use in practice. It required the user to find out which DaoAccount belonged to them and import that into their wallet before they could buy tokens from it.

I added wrapper functions to DaoChallenge; they automatically look up the user’s DaoAccount and call their corresponding function there:

function buyTokens () returns (uint256 tokens) {
  DaoAccount account = accountFor(msg.sender, true);
    tokens = account.buyTokens.value(msg.value)();

    notifyBuyToken(msg.sender, tokens, msg.value);
    return tokens;
 }

function withdraw(uint256 tokens) noEther {
    DaoAccount account = accountFor(msg.sender, false);
    if (account == DaoAccount(0x00)) throw;

    account.withdraw(tokens);
    notifyWithdraw(msg.sender, tokens);
}

This gives more power to the main DaoChallenge contract, which could potentially be exploited to drain all DaoAccount contracts. However, that hasn’t yet happened.

More information can be found in the original article.

What’s Next?

I want to make these contracts more useful. I’d like to bring back transferring tokens between users, which is a feature I removed in Challenge 4. I also want to introduce a proposal system similar to that of the DAO, where users can vote with their tokens on how the DaoChallenge spends its funds. I’ll try to introduce these features in incremental steps, offering a bounty each time for those who can rob any of the future contracts.

avatar

DAO Challenge

Writing secure smart contracts with skin in the game