UNPKG

@unruggable/gateways

Version:

Trustless Ethereum Multichain CCIP-Read Gateway

151 lines (150 loc) 5.23 kB
import { ZeroHash } from 'ethers/constants'; import { isRPCError, toPaddedHex, withResolvers } from '../utils.mjs'; import { AbstractProver, makeStorageKey } from '../vm.mjs'; function proofEdge(proof) { const last = proof[proof.length - 1]; if (!('edge' in last)) throw new TypeError(`expected edge`); return last.edge; } function isContract(proof) { return !!proof.contract_data; } export class StarknetProver extends AbstractProver { static async latest(provider) { //}, relBlockTag: string) { return new this(provider, await provider.send('starknet_blockNumber', [])); } blockId; constructor(provider, block) { super(provider); this.blockId = { block_number: block }; } get context() { return { block: this.blockNumber }; } get blockNumber() { return this.blockId.block_number; } fetchBlock() { return this.cache.get('BLOCK', () => this.provider.send('starknet_getBlockWithTxHashes', [this.blockId])); } async fetchStateRoot() { return (await this.fetchBlock()).new_root; } async fetchTimestamp() { return (await this.fetchBlock()).timestamp; } async isContract(target, fast = this.fast) { target = target.toLowerCase(); if (fast) { return this.cache.get(target, async (a) => { try { await this.provider.send('starknet_getClassHashAt', [ this.blockId, a, ]); return true; } catch (err) { if (isRPCError(err, 20)) return false; throw err; } }); } return isContract(await this.getProofs(target)); } async getStorage(target, slot, fast = this.fast) { target = target.toLowerCase(); const accountProof = await this.proofLRU.touch(target); if (accountProof && !isContract(accountProof)) return ZeroHash; const storageKey = makeStorageKey(target, slot); const storageProof = await this.proofLRU.touch(storageKey); if (storageProof) { return toPaddedHex(proofEdge(storageProof).path.value); } if (fast) { return this.provider.send('starknet_getStorageAt', [ target, toPaddedHex(slot), this.blockId, ]); } const proofs = await this.getProofs(target, [slot]); return isContract(proofs) ? toPaddedHex(proofEdge(proofs.contract_proof).path.value) : ZeroHash; } prove(_needs) { throw new Error('Method not implemented.'); } async getProofs(target, slots = []) { target = target.toLowerCase(); const missing = []; const { promise, resolve, reject } = withResolvers(); let accountProof = this.proofLRU.touch(target); if (!accountProof) { this.proofLRU.setFuture(target, promise.then(() => accountProof)); } const storageProofs = slots.map((slot, i) => { const key = makeStorageKey(target, slot); const p = this.proofLRU.touch(key); if (!p) { this.proofLRU.setFuture(key, promise.then(() => storageProofs[i])); missing.push(i); } return p; }); if (!accountProof || missing.length) { try { const proofs = await this.fetchProofs(target, missing.map((x) => slots[x])); if (isContract(proofs)) { const v = proofs.contract_data.storage_proofs; proofs.contract_data.storage_proofs = []; missing.forEach((x, i) => (storageProofs[x] = v[i])); } accountProof = proofs; resolve(); } catch (err) { reject(err); throw err; } } // reassemble const [a, v] = await Promise.all([ accountProof, Promise.all(storageProofs), ]); this.checkStorageProofs(isContract(a), slots, v); const proofs = { ...a }; if (isContract(proofs)) { proofs.contract_data = { ...proofs.contract_data }; proofs.contract_data.storage_proofs = v; } return proofs; } async fetchProofs(target, slots = []) { const ps = []; for (let i = 0;;) { ps.push(this.provider.send('pathfinder_getProof', [ this.blockId, target, slots .slice(i, (i += this.proofBatchSize)) .map((slot) => toPaddedHex(slot)), ])); if (i >= slots.length) break; } const vs = await Promise.all(ps); if (isContract(vs[0])) { for (let i = 1; i < vs.length; i++) { vs[0].contract_data.storage_proofs.push(...vs[i].contract_data.storage_proofs); } } return vs[0]; } }