Skip to content

Read a value that depends on the caller ​

Ask stETH to simulate a one-stETH transfer from two different callers, side by side.

cql
let stETH  = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
let wstETH = ethereum:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
let nobody = ethereum:0x0000000000000000000000000000000000000001;

from stETH as s
| project {
    asHolder: s.transfer(nobody, 1e18) with { caller: wstETH },
    asNobody: s.transfer(wstETH, 1e18) with { caller: nobody }
  }

Open in workbench →

asHolderasNobody
truenull

wstETH holds millions of stETH, so the transfer it appears to make succeeds and asHolder is true; nobody holds none, so the transfer it appears to make reverts and asNobody is null. Nothing was sent either way. revert_reason is the language's way to read why a call reverted, and it is not available in the workbench yet.

How it reads ​

with { caller: wstETH } after a call sets who that one call appears to come from. Nothing is sent and nothing is signed; the contract simply answers as if wstETH had asked. transfer is not a view function, so CQL simulates it, notes the simulation as CQL3908, and no balance moves. The second call carries a different caller, and the two sit in one row because both are made on the same source row.

Any function that reads msg.sender — a balance check, a whitelist, an allowance — answers differently per caller, and this is how you see the difference without holding either key.

Put the option on the source instead — from stETH with { caller: wstETH } as s — and every call through s carries it.

Variations ​

  • Show why. Add balance: format(s.balanceOf(nobody), 18) to the projection and the null explains itself.
  • Send value with the call. s.submit(nobody) with { caller: wstETH, value: 1e18 } simulates staking one ETH; submit is payable, so value is allowed, and the answer is the shares it would mint.

See also ​