Skip to content
LogoLogo

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 then is a separate phase. The exchange sees each order as Pending while it runs.
  • storage.floor is re-read in the continuation, so if the exchange changed it, the check uses the new value.