Skip to content
LogoLogo

Dispatch

Group actions by role, attach one guard per group, and let the compiler flatten the tree into ordinary ABI functions. Each flattened function carries every guard on its path.

dispatch.lithe
(* A variant parameter with nested groups becomes a flat ABI. This contract exposes
   `mint(address,uint256)`, `setMinter(address)`, `transfer(address,uint256)`, and
   `burn(uint256)`. Each carries the guards on its path through the tree. *)
 
import "std/evm";
 
error NotAdmin;
error NotMinter;
 
type MintParams is record {
  to : address;
  amount : uint;
}
 
type TransferParams is record {
  to : address;
  amount : uint;
}
 
type AdminAction is
  | SetMinter external setMinter of address
 
type MinterAction is
  | Mint external mint of MintParams
 
type HolderAction is
  | Transfer external transfer of TransferParams
  | Burn external burn of uint
 
type Action is
  | Admin of AdminAction
  | Minter of MinterAction
  | Holder of HolderAction
 
storage is record {
  admin : address;
  minter : address;
  balances : map(address, uint);
}
 
function adminMain(const action : AdminAction)
  raises NotAdmin
is {
  assert(Evm.sender() = storage.admin, NotAdmin);
}
with case action of {
| SetMinter(who) -> { storage.minter := who; }
}
 
function minterMain(const action : MinterAction)
  raises NotMinter
is {
  assert(Evm.sender() = storage.minter, NotMinter);
}
with case action of {
| Mint(p) -> { storage.balances[p.to] := storage.balances[p.to] + p.amount; }
}
 
function holderMain(const action : HolderAction) is
  case action of {
  | Transfer(p) -> {
      storage.balances[Evm.sender()] := storage.balances[Evm.sender()] - p.amount;
      storage.balances[p.to] := storage.balances[p.to] + p.amount;
    }
  | Burn(amount) -> {
      storage.balances[Evm.sender()] := storage.balances[Evm.sender()] - amount;
    }
  }
 
external function main(const action : Action)
  raises NotAdmin | NotMinter
is
  case action of {
  | Admin(a) -> adminMain(a)
  | Minter(a) -> minterMain(a)
  | Holder(a) -> holderMain(a)
  }
 
external function init() is {
  storage.admin := Evm.sender();
}

Each leaf case names its ABI function with external: | Mint external mint of MintParams is mint(address,uint256). A case without one is exposed as written, so | Mint of MintParams alone would be Mint(address,uint256). The group cases, Admin, Minter, and Holder, are never entrypoints, so they take no name.

lithe abi-tree dispatch.lithe shows each entrypoint's path and guards:

mint
  path:
    main -> minterMain -> mint

  calls:
    none

  guards:
    - Evm.sender() = storage.minter

  ...