Skip to content
LogoLogo

Collections

A collection lives in storage. A collection function takes the collection as its first argument, as a storage path such as storage.members, and reads and writes it in place.

import "std/evm";
import "std/collections/set";
import "std/collections/set_enumerable";
import "std/collections/map_enumerable";
import "std/collections/bitmap";
import "std/collections/queue";
 
contract Airdrop is {
  use Bitmap;
 
  storage is record {
    admins : Set(address);
    holders : SetEnumerable(address);
    claims : MapEnumerable(address, uint);
    claimed : Bitmap;
    waiting : Queue(address);
  }
 
  external function claim(const index : uint) is {
    if not Bitmap.get(storage.claimed, index) then {
      Bitmap.set(storage.claimed, index);
      const _ : bool := SetEnumerable.add(storage.holders, Evm.sender());
      const _ : bool := MapEnumerable.set(storage.claims, Evm.sender(), index);
    }
  }
 
  external function claimRange(const start : uint; const amount : uint) is {
    Bitmap.setBatch(storage.claimed, start, amount);
  }
 
  external view function claimedIn(const start : uint; const amount : uint) : uint is
    Bitmap.popCount(storage.claimed, start, amount)
 
  external view function claimOf(const who : address) : uint
    raises EnumerableMapNonexistentKey
  is
    MapEnumerable.get(storage.claims, who)
 
  external function wait() is {
    Queue.push(storage.waiting, Evm.sender());
  }
 
  external view function holderCount() : uint is
    SetEnumerable.length(storage.holders)
 
  external view function isAdmin(const who : address) : bool is
    Set.contains(storage.admins, who)
}
ModuleTypeFunctions
Set(T)a map from T to boolcontains, add, remove
SetEnumerable(T)a list and an index mapcontains, add, remove, length, at
MapEnumerable(K, V)an enumerable set of keys and a map of valuescontains, set, remove, get, tryGet, length, at
BitmapBitmap, 256 flags a slotget, set, unset, setTo, setBatch, unsetBatch, popCount
Queue(T)a map and two counterspush, pop, front, back, at, length, isEmpty
  • Set and SetEnumerable take a MapKey: an integer, bool, address, bytesN, or a nominal type over one. SetEnumerable costs more to write, because it keeps a list as well as the map.
  • SetEnumerable.add and remove, and MapEnumerable.set and remove, answer whether they changed membership, as OpenZeppelin's do: add and set whether the element or key is new, remove whether it was there. A call statement must return nothing, so bind an answer you don't need to _, as in const _ : bool := SetEnumerable.add(storage.holders, who);. An answer bound to _ isn't computed: the code is the same as for a function that answers nothing.
  • remove in SetEnumerable and MapEnumerable moves the last element into the gap, so order isn't kept.
  • MapEnumerable is OpenZeppelin's EnumerableMap for any key and value. get raises its EnumerableMapNonexistentKey, with the key's ABI word as the bytes32. tryGet answers an option instead, and at(index) a (key, value) pair, raising OutOfBounds past the end. remove leaves the old value in storage, where nothing reads it: its slot is cold by then, so deleting it would cost more than its refund, and setting the key again costs less.
  • Bitmap.setBatch(bits, start, amount) and unsetBatch change the amount flags from start, a whole slot at a time, and popCount(bits, start, amount) counts those that are set, as Solady's LibBitmap does.
  • Queue.pop deletes the element it returns, which refunds its slots: about 1,800 gas a pop for a one-slot T.