Appearance
Follow a contract that names others
Ask stETH where the rest of Lido lives, then read the contract it names.
stETH holds one address it calls the locator, and the locator holds the addresses of everything else: the withdrawal queue, the treasury, the oracles. Two queries follow the trail; the one-query form is at the end.
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH as s
| project { locator: s.getLidoLocator(), treasury: s.getTreasury() }| locator | treasury |
|---|---|
| ethereum:0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb | ethereum:0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c |
Two addresses: the locator, and the treasury stETH names directly. Write the first as a let and ask it the same kind of question:
cql
let locator = ethereum:0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb;
from locator as l
| project { queue: l.withdrawalQueue(), treasury: l.treasury() }| queue | treasury |
|---|---|
| ethereum:0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1 | ethereum:0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c |
The withdrawal queue, and the same treasury stETH gave — the two agree, which is the check.
How it reads
s.getLidoLocator() is a call that returns an address, and an address in the grid carries a ⓘ: press it and the Contract tab shows what the contract is and what it offers. The second query is the first's answer written as a let. Neither query carries with { abi }, because both contracts are verified and CQL finds each ABI on its own.
The one-query form attaches an ABI to the address as it comes out of the call, so the locator's functions are callable on the same row that found it:
Not available yet
Calling through an address that another call returned, when that address carries with { abi: … }, is not available in the workbench yet. This page shows the query as the language defines it; it runs unchanged once it is.
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
let locator = abi ['function withdrawalQueue() view returns (address)', 'function treasury() view returns (address)'];
from stETH as s
| extend locatorAddr = s.getLidoLocator()
| extend l = locatorAddr with { abi: locator }
| project {
locator: locatorAddr,
queue: l.withdrawalQueue(),
treasury: l.treasury(),
same: l.treasury() == s.getTreasury()
}locatorAddr with { abi: locator } attaches an ABI from the query to the address, and l.withdrawalQueue() calls the locator with it. s.getTreasury() still goes to stETH — each alias keeps its own contract — so same is the check written as a column.
Variations
- From your library.
let locator = abi 'lidoLocator';names an ABI you uploaded instead of writing the signatures inline. - Keep going. The withdrawal queue is a contract too:
let queue = ethereum:0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1;andq.unfinalizedStETH()is the stETH waiting to leave. - Many at once. When a call returns a list rather than one value — the staking router's module ids, say —
expandgives a row per element before the calls that use it: Unfold an array into rows.