Skip to content
LogoLogo

Time lock

The admin queues a payment for a future time, and it can execute only after a minimum delay and before a grace period ends. Watchers get time to react before it runs.

time-lock.lithe
(* Payments are queued with a delay, so anyone watching has time to react before
   they execute. The admin can cancel a queued payment. *)
 
import "std/abi";
import "std/crypto";
import "std/evm";
 
error NotAdmin;
error DelayTooShort of uint;
error NotQueued of bytes32;
error TooEarly of record {
  now : timestamp;
  eta : timestamp;
}
error Stale of bytes32;
 
event Queued of record {
  indexed id : bytes32;
  to : address;
  value : wei;
  eta : timestamp;
}
 
event Executed of record {
  indexed id : bytes32;
}
 
event Cancelled of record {
  indexed id : bytes32;
}
 
storage is record {
  admin : address;
  queued : map(bytes32, bool);
}
 
pure function minDelay() : uint is 2days
pure function gracePeriod() : uint is 14days
 
external function init() is {
  storage.admin := Evm.sender();
}
 
view function onlyAdmin() raises NotAdmin is {
  assert(Evm.sender() = storage.admin, NotAdmin);
}
 
external pure function paymentId(const to : address; const value : wei; const eta : timestamp) : bytes32 is
  Crypto.keccak256(Abi.encode(to, value, eta))
 
external function queue(const to : address; const value : wei; const eta : timestamp) : bytes32
  raises NotAdmin | DelayTooShort
is {
  onlyAdmin();
  assert(eta >= Evm.blockTimestamp() + minDelay(), DelayTooShort(minDelay()));
  const id : bytes32 := Crypto.keccak256(Abi.encode(to, value, eta));
  storage.queued[id] := true;
  emit Queued { id = id; to = to; value = value; eta = eta; };
} with id
 
external function cancel(const id : bytes32)
  raises NotAdmin | NotQueued
is {
  onlyAdmin();
  assert(storage.queued[id], NotQueued(id));
  storage.queued[id] := false;
  emit Cancelled { id = id; };
}
 
external nonreentrant function execute(const to : address; const value : wei; const eta : timestamp)
  raises NotAdmin | NotQueued | TooEarly | Stale
is {
  onlyAdmin();
  const id : bytes32 := Crypto.keccak256(Abi.encode(to, value, eta));
  assert(storage.queued[id], NotQueued(id));
  assert(Evm.blockTimestamp() >= eta, TooEarly { now = Evm.blockTimestamp(); eta = eta; });
  assert(Evm.blockTimestamp() <= eta + gracePeriod(), Stale(id));
  storage.queued[id] := false;
  emit Executed { id = id; };
  defer Evm.transfer(to, value);
}
 
external payable function fund() is {
  skip
}