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