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.