@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
139 lines (138 loc) • 6.18 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScrollRollup = void 0;
const rollup_js_1 = require("../rollup.cjs");
const contract_1 = require("ethers/contract");
const abi_1 = require("ethers/abi");
const utils_1 = require("ethers/utils");
const EthProver_js_1 = require("../eth/EthProver.cjs");
const utils_js_1 = require("../utils.cjs");
// https://github.com/scroll-tech/scroll-contracts/
// https://docs.scroll.io/en/developers/ethereum-and-scroll-differences/
// https://status.scroll.io/
// https://github.com/scroll-tech/scroll/tree/738c85759d0248c005469972a49fc983b031ff1c/contracts/src/L1
const ROLLUP_ABI = new abi_1.Interface([
`function lastFinalizedBatchIndex() view returns (uint256)`,
`function finalizedStateRoots(uint256 batchIndex) view returns (bytes32)`,
`event FinalizeBatch(
uint256 indexed batchIndex,
bytes32 indexed batchHash,
bytes32 stateRoot,
bytes32 withdrawRoot
)`,
`event CommitBatch(
uint256 indexed batchIndex,
bytes32 indexed batchHash
)`,
`function commitBatchWithBlobProof(
uint8 version,
bytes parentBatchHeader,
bytes[] chunks,
bytes skippedL1MessageBitmap,
bytes blobDataProof
)`,
`function commitBatch(
uint8 version,
bytes calldata parentBatchHeader,
bytes[] memory chunks,
bytes calldata skippedL1MessageBitmap
)`,
]);
// 20240815: commits are approximately every minute
// to make caching useful, we align to a step
// note: use 1 to disable the alignment
// 20240827: finalization is every ~15 min
class ScrollRollup extends rollup_js_1.AbstractRollup {
// 20250417: https://x.com/Scroll_ZKP/status/1912944671686533541
// https://docs.scroll.io/en/developers/scroll-contracts/
// https://etherscan.io/address/0xC4362457a91B2E55934bDCb7DaaF6b1aB3dDf203
// https://mainnet-api-re.scroll.io/api/
// https://scrollscan.com/batches
// static readonly mainnetConfig: RollupDeployment<ScrollConfig> = {
// chain1: CHAINS.MAINNET,
// chain2: CHAINS.SCROLL,
// ScrollChain: '0xa13BAF47339d63B743e7Da8741db5456DAc1E556',
// poseidon: '0x3508174Fa966e75f70B15348209E33BC711AE63e',
// };
// 20250307: https://sepolia.etherscan.io/tx/0xa8c2e812c47ff2f076a64687d380fa3b79cccfa5bc8368be1993907788f3ee50
// https://sepolia.etherscan.io/address/0x64cb3A0Dcf43Ae0EE35C1C15edDF5F46D48Fa570
// https://sepolia-api-re.scroll.io/api/
// https://sepolia.scrollscan.com/batches
// static readonly sepoliaConfig: RollupDeployment<ScrollConfig> = {
// chain1: CHAINS.SEPOLIA,
// chain2: CHAINS.SCROLL_SEPOLIA,
// ScrollChain: '0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0',
// poseidon: '0xFeE7242E8587d7E22Ea5E9cFC585d0eDB6D57faA',
// };
ScrollChain;
poseidon;
constructor(providers, config) {
super(providers);
this.ScrollChain = new contract_1.Contract(config.ScrollChain, ROLLUP_ABI, this.provider1);
this.poseidon = config.poseidon;
}
async fetchLatestCommitIndex() {
return this.ScrollChain.lastFinalizedBatchIndex({
blockTag: this.latestBlockTag,
});
}
async _fetchParentCommitIndex(commit) {
return this.ScrollChain.lastFinalizedBatchIndex({
blockTag: commit.l1BlockNumber - 1,
});
}
async _fetchCommit(index) {
// 20241029: removed offchain indexer dependency
const [[commitEvent], [finalEvent]] = await Promise.all([
this.ScrollChain.queryFilter(this.ScrollChain.filters.CommitBatch(index)),
this.ScrollChain.queryFilter(this.ScrollChain.filters.FinalizeBatch(index)),
]);
if (!commitEvent)
throw new Error(`unknown batch`);
if (!finalEvent)
throw new Error('not finalized');
const tx = await commitEvent.getTransaction();
const desc = this.ScrollChain.interface.parseTransaction(tx);
if (!desc)
throw new Error(`unknown transaction: ${tx.hash}`);
const { chunks } = desc.args;
if (!Array.isArray(chunks))
throw new Error('no chunks');
const prover = new EthProver_js_1.EthProver(this.provider2, lastBlockFromChunks(chunks));
return { index, prover, l1BlockNumber: finalEvent.blockNumber };
}
encodeWitness(commit, proofSeq) {
return utils_js_1.ABI_CODER.encode(['(uint256, bytes[], bytes)'], [[commit.index, proofSeq.proofs, proofSeq.order]]);
}
encodeWitnessV1(commit, proofSeq) {
const compressed = proofSeq.storageProofs.map((storageProof) => (0, utils_1.concat)([
(0, utils_js_1.toPaddedHex)(proofSeq.accountProof.length, 1),
...proofSeq.accountProof,
(0, utils_js_1.toPaddedHex)(storageProof.length, 1),
...storageProof,
]));
return utils_js_1.ABI_CODER.encode(['(uint256)', '(bytes, bytes[])'], [[commit.index], ['0x', compressed]]);
}
windowFromSec(sec) {
// finalization time is not on-chain
// https://etherscan.io/advanced-filter?eladd=0xa13baf47339d63b743e7da8741db5456dac1e556&eltpc=0x26ba82f907317eedc97d0cbef23de76a43dd6edb563bdb6e9407645b950a7a2d
const span = 20; // every 10-20 batches
const freq = 3600; // every hour?
return span * Math.ceil(sec / freq); // units of batchIndex
}
}
exports.ScrollRollup = ScrollRollup;
function lastBlockFromChunks(chunks) {
// this supports V0 and V1
// https://docs.scroll.io/en/technology/chain/rollup/#chunk-codec
// https://github.com/scroll-tech/scroll-contracts/blob/main/src/libraries/codec/ChunkCodecV0.sol
// https://github.com/scroll-tech/scroll-contracts/blob/main/src/libraries/codec/ChunkCodecV1.sol
// this likely doesn't happen due to ErrorBatchIsEmpty()
if (!chunks.length)
throw new Error('no chunks');
const chunk = chunks[chunks.length - 1];
const SIZE = 60;
const count = parseInt(chunk.slice(0, 4)); // uint8 => numBlocks
const pos = 1 + SIZE * (count - 1);
return BigInt((0, utils_1.dataSlice)(chunk, pos, pos + 8)); // uint64 => block[numBlocks - 1].blockIndex
}