@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
101 lines (100 loc) • 4.14 kB
JavaScript
import { Interface } from 'ethers/abi';
import { CHAINS } from '../chains.mjs';
import { AbstractRollup, } from '../rollup.mjs';
import { StarknetProver } from './StarknetProver.mjs';
import { Contract } from 'ethers/contract';
import { ABI_CODER } from '../utils.mjs';
import { EthProver } from '../eth/EthProver.mjs';
import { encodeRlpBlock } from '../rlp.mjs';
import { dataSlice } from 'ethers/utils';
import { id as keccakStr } from 'ethers/hash';
const CORE_ABI = new Interface([
`function stateRoot() view returns (bytes32)`,
`function stateBlockNumber() view returns (uint256)`,
`function updateStateKzgDA(uint256[] programOutput, bytes[] kzgProofs)`,
`event LogStateUpdate(uint256 globalRoot, int256 blockNumber, uint256 blockHash)`,
]);
const SLOT_STATE_ROOT = BigInt(keccakStr('STARKNET_1.0_INIT_STARKNET_STATE_STRUCT'));
export class StarknetRollup extends AbstractRollup {
// https://docs.starknet.io/tools/important-addresses/
static mainnetConfig = {
chain1: CHAINS.MAINNET,
chain2: CHAINS.STARKNET,
Rollup: '0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4',
};
static sepoliaConfig = {
chain1: CHAINS.SEPOLIA,
chain2: CHAINS.SCROLL_SEPOLIA,
Rollup: '0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057',
};
Rollup;
constructor(providers, config) {
super(providers);
this.Rollup = new Contract(config.Rollup, CORE_ABI, this.provider1);
}
async findStateUpdate(l2BlockNumber) {
loop: for (let block = await this.provider1.getBlockNumber(); block >= 0; block -= this.getLogsStepSize) {
const events = await this.Rollup.queryFilter(this.Rollup.filters.LogStateUpdate(), Math.max(0, block - this.getLogsStepSize), block);
for (let i = events.length - 1; i >= 0; i--) {
const event = events[i];
const bn = BigInt(dataSlice(event.data, 32, 64));
if (bn === l2BlockNumber)
return event;
if (bn < l2BlockNumber)
break loop;
}
}
throw new Error(`not finalized: ${l2BlockNumber}`);
}
fetchLatestCommitIndex() {
return this.Rollup.stateBlockNumber({
blockTag: this.latestBlockTag,
});
}
async _fetchParentCommitIndex(commit) {
const tx = await this.provider1.getTransaction(commit.commitTx);
if (!tx)
throw new Error(`no commit: ${commit.commitTx}`);
const desc = this.Rollup.interface.parseTransaction(tx);
if (!desc || desc.name !== 'updateStateKzgDA') {
throw new Error(`expected updateStateKzgDA: ${tx}`);
}
return desc.args.programOutput[2]; // prev blockNumber
}
async _fetchCommit(index) {
const event = await this.findStateUpdate(index);
// const [event] = await this.Rollup.queryFilter(
// this.Rollup.filters.LogStateUpdate(null, index, null)
// );
// if (!event) throw new Error(`not finalized`);
const prover1 = new EthProver(this.provider1, event.blockNumber);
const [block, proof] = await Promise.all([
prover1.fetchBlock(),
prover1.fetchProofs(await this.Rollup.getAddress(), [SLOT_STATE_ROOT]),
]);
const prover = new StarknetProver(this.provider2, Number(index));
return {
index,
prover,
commitTx: event.transactionHash,
rlpEncodedL1Block: encodeRlpBlock(block),
accountProof: EthProver.encodeProof(proof.accountProof),
storageProof: EthProver.encodeProof(proof.storageProof[0].proof),
};
}
encodeWitness(commit, proofSeq) {
return ABI_CODER.encode(['(uint256, bytes, bytes, bytes, bytes[], bytes)'], [
commit.index,
commit.rlpEncodedL1Block,
commit.accountProof,
commit.storageProof,
proofSeq.proofs,
proofSeq.order,
]);
}
windowFromSec(_sec) {
// finalization is not onchain
// TODO: fix me
return 69420;
}
}