diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index f390b5a..55e6394 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -1,13 +1,13 @@ import { configVariable, defineConfig } from "hardhat/config"; import hardhatToolboxViem from "@nomicfoundation/hardhat-toolbox-viem"; -import codegenPlugin from "./plugins/codegen/index.ts"; +import hardhatViemAbi from "hardhat-viem-abi"; import { tryLoadEnvFile } from "./lib/env.ts"; tryLoadEnvFile("./../.env"); tryLoadEnvFile(".env"); export default defineConfig({ - plugins: [hardhatToolboxViem, codegenPlugin], + plugins: [hardhatToolboxViem, hardhatViemAbi], codegen: { contracts: [ "CollateralVault", diff --git a/contracts/package.json b/contracts/package.json index c2ed643..6069764 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -27,6 +27,7 @@ "@types/node": "^22.0.0", "@typescript/native-preview": "7.0.0-dev.20260707.2", "hardhat": "^3.9.1", + "hardhat-viem-abi": "https://github.com/lsheva/hardhat-viem-abi.git#v1.0.0-alpha.1&path:packages/hardhat-viem-abi", "typescript": "^5.8.0" }, "dependencies": { @@ -35,5 +36,10 @@ "dotenv": "^16.4.1", "viem": "^2.52.2" }, - "packageManager": "pnpm@10.28.1" + "packageManager": "pnpm@10.28.1", + "pnpm": { + "onlyBuiltDependencies": [ + "hardhat-viem-abi" + ] + } } \ No newline at end of file diff --git a/contracts/plugins/codegen/compile-action.ts b/contracts/plugins/codegen/compile-action.ts deleted file mode 100644 index d005d43..0000000 --- a/contracts/plugins/codegen/compile-action.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { HardhatRuntimeEnvironment } from "hardhat/types/hre"; -import type { TaskArguments } from "hardhat/types/tasks"; - -export default async function ( - args: TaskArguments, - hre: HardhatRuntimeEnvironment, - runSuper: (args: TaskArguments) => Promise, -): Promise { - await runSuper(args); - const { main } = await import("./export-abi.ts"); - const { contracts } = hre.config.codegen; - main(contracts.length > 0 ? contracts : undefined); -} diff --git a/contracts/plugins/codegen/export-abi.ts b/contracts/plugins/codegen/export-abi.ts deleted file mode 100644 index 5e26fd0..0000000 --- a/contracts/plugins/codegen/export-abi.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * 1. Emits `abi/.ts` with `export const Abi = … as const` and - * `abi/.json` with the raw ABI array from Hardhat artifacts. - * 2. Collects unique Solidity `error` ABI items (+ `Error` / `Panic` builtins) into - * `abi/ContractErrors.json` and `abi/ContractErrors.ts`. - * - * Replaces hardhat-abi-exporter for Hardhat v3. - */ -import { mkdirSync, readFileSync, readdirSync, writeFileSync, rmSync } from "node:fs"; -import { basename, dirname, join, relative, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import type { Abi } from "viem"; -import { toFunctionSelector } from "viem"; -import { formatAbiItem } from "viem/utils"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = resolve(__dirname, "../.."); -const ARTIFACTS_DIR = resolve(REPO_ROOT, "artifacts"); -const OUT_DIR = resolve(REPO_ROOT, "abi"); -const OUT_ERRORS_TS = join(OUT_DIR, "ContractErrors.ts"); -const OUT_ERRORS_JSON = join(OUT_DIR, "ContractErrors.json"); - -function main(contracts?: string[]): void { - // Clear abi directory - rmSync(OUT_DIR, { recursive: true, force: true }); - - const bucket = new Map(); - - function add(err: AbiError, file: string): void { - const abi = toJsonAbiError(err); - const signature = formatAbiItem(abi); - const cur = bucket.get(signature); - if (!cur) { - bucket.set(signature, { - selector: errorSelector(abi), - signature, - abi, - files: [file], - }); - return; - } - if (!cur.files.includes(file)) { - cur.files.push(file); - } - } - - for (const builtin of BUILTIN) { - add(builtin, "(builtin)"); - } - - mkdirSync(OUT_DIR, { recursive: true }); - - for (const rel of listContractArtifactJson(contracts)) { - const src = resolve(ARTIFACTS_DIR, rel); - const name = basename(rel, ".json"); - if (!name) { - console.warn(` skipped ${rel} (no name)`); - continue; - } - try { - const raw = readFileSync(src, "utf-8"); - const artifact = JSON.parse(raw) as { abi?: unknown }; - if (!Array.isArray(artifact.abi)) { - console.warn(` skipped ${name} (no contract ABI)`); - continue; - } - - const abi = artifact.abi as Abi; - const outName = `${name}.ts`; - const dest = resolve(OUT_DIR, outName); - writeFileSync( - dest, - `export const ${name}Abi = ${JSON.stringify(artifact.abi, null, 2)} as const;\n`, - ); - writeFileSync(resolve(OUT_DIR, `${name}.json`), JSON.stringify(artifact.abi, null, 2) + "\n"); - console.log(` exported ${name}`); - - for (const item of abi) { - if (item.type !== "error") { - continue; - } - add(item as AbiError, outName); - } - } catch { - console.warn(` skipped ${name} (read/parse failed)`); - } - } - - const rows = [...bucket.values()].sort((a, b) => a.selector.localeCompare(b.selector)); - const outAbi = rows.map((r) => r.abi); - - writeFileSync( - OUT_ERRORS_TS, - `export const contractErrors = ${JSON.stringify(outAbi, null, 2)} as const;\n`, - "utf-8", - ); - writeFileSync(OUT_ERRORS_JSON, JSON.stringify(outAbi, null, 2) + "\n", "utf-8"); - - console.log(`contract errors: ${rows.length} unique → ${relative(REPO_ROOT, OUT_ERRORS_TS)}`); - console.log(""); - for (const r of rows) { - console.log(`${r.signature} - ${r.selector}`); - } -} - -/** All .json under artifacts except build-info, optionally filtered by contract name patterns. */ -function listContractArtifactJson(contracts?: string[]): string[] { - const relativePaths = readdirSync(ARTIFACTS_DIR, { recursive: true }) as string[]; - return relativePaths.filter((rel) => { - if (!rel.endsWith(".json") || rel.split(/[/\\]/).includes("build-info")) return false; - if (!contracts) return true; - const name = basename(rel, ".json"); - return contracts.some((pattern) => - pattern.includes("*") - ? new RegExp(`^${pattern.replace(/\*/g, ".*")}$`).test(name) - : name === pattern, - ); - }); -} - -type AbiError = { - inputs: { internalType: string; name: string; type: string }[]; - name: string; - type: "error"; -}; - -const BUILTIN: AbiError[] = [ - { - inputs: [{ internalType: "string", name: "message", type: "string" }], - name: "Error", - type: "error", - }, - { - inputs: [{ internalType: "uint256", name: "code", type: "uint256" }], - name: "Panic", - type: "error", - }, -]; - -function errorSelector(item: AbiError): `0x${string}` { - return toFunctionSelector({ - type: "function", - name: item.name, - inputs: item.inputs, - outputs: [], - stateMutability: "nonpayable", - }); -} - -function toJsonAbiError(item: AbiError): AbiError { - return JSON.parse(JSON.stringify(item)) as AbiError; -} - -type Accum = { - selector: `0x${string}`; - signature: string; - abi: AbiError; - files: string[]; -}; - -export { main }; diff --git a/contracts/plugins/codegen/index.ts b/contracts/plugins/codegen/index.ts deleted file mode 100644 index 299bad0..0000000 --- a/contracts/plugins/codegen/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { overrideTask } from "hardhat/config"; -import type { HardhatUserConfig, HardhatConfig } from "hardhat/types/config"; -import type { ConfigurationVariableResolver } from "hardhat/types/config"; -import type { HardhatPlugin } from "hardhat/types/plugins"; -import "./type-extensions.ts"; - -const codegenPlugin: HardhatPlugin = { - id: "codegen-after-compile", - hookHandlers: { - config: async () => ({ - default: async () => ({ - resolveUserConfig: async ( - userConfig: HardhatUserConfig, - resolveConfigVar: ConfigurationVariableResolver, - next: (u: HardhatUserConfig, r: ConfigurationVariableResolver) => Promise, - ) => { - const resolved = await next(userConfig, resolveConfigVar); - resolved.codegen = { contracts: userConfig.codegen?.contracts ?? [] }; - return resolved; - }, - }), - }), - }, - tasks: [ - overrideTask(["compile"]) - .setAction(() => import("./compile-action.ts")) - .build(), - ], -}; - -export default codegenPlugin; diff --git a/contracts/plugins/codegen/type-extensions.ts b/contracts/plugins/codegen/type-extensions.ts deleted file mode 100644 index fa305c1..0000000 --- a/contracts/plugins/codegen/type-extensions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { StringWithArtifactContractNamesAutocompletion } from "hardhat/types/artifacts"; - -declare module "hardhat/types/config" { - interface HardhatUserConfig { - codegen?: { - /** Contract names (exact or glob) to emit ABI files for. Exports all if omitted. */ - contracts?: StringWithArtifactContractNamesAutocompletion[]; - }; - } - - interface HardhatConfig { - codegen: { - contracts: string[]; - }; - } -} - -export {}; diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index b225172..8f9c3ae 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -42,6 +42,9 @@ importers: hardhat: specifier: ^3.9.1 version: 3.9.1 + hardhat-viem-abi: + specifier: https://github.com/lsheva/hardhat-viem-abi.git#v1.0.0-alpha.1&path:packages/hardhat-viem-abi + version: https://codeload.github.com/lsheva/hardhat-viem-abi/tar.gz/194bf0c28cc1ef7a37e4d6aec024fbd191f5b8a9#path:packages/hardhat-viem-abi(hardhat@3.9.1)(viem@2.52.2(typescript@5.9.3)(zod@3.25.76)) typescript: specifier: ^5.8.0 version: 5.9.3 @@ -751,6 +754,14 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + hardhat-viem-abi@https://codeload.github.com/lsheva/hardhat-viem-abi/tar.gz/194bf0c28cc1ef7a37e4d6aec024fbd191f5b8a9#path:packages/hardhat-viem-abi: + resolution: {path: packages/hardhat-viem-abi, tarball: https://codeload.github.com/lsheva/hardhat-viem-abi/tar.gz/194bf0c28cc1ef7a37e4d6aec024fbd191f5b8a9} + version: 1.0.0-alpha.1 + engines: {node: '>=22'} + peerDependencies: + hardhat: ^3.0.0 + viem: ^2.0.0 + hardhat@3.9.1: resolution: {integrity: sha512-yg+0oH5tWqdsxITh6fAJjAWOSHOkC2VPlsJDJwoifs2QS1t7kyRciMy5O2F846qzH+4iqRn1rbv/5voykX3RSQ==} hasBin: true @@ -1695,6 +1706,11 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + hardhat-viem-abi@https://codeload.github.com/lsheva/hardhat-viem-abi/tar.gz/194bf0c28cc1ef7a37e4d6aec024fbd191f5b8a9#path:packages/hardhat-viem-abi(hardhat@3.9.1)(viem@2.52.2(typescript@5.9.3)(zod@3.25.76)): + dependencies: + hardhat: 3.9.1 + viem: 2.52.2(typescript@5.9.3)(zod@3.25.76) + hardhat@3.9.1: dependencies: '@nomicfoundation/edr': 0.12.1 diff --git a/keeper/README.md b/keeper/README.md index 029b7d0..a1c3b14 100644 --- a/keeper/README.md +++ b/keeper/README.md @@ -223,7 +223,7 @@ Suites cover: - `discovery/tracker` — checksum dedupe, `onAdded` / `onChanged` listeners, startup backfill - `discovery/webhook` — payload extraction across `data` / `records` / array shapes - `runtime/scheduler` — alert ladder thresholds, queue upsert + executor kick wiring -- `oracle/priceFeed` — rebase to token decimals + contract-size unit (`CONTRACT_SIZE_HPS_DAY / ORACLE_UNIT_HPS_DAY`, ×10 at defaults), dispatch, no-op on unchanged answer +- `oracle/priceFeed` — rebase to token decimals (oracle already quotes 1 PH/s·day), dispatch, no-op on unchanged answer - `predict/mm` — net delta, stress, perp/futures unrealized loss, mm/im surplus - `predict/solve` — long/short downside & upside thresholds, drag from orderMargin/funding - `predict/predictiveIndex` — upsert/invalidate, sorted crossings on rise & drop diff --git a/keeper/src/oracle/priceFeed.ts b/keeper/src/oracle/priceFeed.ts index f262744..eadff8f 100644 --- a/keeper/src/oracle/priceFeed.ts +++ b/keeper/src/oracle/priceFeed.ts @@ -1,4 +1,3 @@ -import { HashPowerPerpsDEXAbi } from "derivatives-marketplace-abi/HashPowerPerpsDEX.ts"; import type pino from "pino"; import type { Chain } from "../chain.ts"; import type { Config } from "../config.ts"; @@ -39,13 +38,8 @@ export type PriceListener = (update: PriceUpdate) => void; * The feed also handles the upstream-decimals → token-decimals rebase: the * aggregator answer is `oracle.decimals()` (typically 8 for HashpriceUSD); * we rescale to the perps/futures token decimals (USDC = 6) so consumers - * compare apples to apples with `getMarketPrice()`. - * - * The oracle quotes the price of `ORACLE_UNIT_HPS_DAY` (100 TH/s over a day), but the - * venues denominate one contract in `contractSizeHpsDay` (default 1e15 = 1 PH/s over a - * day). We read that contract-size multiplier from the perps venue once at `start()` and - * apply `contractSizeHpsDay / ORACLE_UNIT_HPS_DAY` so the streamed price matches on-chain - * `getMarketPrice()`. Both venues are assumed to share the same contract size. + * compare apples to apples with `getMarketPrice()`. The oracle already quotes + * 1 PH/s per day (= venue `CONTRACT_SIZE_HPS_DAY`), so no unit rebase is applied. * * Lifecycle: * - `start()`: read decimals, prime `current` via one `latestRoundData`, @@ -60,10 +54,6 @@ export class PriceFeed { private unwatch: (() => void) | undefined; /** 10^(oracleDecimals - tokenDecimals). Set during `start()`. */ private rescaleDivisor: bigint = 1n; - /** Contract size in hashes/s·day (`contractSizeHpsDay`). Set during `start()`. */ - private contractSizeHpsDay: bigint = 1n; - /** Oracle quote basis in hashes/s·day (`ORACLE_UNIT_HPS_DAY`). Set during `start()`. */ - private oracleUnitHpsDay: bigint = 1n; private readonly chain: Chain; private readonly config: Config; @@ -102,28 +92,6 @@ export class PriceFeed { } this.rescaleDivisor = 10n ** BigInt(oracleDecimals - this.tokenDecimals); - // Rebase from the oracle's quote basis (100 TH/s/day) to one contract unit - // (contractSizeHpsDay/day), matching `getMarketPrice()` on-chain. - const [contractSizeHpsDay, oracleUnitHpsDay] = await Promise.all([ - this.chain.publicClient.readContract({ - address: this.config.perps.address, - abi: HashPowerPerpsDEXAbi, - functionName: "CONTRACT_SIZE_HPS_DAY", - }) as Promise, - this.chain.publicClient.readContract({ - address: this.config.perps.address, - abi: HashPowerPerpsDEXAbi, - functionName: "ORACLE_UNIT_HPS_DAY", - }) as Promise, - ]); - if (contractSizeHpsDay <= 0n || oracleUnitHpsDay <= 0n) { - throw new Error( - `PriceFeed: invalid contract size (contractSizeHpsDay=${contractSizeHpsDay}, ORACLE_UNIT_HPS_DAY=${oracleUnitHpsDay})`, - ); - } - this.contractSizeHpsDay = contractSizeHpsDay; - this.oracleUnitHpsDay = oracleUnitHpsDay; - await this.refresh("start"); // We watch BTC/USDC (not HashpriceUSD) because HashpriceUSD is a pure @@ -147,8 +115,6 @@ export class PriceFeed { btcUsdcFeed: this.config.oracle.btcUsdcFeedAddress, oracleDecimals, tokenDecimals: this.tokenDecimals, - contractSizeHpsDay: this.contractSizeHpsDay, - oracleUnitHpsDay: this.oracleUnitHpsDay, currentPrice: this.currentPrice, }, "PriceFeed started", @@ -199,9 +165,8 @@ export class PriceFeed { return; } - // Mirror on-chain `getMarketPrice()`: rebase decimals first, then apply the - // contract-size multiplier (contractSizeHpsDay / ORACLE_UNIT_HPS_DAY). - const next = ((answer / this.rescaleDivisor) * this.contractSizeHpsDay) / this.oracleUnitHpsDay; + // Mirror on-chain `getMarketPrice()`: rebase decimals only (oracle quotes 1 PH/s/day). + const next = answer / this.rescaleDivisor; const prev = this.currentPrice; if (prev === next) return; diff --git a/keeper/tests/integration/deployStack.ts b/keeper/tests/integration/deployStack.ts index 8caf98c..afb6095 100644 --- a/keeper/tests/integration/deployStack.ts +++ b/keeper/tests/integration/deployStack.ts @@ -93,17 +93,16 @@ export interface DeployedStack { tokenDecimals: number; oracleDecimals: number; /** - * Raw hashprice oracle answer (per 100 TH/s·day, i.e. `ORACLE_UNIT_HPS_DAY`). - * Both venues rebase this to a per-contract mark via - * `market = answer × CONTRACT_SIZE_HPS_DAY / ORACLE_UNIT_HPS_DAY` (= ×10), - * so this seed is `initialMarketPrice / 10`. Fixtures that need to re-post - * the oracle (e.g. delivery settlement) write this value directly. + * Raw hashprice oracle answer (per 1 PH/s·day). Matches `initialMarketPrice` + * when oracle and token decimals align — venues apply only decimal scaling. + * Fixtures that need to re-post the oracle (e.g. delivery settlement) write + * this value directly. */ initialHashprice: bigint; /** - * Per-contract mark at deploy time (= `initialHashprice × 10`). This is the - * unit orders and positions are denominated in — scenarios use it as the - * at-the-money entry price. + * Per-contract mark at deploy time (= `initialHashprice` after decimal scale). + * This is the unit orders and positions are denominated in — scenarios use + * it as the at-the-money entry price. */ initialMarketPrice: bigint; initialBtcUsdc: bigint; @@ -124,25 +123,15 @@ const TOKEN_DECIMALS = 6; const ORACLE_DECIMALS = 6; const QUANTITY_DECIMALS = 6; -/** - * Ratio by which both venues rebase the oracle answer into a per-contract mark: - * `CONTRACT_SIZE_HPS_DAY / ORACLE_UNIT_HPS_DAY = 1e15 / 1e14 = 10`. The oracle - * quotes 100 TH/s·day; one contract settles 1 PH/s·day, so the mark is ×10 the - * raw answer. Exported so scenarios convert market prices → oracle answers in - * one place. - */ -export const ORACLE_TO_MARKET_MULTIPLIER = 10n; - /** * Per-contract mark at deploy time. Positions and orders are denominated in this - * (contract) unit; the oracle answer is seeded at `/ ORACLE_TO_MARKET_MULTIPLIER` - * so `getMarketPrice()` (answer × 10) lands back here. Kept at $4.21 so the - * pre-existing perps fixtures (which never carried the duration factor) keep - * their dollar sizing unchanged. + * (contract) unit; the oracle answer is seeded to the same value so + * `getMarketPrice()` lands here (oracle already quotes 1 PH/s·day). Kept at + * $4.21 so pre-existing perps fixtures keep their dollar sizing unchanged. */ const INITIAL_MARKET_PRICE = parseUnits("4.21", TOKEN_DECIMALS); -/** Raw hashprice oracle answer (per 100 TH/s·day) — rebased ×10 into the mark above. */ -const INITIAL_HASHPRICE = INITIAL_MARKET_PRICE / ORACLE_TO_MARKET_MULTIPLIER; +/** Raw hashprice oracle answer (per 1 PH/s·day) — equals the mark above. */ +const INITIAL_HASHPRICE = INITIAL_MARKET_PRICE; /** Reference BTC/USDC mid-price; only the *delta* matters for predictor tests. */ const INITIAL_BTC_USDC = parseUnits("65000", ORACLE_DECIMALS); diff --git a/keeper/tests/integration/scenarios.ts b/keeper/tests/integration/scenarios.ts index a7acba7..d7af704 100644 --- a/keeper/tests/integration/scenarios.ts +++ b/keeper/tests/integration/scenarios.ts @@ -1,11 +1,6 @@ import { parseUnits, type Address } from "viem"; import { hardhat } from "viem/chains"; -import { - deployStack, - ORACLE_TO_MARKET_MULTIPLIER, - type DeployedStack, - type Wallet, -} from "./deployStack.ts"; +import { deployStack, type DeployedStack, type Wallet } from "./deployStack.ts"; /** * Fixture builders. @@ -36,14 +31,13 @@ import { // ───────────────────────────────────────────────────────────────────────── export interface BaseFixture extends DeployedStack { - /** Write a raw hashprice *oracle answer* (per 100 TH/s·day). */ + /** Write a raw hashprice *oracle answer* (per 1 PH/s·day). */ bumpHashprice(newPrice: bigint): Promise; bumpBtcUsdc(newPrice: bigint): Promise; /** - * Set the per-contract *mark* (`getMarketPrice()` value). Internally divides - * by `ORACLE_TO_MARKET_MULTIPLIER` (×10 rebase) before writing the oracle, so - * callers can reason in the same contract unit that orders/positions use. - * Does not touch BTC/USDC — used to stage a fixture's at-the-money entry mark. + * Set the per-contract *mark* (`getMarketPrice()` value). Writes the same + * value to the oracle (oracle already quotes 1 PH/s·day). Does not touch + * BTC/USDC — used to stage a fixture's at-the-money entry mark. */ setMark(marketPrice: bigint): Promise; /** Deposit USDC into the vault from the given (test-known) wallet. */ @@ -189,27 +183,21 @@ export interface CrossVenueOrdersAndPositionsFixture extends CrossVenueFixture { // Base fixture // ───────────────────────────────────────────────────────────────────────── -/** Convert a per-contract mark into the raw oracle answer the venues rebase ×10. */ -function markToOracle(marketPrice: bigint): bigint { - return marketPrice / ORACLE_TO_MARKET_MULTIPLIER; -} - export async function baseFixture(rpcUrl: string): Promise { const stack = await deployStack(rpcUrl); return { ...stack, bumpHashprice: (price) => writeOracle(stack, stack.addresses.hashpriceOracle, price), bumpBtcUsdc: (price) => writeOracle(stack, stack.addresses.btcUsdcFeed, price), - setMark: (marketPrice) => - writeOracle(stack, stack.addresses.hashpriceOracle, markToOracle(marketPrice)), + setMark: (marketPrice) => writeOracle(stack, stack.addresses.hashpriceOracle, marketPrice), deposit: (user, amount) => depositTo(stack, user, amount), crashOracles: async (marketPrice) => { - await writeOracle(stack, stack.addresses.hashpriceOracle, markToOracle(marketPrice)); + await writeOracle(stack, stack.addresses.hashpriceOracle, marketPrice); const movedBtc = (stack.config.initialBtcUsdc * 9n) / 10n; await writeOracle(stack, stack.addresses.btcUsdcFeed, movedBtc); }, pumpOracles: async (marketPrice) => { - await writeOracle(stack, stack.addresses.hashpriceOracle, markToOracle(marketPrice)); + await writeOracle(stack, stack.addresses.hashpriceOracle, marketPrice); const movedBtc = (stack.config.initialBtcUsdc * 11n) / 10n; await writeOracle(stack, stack.addresses.btcUsdcFeed, movedBtc); }, diff --git a/keeper/tests/oracle/priceFeed.test.ts b/keeper/tests/oracle/priceFeed.test.ts index dcf530c..c3dce82 100644 --- a/keeper/tests/oracle/priceFeed.test.ts +++ b/keeper/tests/oracle/priceFeed.test.ts @@ -46,12 +46,6 @@ function makeChainStub(initialAnswer: bigint, decimals: number): ChainStub { publicClient: { readContract: async ({ functionName }: { functionName: string }) => { if (functionName === "decimals") return decimals; - // Contract-size rebase reads. Returning equal values gives a 1× - // passthrough so these tests assert the pure decimals rebase without a - // contract-size multiplier (the x10 factor is exercised by the venue / - // integration paths that use the real 1e15 / 100e12 constants). - if (functionName === "CONTRACT_SIZE_HPS_DAY") return 100n * 10n ** 12n; - if (functionName === "ORACLE_UNIT_HPS_DAY") return 100n * 10n ** 12n; if (functionName === "latestRoundData") { reads++; return [1n, currentAnswer, 1_000n, 1_000n, 1n] as const; diff --git a/keeper/tests/predict/coordinator.test.ts b/keeper/tests/predict/coordinator.test.ts index 9b9c059..9fffdbe 100644 --- a/keeper/tests/predict/coordinator.test.ts +++ b/keeper/tests/predict/coordinator.test.ts @@ -87,11 +87,6 @@ function buildHarness({ publicClient: { readContract: async ({ functionName }: { functionName: string }) => { if (functionName === "decimals") return 8; - // Contract-size rebase reads (PriceFeed.start). Equal values → 1× - // passthrough, so the streamed price stays $100 and matches the - // `computePortfolioMM` price the harness derives from the same answer. - if (functionName === "CONTRACT_SIZE_HPS_DAY") return 100n * 10n ** 12n; - if (functionName === "ORACLE_UNIT_HPS_DAY") return 100n * 10n ** 12n; if (functionName === "latestRoundData") { return [1n, oracleAnswer, 1_000n, 1_000n, 1n] as const; } diff --git a/keeper/tests/predict/coordinatorAlerts.test.ts b/keeper/tests/predict/coordinatorAlerts.test.ts index a927270..b551be7 100644 --- a/keeper/tests/predict/coordinatorAlerts.test.ts +++ b/keeper/tests/predict/coordinatorAlerts.test.ts @@ -64,11 +64,6 @@ function buildHarness({ balance, perpEntry }: { balance: bigint; perpEntry: bigi publicClient: { readContract: async ({ functionName }: { functionName: string }) => { if (functionName === "decimals") return 8; - // Contract-size rebase reads (PriceFeed.start). Equal values → 1× - // passthrough, so the streamed price stays $100 and matches the IM/MM - // the harness derives from the same answer. - if (functionName === "CONTRACT_SIZE_HPS_DAY") return 100n * 10n ** 12n; - if (functionName === "ORACLE_UNIT_HPS_DAY") return 100n * 10n ** 12n; if (functionName === "latestRoundData") { return [1n, oracleAnswer, 1_000n, 1_000n, 1n] as const; } diff --git a/market-maker/src/adapters/futures/venue.ts b/market-maker/src/adapters/futures/venue.ts index 814631d..fdd4d8c 100644 --- a/market-maker/src/adapters/futures/venue.ts +++ b/market-maker/src/adapters/futures/venue.ts @@ -125,7 +125,7 @@ export class FuturesVenueAdapter implements VenueAdapter { publicClient: this.publicClient, label: "futures", resolve: async () => { - const [oracle, divisor, contractSizeHpsDay, oracleUnitHpsDay] = await this.publicClient.multicall({ + const [oracle, divisor] = await this.publicClient.multicall({ allowFailure: false, contracts: [ { @@ -138,19 +138,9 @@ export class FuturesVenueAdapter implements VenueAdapter { abi: FuturesAbi, functionName: "hashpriceScalingDivisor", }, - { - address: this.address, - abi: FuturesAbi, - functionName: "CONTRACT_SIZE_HPS_DAY", - }, - { - address: this.address, - abi: FuturesAbi, - functionName: "ORACLE_UNIT_HPS_DAY", - }, ], }); - return { oracle, divisor, contractSizeHpsDay, oracleUnitHpsDay }; + return { oracle, divisor }; }, }); } diff --git a/market-maker/src/adapters/perps/venue.ts b/market-maker/src/adapters/perps/venue.ts index ec0e4bf..8df2079 100644 --- a/market-maker/src/adapters/perps/venue.ts +++ b/market-maker/src/adapters/perps/venue.ts @@ -109,28 +109,17 @@ export class PerpsVenueAdapter implements VenueAdapter { abi: HashPowerPerpsDEXAbi, functionName: "priceOracle", }); - const [oracleDecimals, tokenDecimals, contractSizeHpsDay, oracleUnitHpsDay] = - await this.publicClient.multicall({ - allowFailure: false, - contracts: [ - { - address: oracle, - abi: chainlinkAggregatorAbi, - functionName: "decimals", - }, - { address: token, abi: erc20Abi, functionName: "decimals" }, - { - address: this.address, - abi: HashPowerPerpsDEXAbi, - functionName: "CONTRACT_SIZE_HPS_DAY", - }, - { - address: this.address, - abi: HashPowerPerpsDEXAbi, - functionName: "ORACLE_UNIT_HPS_DAY", - }, - ], - }); + const [oracleDecimals, tokenDecimals] = await this.publicClient.multicall({ + allowFailure: false, + contracts: [ + { + address: oracle, + abi: chainlinkAggregatorAbi, + functionName: "decimals", + }, + { address: token, abi: erc20Abi, functionName: "decimals" }, + ], + }); if (tokenDecimals > oracleDecimals) { throw new Error( `perps: tokenDecimals (${tokenDecimals}) > oracleDecimals (${oracleDecimals})`, @@ -139,8 +128,6 @@ export class PerpsVenueAdapter implements VenueAdapter { return { oracle, divisor: 10n ** BigInt(oracleDecimals - tokenDecimals), - contractSizeHpsDay, - oracleUnitHpsDay, }; }, }); diff --git a/market-maker/src/core/rawOracle.ts b/market-maker/src/core/rawOracle.ts index ff3421d..d345b9f 100644 --- a/market-maker/src/core/rawOracle.ts +++ b/market-maker/src/core/rawOracle.ts @@ -7,17 +7,16 @@ * 2-tick floor on the symmetric bid/ask layout. * * `RawOracleReader` reads the underlying Chainlink aggregator directly and - * applies the same `10^(oracle.decimals − token.decimals)` rebase AND the same - * contract-size multiplier (`contractSizeHpsDay / ORACLE_UNIT_HPS_DAY`) the venue does, + * applies the same `10^(oracle.decimals − token.decimals)` rebase the venue does, * but skips the tick rounding. The MM gets a unit-precision mid that lands * between ticks ~99% of the time, so `roundDownToTick(r) → bidMid` and * `roundUpToTick(r) → askMid` produce a 1-tick spread without any extra * pricing-strategy plumbing. * - * The two venues differ only in *how* the (oracle address, scaling divisor, - * contract-size multiplier) tuple is discovered. Each adapter supplies that as a - * `resolve()` callback; the reader caches the result for the lifetime of the - * process (all three change only on `setOracle`/`setContractSize`-style admin txs). + * The two venues differ only in *how* the (oracle address, scaling divisor) + * tuple is discovered. Each adapter supplies that as a `resolve()` callback; + * the reader caches the result for the lifetime of the process (both change + * only on `setOracle`-style admin txs). */ import type { PublicClient } from "viem"; @@ -50,10 +49,6 @@ export interface RawOracleConfig { oracle: `0x${string}`; /** 10^(oracle.decimals − token.decimals); used to rebase the answer to token decimals. */ divisor: bigint; - /** Contract size in hashes/s·day (`contractSizeHpsDay`). Numerator of the unit rebase. */ - contractSizeHpsDay: bigint; - /** The oracle's quote basis in hashes/s·day (`ORACLE_UNIT_HPS_DAY`). Denominator of the unit rebase. */ - oracleUnitHpsDay: bigint; } export class RawOracleReader { @@ -88,9 +83,8 @@ export class RawOracleReader { if (answer <= 0n) { throw new Error(`${this.label}: oracle returned non-positive answer (${answer.toString()})`); } - // Mirror the venue's `getMarketPrice()`: rebase decimals first, then apply the - // contract-size multiplier (contractSizeHpsDay / ORACLE_UNIT_HPS_DAY). - return ((answer / this.cache.divisor) * this.cache.contractSizeHpsDay) / this.cache.oracleUnitHpsDay; + // Mirror the venue's `getMarketPrice()` decimal rebase (no unit factor). + return answer / this.cache.divisor; } /** Drop cached (oracle, divisor) — next `read()` will re-resolve. */