@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
94 lines (93 loc) • 3.8 kB
JavaScript
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);
}
}