Tuesday, October 4
Shadow

Async Solidity Contracts



Having seen @pirapira's sketch of Bamboo ( https://github.com/pirapira/bamboo/ ), which proposed to add better control about the "smart contract program flow", even across calls, I thought that this should certainly be added to Solidity, and actually, it might even be possible now to a certain degree using inline assembly.

The problem is that with many functions in a contract, it is not always clear which can be called at which stage in the contract's lifetime. Certain smart contracts would be easier to understand if written as follows:

contract Auction {
  uint deadline = now + 1 weeks;
  address highest_bidder;
  uint highest_bid;
  mapping(address => uint) refunds;

  wait(); // This returns control flow to the caller
  // If the contract is called again, it continues here.
  while (now < deadline) {
    if (msg.value <= highest_bid) throw;
    refunds[highest_bidder] += highest_bid;
    highest_bidder = msg.sender;
    highest_bid = msg.value;
    wait(); // Same mechanism here
  }
  // auction ended, allow refunds to be processed
  while (true) {
    uint amount = refunds[msg.sender];
    if (msg.value > 0 || amount == 0)
      throw; // This throw will only revert back to the last wait()
    refunds[msg.sender] = 0;
    if (!msg.sender.send(amount)) throw;
    wait();
  }
}

And indeed, this is actually more or less possible right now!

pragma solidity ^0.4.0;

// This is the glue contract, look at "contract C" below to see it in action.
contract async {
    uint pc;
    function run() internal;
    function () payable {
        if (pc == 0)
            run();
        else if (pc == uint(-1))
            throw;
        else {
            uint pc_local = pc;
            assembly {
                pc_local jump
            }
        }
    }
    function wait() internal {
        uint retaddr;
        assembly {
            // move return address into local variable
            swap1
        }
        pc = retaddr;
        assembly { stop }
    }
    function finish() internal {
        pc = uint(-1);
    }
}

// Try it out: Call the fallback function of C twice
// and see how x is first set to 2 and then to 4
contract C is async {
    uint public x;
    function run() internal {
        x = 2;
        wait();
        x = 4;
        finish();
    }
}

// We can even have loops!
contract D is async {
    uint public x;
    function run() internal {
        while (true) {
            x++;
            wait();
        }
    }
}
// Just take care to not access any local variables inside
// run!

// Now let's try the auction contract:

contract Auction is async {
  uint deadline = now + 20 seconds;
  address public highest_bidder;
  uint public highest_bid;
  mapping(address => uint) refunds;

  function run() internal {
      while (now < deadline) {
        if (msg.value <= highest_bid) throw;
        refunds[highest_bidder] += highest_bid;
        highest_bidder = msg.sender;
        highest_bid = msg.value;
        wait(); // Same mechanism here
      }
      // auction ended, allow refunds to be processed
      while (true) {
        uint amount = refunds[msg.sender];
        if (msg.value > 0 || amount == 0)
          throw; // This throw will only revert back to the last wait()
        refunds[msg.sender] = 0;
        if (!msg.sender.send(amount)) throw;
        wait();
      }
  }
}

// Homework: Extend this into a framework where we can do:
// switch (wait(7)) { // return 7 and wait for next function call
//  case "bid(uint)": /* code to run if bid(uint) is called next */
//  case "abort(uint)": /* code to run if abort(uint) is called next */
// }
// If no case matches, the switch throws
// NB: switch is not yet implemented.

// Homework2: Extend this framework to allow "multi-threading",
// i.e. allow multiple entry points (not only he fallback function),
// each of them with their own pc pointer.

Leave a Reply