Skip to content
LogoLogo

Loops

for runs its body once per element of a list or once per number in a range. The number of iterations is fixed when the loop starts, until ends it early, and bound caps it. There is no while.

loops.lithe
(* Loops. The number of iterations is fixed when the loop starts: a range, or a list's
   length. `until` ends a loop early. `bound n` caps the count, and a longer list panics. *)
 
import "std/evm";
import "std/list";
 
interface Exchange is {
  function swap(amount : uint) : uint;
}
 
event Paid of record {
  indexed to : address;
  amount : wei;
}
 
storage is record {
  total : uint;
  gcd : uint;
  found : uint;
  members : list(address);
  isMember : map(address, bool);
  exchange : Exchange;
  results : map(uint, uint);
  paid : uint;
}
 
(* A range is half-open: 0 <= i < n. *)
external function triangle(const n : uint) is {
  var acc : uint := 0;
  for i : uint in 0 .. n do {
    acc := acc + i;
  }
  storage.total := acc;
}
 
(* A fixed list's length is in its type. `(i, x)` binds the index too. *)
external function weighted(const xs : list(uint, 3)) is {
  var acc : uint := 0;
  for (i, x) : (uint, uint) in xs do {
    acc := acc + x * (i + 1);
  }
  storage.total := acc;
}
 
external function join(const who : address) is {
  List.push(storage.members, who);
}
 
(* A storage list can grow, so its loop carries a bound. Past it, the call panics. *)
external function markAll() is {
  for who : address in storage.members bound 4 do {
    storage.isMember[who] := true;
  }
}
 
(* `until` is checked before each iteration, so the search stops once it has found. *)
external function find(const target : address) is {
  var hit : uint := 0;
  for (i, who) : (uint, address) in storage.members until hit <> 0 bound 4 do {
    if who = target then {
      hit := i + 1;
    }
  }
  storage.found := hit;
}
 
(* A tuple assignment steps two `var`s at once. *)
external function euclid(var a : uint; var b : uint) is {
  for _ : uint in 0 .. 256 until b = 0 do {
    (a, b) := (b, a % b);
  }
  storage.gcd := a;
}
 
(* Effects in a loop: one event and one deferred transfer per recipient. *)
external function pay(const tos : list(address, 2); const amounts : list(uint, 2)) is {
  for (i, to) : (uint, address) in tos do {
    const amount : wei := wei(amounts[i]);
    storage.paid := storage.paid + amounts[i];
    emit Paid { to = to; amount = amount; };
    defer Evm.transfer(to, amount);
  }
}
 
external function setExchange(const exchange : Exchange) is {
  storage.exchange := exchange;
}
 
(* A continuation in a loop sees that iteration's bindings. *)
external nonreentrant function swapAll(const amounts : list(uint, 2)) is {
  for (i, amount) : (uint, uint) in amounts do {
    defer storage.exchange.swap(amount) then (out : uint) {
      storage.results[i] := out;
    };
  }
}
 
external function grid(const n : uint) is {
  var acc : uint := 0;
  for i : uint in 0 .. n do {
    for j : uint in 0 .. n do {
      acc := acc + i * j;
    }
  }
  storage.total := acc;
}
 
external view function total() : uint is
  storage.total
  • triangle counts a half-open range: 0 .. n runs for 0 up to n - 1.
  • weighted walks a fixed list with its index. A fixed list's length is in its type, so the loop's cost is known when the contract is compiled.
  • markAll walks a storage list, which can grow. Its bound 4 makes the worst case visible, and a fifth member makes the call revert with Panic(0x01). Leave the bound off and the compiler warns that the loop's gas depends on storage.
  • find stops as soon as until is true. The condition is checked before every iteration, including the first.
  • euclid takes var parameters and steps them together with a tuple assignment. With b = 0 the loop runs zero times.
  • pay and swapAll show effects in a loop: one event and one deferred call per iteration, in order, and a continuation that sees its own iteration's i.
  • Maps cannot be iterated. Keep an iterable set when members must be listed.