Skip to content

Commit

Permalink
feat: add generateProof script
Browse files Browse the repository at this point in the history
  • Loading branch information
GitGuru7 committed Oct 29, 2024
1 parent 8838697 commit 5ce043e
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 25 deletions.
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "module-alias/register";

import "@nomicfoundation/hardhat-chai-matchers";
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-etherscan";
import "@openzeppelin/hardhat-upgrades";
import "@typechain/hardhat";
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.zksync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "@matterlabs/hardhat-zksync";
import "@matterlabs/hardhat-zksync-solc";
import "@matterlabs/hardhat-zksync-verify";
import "@nomicfoundation/hardhat-chai-matchers";
import "@nomiclabs/hardhat-ethers";
import "hardhat-dependency-compiler";
import "hardhat-deploy";
import { HardhatUserConfig, task } from "hardhat/config";
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@nomicfoundation/hardhat-network-helpers": "^1.0.6",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.8",
"@nomiclabs/hardhat-ethers": "^2.2.1",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.2",
"@openzeppelin/contracts": "^4.8.2",
"@openzeppelin/contracts-upgradeable": "^4.8.2",
Expand All @@ -77,6 +77,7 @@
"@types/chai": "^4.3.4",
"@types/debug": "^4.1.12",
"@types/fs-extra": "^9.0.13",
"@types/json-stable-stringify": "^1.0.36",
"@types/mocha": "^10.0.0",
"@types/node": "^18.16.3",
"@typescript-eslint/eslint-plugin": "^5.44.0",
Expand All @@ -98,6 +99,7 @@
"hardhat-docgen": "^1.3.0",
"hardhat-gas-reporter": "^1.0.9",
"husky": "^8.0.3",
"json-stable-stringify": "^1.1.1",
"lint-staged": "^13.0.4",
"lodash": "^4.17.21",
"mocha": "^10.1.0",
Expand Down
175 changes: 175 additions & 0 deletions script/generateProofs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import "dotenv/config";
import { BigNumber, utils } from "ethers";
import { hexStripZeros, hexZeroPad } from "ethers/lib/utils.js";
import * as fs from "fs";
import { network } from "hardhat";
import stringify from "json-stable-stringify";

import {
formatToProofRLP,
getExtendedBlock,
getProof,
getSolidityStorageSlotBytes,
getSolidityTwoLevelStorageSlotHash,
prepareBLockRLP,
} from "./utils.ts";

export type ProofData = {
blockNumber?: number;
blockHash?: string;
accountStateProofRLP?: string;
blockHeaderRLP?: string;
xvsVaultAddress?: string;
checkpointsSlot?: string;
numCheckpointsSlot?: string;
checkpointsSlotHash?: string;
numCheckpointsSlotHash?: string;
numCheckpoints?: number;
xvsVaultNumCheckpointsStorageProofRlp?: string;
checkpoint?: {
fromBlockNumber?: string;
votes?: string;
};
xvsVaultCheckpointsStorageProofRlp?: string;
};

const xvsVault = {
sepolia: "0x1129f882eAa912aE6D4f6D445b2E2b1eCbA99fd5",
ethereum: "0xA0882C2D5DF29233A092d2887A258C2b90e9b994",
opbnbtestnet: "0xB14A0e72C5C202139F78963C9e89252c1ad16f01",
opbnbmainnet: "0x7dc969122450749A8B0777c0e324522d67737988",
arbitrumone: "0x8b79692AAB2822Be30a6382Eb04763A74752d5B4",
arbitrumsepolia: "0x407507DC2809D3aa31D54EcA3BEde5C5c4C8A17F",
zksyncsepolia: "0x825f9EE3b2b1C159a5444A111A70607f3918564e",
zksyncmainnet: "0xbbB3C88192a5B0DB759229BeF49DcD1f168F326F",
opsepolia: "0x4d344e48F02234E82D7D1dB84d0A4A18Aa43Dacc",
opmainnet: "0x133120607C018c949E91AE333785519F6d947e01",
};

const SLOTS = {
checkpoints: 16,
numCheckpoint: 17,
};

const saveJson = (stringifiedJson: string) => {
fs.writeFileSync(`./tests/Syncing-of-votes/${process.env.REMOTE_NETWORK}Proofs.json`, stringifiedJson);
};

export const getProofsJson = (): ProofData => {
try {
const file = fs.readFileSync(`./tests/Syncing-of-votes/${process.env.REMOTE_NETWORK}Proofs.json`);
return JSON.parse(file.toString());
} catch (error) {
return {};
}
};

const generateRoots = async (xvsVaultAddress: string, numCheckpointSlotRaw: number, checkpointSlotRaw: number) => {
const proofsJson = getProofsJson();

if (!proofsJson["xvsVaultAddress"]) {
proofsJson["xvsVaultAddress"] = xvsVaultAddress;
}

// calculate blockHeaderRLP
const blockData = await getExtendedBlock(parseInt(process.env.BLOCK as string));
const blockHeaderRLP = prepareBLockRLP(blockData);
proofsJson.blockHash = blockData.hash;
proofsJson.blockNumber = BigNumber.from(blockData.number).toNumber();
proofsJson.blockHeaderRLP = blockHeaderRLP;

// calculate slots
const slots: string[] = [];

const checkpointSlot = hexZeroPad(utils.hexlify(checkpointSlotRaw), 32);
slots.push(checkpointSlot);
proofsJson.checkpointsSlot = checkpointSlot;

const numCheckpointSlot = hexZeroPad(utils.hexlify(numCheckpointSlotRaw), 32);
slots.push(numCheckpointSlot);
proofsJson.numCheckpointsSlot = numCheckpointSlot;

// get account state proof rlp
const rawAccountProofData = await getProof(xvsVaultAddress, slots, proofsJson.blockNumber);
const accountStateProofRLP = formatToProofRLP(rawAccountProofData.accountProof);
proofsJson.accountStateProofRLP = accountStateProofRLP;
saveJson(stringify(proofsJson));
};

const generateProofsNumCheckpointsSlot = async (vault: string, rawSlot: number, voter: string) => {
const proofsJson = getProofsJson();
const hexSlot = utils.hexlify(rawSlot);
const slot = getSolidityStorageSlotBytes(hexSlot, voter);
if (!proofsJson.blockNumber) {
throw new Error("blockNumber is not set");
}

const numCheckpointsHex = await network.provider.send("eth_getStorageAt", [
vault,
slot,
hexStripZeros(utils.hexlify(proofsJson.blockNumber)),
]);

const numCheckpoints = BigNumber.from(numCheckpointsHex).toNumber();

const rawProofData = await getProof(vault, [slot], proofsJson.blockNumber);

const storageProofRlp = formatToProofRLP(rawProofData.storageProof[0].proof);
proofsJson.numCheckpointsSlotHash = slot;
proofsJson.numCheckpoints = numCheckpoints;
proofsJson.xvsVaultNumCheckpointsStorageProofRlp = storageProofRlp;

saveJson(stringify(proofsJson));
};

const generateXvsVaultProofsByCheckpoint = async (vault: string, rawSlot: number, voter: string) => {
const hexSlot = utils.hexlify(rawSlot);
const proofsJson = getProofsJson();

if (!proofsJson.numCheckpoints) {
throw new Error("numCheckpoints is zero");
}

const slot = getSolidityTwoLevelStorageSlotHash(hexSlot, voter, proofsJson.numCheckpoints - 1);

if (!proofsJson.blockNumber) {
throw new Error("blockNumber is not set");
}

const checkpointData = await network.provider.send("eth_getStorageAt", [
vault,
slot,
hexStripZeros(utils.hexlify(proofsJson.blockNumber)),
]);

const fromBlockNumberHex = "0x" + checkpointData.slice(-8);
const votesHex = checkpointData.slice(0, -8);
const fromBlockNumber = BigNumber.from(fromBlockNumberHex).toString();
const votes = BigNumber.from(votesHex).toString();

const rawProofData = await getProof(vault, [slot], proofsJson.blockNumber);

const storageProofRlp = formatToProofRLP(rawProofData.storageProof[0].proof);

proofsJson.checkpointsSlotHash = slot;
proofsJson.checkpoint = {
fromBlockNumber,
votes,
};
proofsJson.xvsVaultCheckpointsStorageProofRlp = storageProofRlp;
saveJson(stringify(proofsJson));
};

const generateJson = async () => {
const XVS_VAULT = xvsVault[process.env.REMOTE_NETWORK as string];

await generateRoots(XVS_VAULT, SLOTS.checkpoints, SLOTS.numCheckpoint);

await generateProofsNumCheckpointsSlot(XVS_VAULT, SLOTS.numCheckpoint, process.env.VOTER as string);

await generateXvsVaultProofsByCheckpoint(XVS_VAULT, SLOTS.checkpoints, process.env.VOTER as string);
};

(async () => {
await generateJson();
})();
85 changes: 85 additions & 0 deletions script/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ethers, providers, utils } from "ethers";
import { defaultAbiCoder, hexStripZeros, hexZeroPad, keccak256 } from "ethers/lib/utils.js";

const provider = new providers.StaticJsonRpcProvider(process.env[`ARCHIVE_NODE_${process.env.REMOTE_NETWORK}`]);

const RLP = require("rlp");

Check warning on line 6 in script/utils.ts

View workflow job for this annotation

GitHub Actions / Lint

Require statement not part of import statement

export function formatToProofRLP(rawData) {
return ethers.utils.RLP.encode(rawData.map(d => ethers.utils.RLP.decode(d)));
}

export const getProof = async (address: string, storageKeys: string[], blockNumber: number) => {
return await provider.send("eth_getProof", [address, storageKeys, hexStripZeros(utils.hexlify(blockNumber))]);
};

export const getExtendedBlock = async (blockNumber: number) => {
const blockNumber_ = blockNumber ? hexStripZeros(utils.hexlify(blockNumber)) : "latest";
return provider.send("eth_getBlockByNumber", [blockNumber_, false]);
};
export function prepareBLockRLP(rawBlock) {
const blockHeader = [
rawBlock.parentHash,
rawBlock.sha3Uncles,
rawBlock.miner,
rawBlock.stateRoot,
rawBlock.transactionsRoot,
rawBlock.receiptsRoot,
rawBlock.logsBloom,
rawBlock.difficulty === "0x0" ? "0x" : rawBlock.difficulty,
rawBlock.number,
rawBlock.gasLimit === "0x0" ? "0x" : rawBlock.gasLimit,
rawBlock.gasUsed === "0x0" ? "0x" : rawBlock.gasUsed,
rawBlock.timestamp,
rawBlock.extraData,
rawBlock.mixHash,
rawBlock.nonce,
];
if (rawBlock.baseFeePerGas) {
blockHeader.push(rawBlock.baseFeePerGas === "0x0" ? "0x" : rawBlock.baseFeePerGas);
}
if (rawBlock.withdrawalsRoot) {
blockHeader.push(rawBlock.withdrawalsRoot);
}
if (rawBlock.blobGasUsed) {
blockHeader.push(rawBlock.blobGasUsed === "0x0" ? "0x" : rawBlock.blobGasUsed);
}
if (rawBlock.excessBlobGas) {
blockHeader.push(rawBlock.excessBlobGas === "0x0" ? "0x" : rawBlock.excessBlobGas);
}
if (rawBlock.parentBeaconBlockRoot) {
blockHeader.push(rawBlock.parentBeaconBlockRoot);
}
const encodedHeader = RLP.encode(blockHeader);
const encodedHeaderHex = "0x" + Buffer.from(encodedHeader).toString("hex");

return encodedHeaderHex;
}

export function getSolidityStorageSlotBytes(
mappingSlot, //: BytesLike,
key, //: string
) {
const slot = hexZeroPad(mappingSlot, 32);
return hexStripZeros(keccak256(defaultAbiCoder.encode(["address", "uint256"], [key, slot])));
}

export function getSolidityTwoLevelStorageSlotHash(
rawSlot, // number
voter, // string
numCheckpoints, // number
) {
const abiCoder = new ethers.utils.AbiCoder();
// ABI Encode the first level of the mapping
// abi.encode(address(voter), uint256(MAPPING_SLOT))
// The keccak256 of this value will be the "slot" of the inner mapping
const firstLevelEncoded = abiCoder.encode(["address", "uint256"], [voter, ethers.BigNumber.from(rawSlot)]);

// ABI Encode the second level of the mapping
// abi.encode(uint256(numCheckpoints))
const secondLevelEncoded = abiCoder.encode(["uint256"], [ethers.BigNumber.from(numCheckpoints)]);

// Compute the storage slot of [address][uint256]
// keccak256(abi.encode(uint256(numCheckpoints)) . abi.encode(address(voter), uint256(MAPPING_SLOT)))
return ethers.utils.keccak256(ethers.utils.concat([secondLevelEncoded, ethers.utils.keccak256(firstLevelEncoded)]));
}
Loading

0 comments on commit 5ce043e

Please sign in to comment.