Skip to content

Join two series ​

Sample the share rate and the pooled ether on the same grid, and put them side by side on each block.

cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

from stETH at 25_800_000..26_000_000 every 7200 as a
| extend ethPerShare = format(a.getPooledEthByShares(1e18), 18)
| join (
    from stETH at 25_800_000..26_000_000 every 7200 as b
    | extend pooledEther = format(b.getTotalPooledEther(), 18)
  ) on $block
| project { block: $block, ethPerShare, pooledEther }
| order by block asc

Open in workbench →

29 rows, oldest first; the first six:

blockethPerSharepooledEther
258048001.242190142783145759577513.744139325389714808
258120001.2422710907264860539577726.064524543518701572
258192001.2423464406452301739578787.206360787975638105
258264001.2424241817093757619583377.950886032964272514
258336001.242501079439676829621710.894523357280615501
258408001.2425759764014953919624902.757062597687619272

One row per sampled block, with the share rate and the pooled ether at that block. The ethPerShare column is Sample a long range's, block for block. The two columns move differently. The share rate steps up once a day, when stETH rebases: the last two rows, 25 999 200 and 26 000 000, are 800 blocks apart and share one rate. The pooled ether moves at every sample, and falls as well as rises, because deposits and withdrawals change it between rebases; across those same two rows it grows by about 784.

How it reads ​

The two sources use the same range and the same step, so they land on the same blocks; that is what makes $block a usable key. An inner join keeps a row only where both sides have that block, which here is every block: 29 on each side, 29 joined. A key written as one name appears once in the output, so $block is one column, not two. The other columns both sides have are kept from both, the right one renamed with a number — $timestamp1, $address1 — and the project leaves them out.

The two sides read the same contract, which keeps the example small. The same shape joins two contracts — stETH on one side and wstETH's stEthPerToken() on the other — as long as their grids agree, and that is when join earns its place: two calls on one contract at one block fit in one project with no join at all, as Read the share rate now does.

Variations ​

  • Everything on either side. join kind=fullouter, then where isnull(pooledEther) or where isnull(ethPerShare) to see the blocks one side lacks.
  • A ratio. Read totalShares = format(b.getTotalShares(), 18) on the right-hand side beside pooledEther, then | extend computed = pooledEther / totalShares after the join: the share rate, computed from its two parts.
  • Across chains. $block is no key between chains, and on $block across two is refused (CQL3007). Sample both sides by time … every 1h, give each a column t = bin($sampleAt, 1h) with extend, and join on t, as Compare a contract across chains does. End both ranges on the hour: an end between two hours is sampled too, and repeats the last hour.

See also ​