const fs = require("fs"); const path = require("path"); const { ethers } = require("ethers"); const DEFAULT_OFFERS_PATH = path.resolve(__dirname, "../deploy/offers.template.json"); const DEFAULT_RUNTIME_PATH = path.resolve(__dirname, "../deploy/runtime-addresses.base-sepolia.json"); const DEFAULT_ENTITLEMENT_PATH = path.resolve(__dirname, "../deploy/entitlement-deploy.sepolia.json"); const OFFER_ABI = [ "function offerConfig(string offerId) view returns (uint256 priceAtomic, bool active, bool membershipRequired, bool exists)", ]; function required(name, value) { if (!String(value || "").trim()) { throw new Error(`missing required env: ${name}`); } return String(value).trim(); } function readJSON(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } function resolveEntitlementAddress() { const envAddress = String(process.env.ENTITLEMENT_CONTRACT_ADDRESS || "").trim(); if (envAddress) { return envAddress; } if (fs.existsSync(DEFAULT_RUNTIME_PATH)) { const runtime = readJSON(DEFAULT_RUNTIME_PATH); if (runtime.entitlement_contract) { return String(runtime.entitlement_contract).trim(); } } if (fs.existsSync(DEFAULT_ENTITLEMENT_PATH)) { const deploy = readJSON(DEFAULT_ENTITLEMENT_PATH); if (deploy.entitlementContract) { return String(deploy.entitlementContract).trim(); } } return ""; } function loadExpectedOffers() { const offersPath = String(process.env.OFFERS_JSON || "").trim() || DEFAULT_OFFERS_PATH; if (!fs.existsSync(offersPath)) { throw new Error(`offers file missing: ${offersPath}`); } const raw = readJSON(offersPath); if (!Array.isArray(raw) || raw.length === 0) { throw new Error("offers json must be a non-empty array"); } return raw.map((entry, idx) => { if (!entry || typeof entry !== "object") { throw new Error(`offers[${idx}] must be an object`); } const offerId = String(entry.offer_id || "").trim(); if (!offerId) { throw new Error(`offers[${idx}] missing offer_id`); } const priceAtomic = String(entry.price_atomic || "").trim(); if (!/^[0-9]+$/.test(priceAtomic)) { throw new Error(`offers[${offerId}] invalid price_atomic`); } return { offerId, priceAtomic, active: entry.active === undefined ? true : Boolean(entry.active), membershipRequired: entry.membership_required === undefined ? true : Boolean(entry.membership_required), }; }); } async function main() { const rpcURL = required("BASE_SEPOLIA_RPC_URL", process.env.BASE_SEPOLIA_RPC_URL); const entitlementAddress = resolveEntitlementAddress(); if (!ethers.utils.isAddress(entitlementAddress)) { throw new Error(`invalid/missing entitlement contract address: ${entitlementAddress}`); } const expectedOffers = loadExpectedOffers(); const provider = new ethers.providers.JsonRpcProvider(rpcURL); const contract = new ethers.Contract(entitlementAddress, OFFER_ABI, provider); const mismatches = []; for (const offer of expectedOffers) { const cfg = await contract.offerConfig(offer.offerId); const got = { exists: Boolean(cfg.exists), priceAtomic: cfg.priceAtomic.toString(), active: Boolean(cfg.active), membershipRequired: Boolean(cfg.membershipRequired), }; const expected = { exists: true, priceAtomic: offer.priceAtomic, active: offer.active, membershipRequired: offer.membershipRequired, }; const match = got.exists === expected.exists && got.priceAtomic === expected.priceAtomic && got.active === expected.active && got.membershipRequired === expected.membershipRequired; if (!match) { mismatches.push({ offer_id: offer.offerId, expected, got, }); } } if (mismatches.length > 0) { console.error( JSON.stringify( { status: "FAIL", entitlement_contract: entitlementAddress, mismatches, }, null, 2 ) ); process.exit(1); } console.log( JSON.stringify( { status: "PASS", entitlement_contract: entitlementAddress, offers_checked: expectedOffers.length, }, null, 2 ) ); } main().catch((err) => { console.error(`FAIL: ${err.message || err}`); process.exit(1); });