Intro

An oracle is a dapp that can provide information to other dapps, code external to the blockchain(s), and people.

With a hard-fork imminent for Ethereum, there needs to be a way to reliably determine which tine (chain) one is on.

There are not many solutions I’ve seen so far - actually, just this one by Timon Rapp. Although, to be honest, I wasn’t looking that much, and this seemed a good exercise anyway.

How this oracle should work

It has to answer one question:

Is this the “classic” or the “hard-fork” chain?

That is reasonably simple. However, complications:

  1. it can’t answer “both”, at any time;
  2. it shouldn’t have a default answer;
  3. it could answer “none”, but only before the fork;
  4. the code must be identical, no matter the answer;
  5. the address must be the identical;
  6. it would be very nice if the answer was available ASAP, preferably same block as the fork, please.

Critique

Barring the discovery of some yet-unknown vunerability in TheDAO that would allow draining the DarkDAO off-shoot pre-fork, Timon’s solution kind-of-fits the requirements above. 1, 2 and 3 are guaranteed and can be verified pre-fork.

Caveats:

  • it uses the constructor to determine the answer, and therefore must be deployed after the fork. Its address is not known pre-fork, so code relying on the answer can’t be deployed before that.
  • It must be simultaneously deployed on both chains, and that requires “transaction hygiene” to ensure that the oracle address is the same on both chains.
  • On the up-side, it can be deployed by anyone. On the down-side, that could mean repetition of effort.

Pretty solid. Since we already have that, I wanted to try something else.

Code

It’s available on GitHub, and is already deployed on the pre-fork blockchain (see etherscan.io and live.ether.camp).

TheDAOHardForkOracle.sol:

contract TheDAOHardForkOracle {
    address constant WithdrawDAO = 0xbf4ed7b27f1d666546e30d74d50d173d20bca754;
    address constant DarkDAO = 0x304a554a310c7e546dfe434669c62820b7d83490;

    // public, so accessors available
    bool public ran;
    bool public forked;
    bool public notforked;
    
    modifier after_dao_hf_block {
        if (block.number < 1920000) throw;
        _
    }
    
    modifier run_once {
        if (ran) throw;
        _
    }

    modifier has_millions(address _addr, uint _millions) {
        if (_addr.balance >= (_millions * 1000000 ether)) _
    }

    // 10M ether is ~ 2M less than would be available for a short
    // while in WithdrawDAO after the HF, but probably more than
    // anyone is willing to drop into WithdrawDAO in Classic
    function check_withdrawdao() internal
        has_millions(WithdrawDAO, 10) {
        forked = true;
    }

    // failsafe: if the above assumption is incorrect, HF tine
    // won't have balance in DarkDAO anyway, and Classic has a
    // sliver of time before DarkDAO split happens
    function check_darkdao() internal
        has_millions(DarkDAO, 3) {
        notforked = true;
    }

    // running is possible only once
    // after that the dapp can only throw
    function ()
        after_dao_hf_block run_once {
        ran = true;

        check_withdrawdao();
        check_darkdao();

        // if both flags are same, then something went wrong
        if (forked == notforked) throw;
    }
}

Does it fit the requirements?

Well, not exactly. Putting a fork oracle onto the chain pre-fork means another possible answer is introduced - “pre-fork”. The answer is no longer binary. One must work around that either by returning a numeric/symbolic answer, or by answering two questions.

This dapp does the latter by having flags ran and forked (notforked is redundant, but can be conveniently used, too). They are public, meaning the Solidity compiler automatically produces accessors (value return functions) for them.

By default, bool variables are false. The fallback function is prohibited to run prior the fork (tested that). Prior to the fork, ran() is guaranteed to return false, but so are the other two - fitting complication 2 above.

1 and 3 are moot, and using forked() without ran() means trouble pre-fork and some time after, until it is general knowledge that the dapp works as intended.

4 and 5 are guaranteed and can be verified pre-fork.

6 has the same gothcha as Timon’s solution. Two clients have to be automated to send a transaction on respective tines of the fork.

Why two conditions?

This hard fork will make certain alterations to the state of the EVM. Namely, moving ether from some accounts to another.

(More on this in my post on Reddit some time ago - it’s dated and doesn’t reflect the current spec exactly, but is non-technical enough).

One condition checks if the refund dapp has indeed been refunded. The other checks if the known split dapp still has its share. A successful hard-fork, I would argue, involves the former being met, but not the latter; and a successful continuation of the pre-fork state - the other way around.

That, and I was trying to work around complication 2 above. Different than my previous attempt at writing Solidity, it’s all on GitHub.

It does introduce an attack vector, though: anyone can drop 10 million ether into WithdrawDAO’s account, pre-fork (yeah, right) or post-fork on the “classic” chain. Then this dapp couldn’t reliably determine which chain it’s on (on one of the fork’s tines), rendering it useless (on both tines).

As with Timon’s dapp, “some yet-unknown vunerability in TheDAO that would allow draining the DarkDAO off-shoot pre-fork” would result in the same failure.

Conclusion

So, there you go. Do you need to run some dapp on block #190005, but deploy it before the fork? Now you can. Make sure to check ran(), though, or send a transaction first if you’re dire.

Any attached ether will be locked forever. This is intentional, since some (dated?) clients don’t allow sending 0 ether.

Liked this?

The dapp and article took ~ 8 hours to write and check, since I’m new and slow.

If you’d like to donate, check my drop page.