Order executor
A larger continuation example. executeAndDeposit swaps, and its continuation deposits the swap's output, and that continuation's own continuation records the shares. unwind destructures a tuple result.
order-executor.lithe
(* An order executor that swaps on an exchange and deposits the proceeds, using
each call's result in a continuation. The order is marked pending before the
exchange runs, so the exchange, and anything it calls, sees a consistent state. *)
type SwapResult is record {
amountOut : uint;
fee : uint;
}
interface Exchange is {
function swap(amountIn : uint; minOut : uint) : SwapResult;
function deposit(amount : uint) : uint;
function split(shares : uint) : (uint, uint);
}
type OrderStatus is
| New
| Pending
| Complete
| Unwound
type Order is record {
status : OrderStatus;
amountIn : uint;
amountOut : uint;
shares : uint;
}
error AlreadyExecuted of uint;
error NotComplete of uint;
error TooLittle of record {
amountOut : uint;
floor : uint;
}
event Executed of record {
indexed id : uint;
amountOut : uint;
fee : uint;
}
event Deposited of record {
indexed id : uint;
shares : uint;
}
storage is record {
exchange : Exchange;
floor : uint;
executed : uint;
totalOut : uint;
left : uint;
right : uint;
orders : map(uint, Order);
}
external function init(const exchange : Exchange; const floor : uint) is {
storage.exchange := exchange;
storage.floor := floor;
}
external nonreentrant function execute(const id : uint; const amountIn : uint; const minOut : uint)
raises AlreadyExecuted | TooLittle
is {
assert(storage.orders[id].status = New, AlreadyExecuted(id));
storage.orders[id].status := Pending;
storage.orders[id].amountIn := amountIn;
defer storage.exchange.swap(amountIn, minOut)
then (result : SwapResult) {
assert(
result.amountOut >= storage.floor,
TooLittle { amountOut = result.amountOut; floor = storage.floor; }
);
storage.orders[id].status := Complete;
storage.orders[id].amountOut := result.amountOut;
storage.executed := storage.executed + 1;
storage.totalOut := storage.totalOut + result.amountOut;
emit Executed { id = id; amountOut = result.amountOut; fee = result.fee; };
};
}
external nonreentrant function executeAndDeposit(const id : uint; const amountIn : uint)
raises AlreadyExecuted | TooLittle
is {
assert(storage.orders[id].status = New, AlreadyExecuted(id));
storage.orders[id].status := Pending;
storage.orders[id].amountIn := amountIn;
defer storage.exchange.swap(amountIn, 0)
then (result : SwapResult) {
storage.orders[id].status := Complete;
storage.orders[id].amountOut := result.amountOut;
defer storage.exchange.deposit(result.amountOut)
then (shares : uint) {
storage.orders[id].shares := shares;
emit Deposited { id = id; shares = shares; };
};
};
}
external nonreentrant function unwind(const id : uint)
raises NotComplete
is {
assert(storage.orders[id].status = Complete, NotComplete(id));
defer storage.exchange.split(storage.orders[id].shares)
then ((a, b) : (uint, uint)) {
storage.orders[id].status := Unwound;
storage.left := storage.left + a;
storage.right := storage.right + b;
};
}
external view function orderOf(const id : uint) : Order is storage.orders[id]- Each
thenis a separate phase. The exchange sees each order asPendingwhile it runs. storage.flooris re-read in the continuation, so if the exchange changed it, the check uses the new value.

