Skip to content
LogoLogo

Raw calls

An interface describes calls whose shape is known when the contract is written. A wallet, a timelock, or a multisig runs calls chosen later, so it sends calldata as bytes. Evm.call and Evm.delegateCall are deferred like every state-changing call, and Evm.staticCall is queried.

raw_calls.lithe
(* A wallet that runs whatever calls its owner asks for: any target, any calldata, any
   value. `Evm.call` is deferred like every call, so the wallet's own state is written
   before the target runs, and the continuation gets the return data as it came back. *)
 
import "std/bytes";
import "std/evm";
 
error NotOwner;
error MismatchedLengths;
 
event Executed of record {
  indexed target : address;
  value : uint;
  result : bytes;
}
 
storage is record {
  owner : address;
  nonce : uint;
}
 
deploy(const owner : address) is {
  storage.owner := owner;
}
 
payable receive is {
  skip
}
 
external nonreentrant function execute(const target : address; const value : uint; const data : bytes)
  raises NotOwner
is {
  assert(Evm.sender() = storage.owner, NotOwner);
  storage.nonce := storage.nonce + 1;
  defer Evm.call(target, data) with value = value then (result : bytes) {
    emit Executed { target = target; value = value; result = result; };
  };
}
 
(* One call per target, in order. A failed call passes its revert data on unchanged. *)
external nonreentrant function batch(const targets : list(address); const calls : list(bytes))
  raises NotOwner | MismatchedLengths | forwarded
is {
  assert(Evm.sender() = storage.owner, NotOwner);
  assert(targets.length = calls.length, MismatchedLengths);
  for (i, target) : (uint, address) in targets do {
    defer Evm.call(target, calls[i]) then {
    | Ok -> { skip }
    | Error(reason : bytes) -> { forward reason; }
    };
  }
}
 
(* A read-only call with calldata of your own: here, the SHA-256 precompile. *)
external view function sha256Of(const data : bytes) : bytes32 is
  Bytes.word(query Evm.staticCall(0x0000000000000000000000000000000000000002, data), 0)
 
external view function nonce() : uint is
  storage.nonce
  • execute bumps nonce before the target runs: the deferred call sees the wallet's state already written.
  • The continuation's result is the return data exactly as it came back. Read it with Abi.decode when you know its shape.
  • In batch, a failed call reverts the whole batch with the target's own revert data, through forward reason, so it raises forwarded.
  • Evm.staticCall reaches precompiles, whose input is not ABI-encoded. Address 0x…02 is SHA-256.
  • payable receive lets the wallet hold the ether it sends.

See Raw calls for the rules, including Evm.delegateCall.