UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

94 lines (93 loc) 3.8 kB
import { AbstractRollup, align } from '../rollup.mjs'; import { ABI_CODER, EVM_BLOCKHASH_DEPTH, MAINNET_BLOCK_SEC } from '../utils.mjs'; import { EthProver } from '../eth/EthProver.mjs'; import { encodeRlpBlock } from '../rlp.mjs'; import { dataSlice } from 'ethers/utils'; import { Contract } from 'ethers/contract'; import { Interface } from 'ethers/abi'; export const L1_BLOCK_ABI = new Interface([ `function number() view returns (uint256)`, ]); // https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1Block.sol // TODO: should these be settings? // (the contract needs to know SLOT_HASH) const SLOT_NUMBER = 0n; const SLOT_HASH = 2n; const L1Block = '0x4200000000000000000000000000000000000015'; // default deployment // TODO: switch this to using previousBeaconRoot // see: test/research/eip-4788/ // im using chain1 as mainnet and chain2 as op // however the proving is from chain2 to chain1 // either rename chain1/chain2 to chainCall/chainData // or add direction: 1=>2 or 2=>1 // 20241116: testName() has reverse, but not a feature of the Rollup yet export class ReverseOPRollup extends AbstractRollup { commitStep; L1Block; //readonly storageSlot: bigint; // using const SLOT_* instead constructor(providers, config, // relax commitStep = 1) { super(providers); this.commitStep = commitStep; //this.latestBlockTag = 'latest'; // 20240922: not necessary this.L1Block = new Contract(config.L1Block ?? L1Block, L1_BLOCK_ABI, this.provider2); } async findL2Block(l1BlockNumber) { let b = (await this.provider2.getBlockNumber()) + 1; let a = Math.max(0, b - EVM_BLOCKHASH_DEPTH); while (a < b) { const middle = Math.floor((a + b) / 2); const value = await this.provider2.getStorage(this.L1Block.target, SLOT_NUMBER, middle); const block = BigInt(dataSlice(value, 24, 32)); // uint64 if (block == l1BlockNumber) return BigInt(middle); if (block > l1BlockNumber) { b = middle; } else { a = middle + 1; } } throw new Error(`unable to find block: ${l1BlockNumber}`); } async fetchLatestCommitIndex() { return align(await this.L1Block.number({ blockTag: this.latestBlockTag }), this.commitStep); } async _fetchParentCommitIndex(commit) { return align(commit.index - 1n, this.commitStep); } async _fetchCommit(index) { const prover = new EthProver(this.provider1, index); const prover2 = new EthProver(this.provider2, await this.findL2Block(index)); const [l1BlockInfo, l2BlockInfo, proof] = await Promise.all([ prover.fetchBlock(), prover2.fetchBlock(), prover2.fetchProofs(this.L1Block.target, [SLOT_HASH]), ]); return { index, rlpEncodedL1Block: encodeRlpBlock(l1BlockInfo), rlpEncodedL2Block: encodeRlpBlock(l2BlockInfo), accountProof: EthProver.encodeProof(proof.accountProof), storageProof: EthProver.encodeProof(proof.storageProof[0].proof), prover, }; } encodeWitness(commit, proofSeq) { return ABI_CODER.encode(['(bytes, bytes, bytes, bytes, bytes[], bytes)'], [ [ commit.rlpEncodedL1Block, commit.rlpEncodedL2Block, commit.accountProof, commit.storageProof, proofSeq.proofs, proofSeq.order, ], ]); } windowFromSec(sec) { // finalization is not on chain // L1 block time is 12 sec return Math.ceil(sec / MAINNET_BLOCK_SEC); } }