From 56cf1212c592ac3306752aeb68913ff3932bba02 Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:39:17 +0000 Subject: [PATCH 1/2] t --- augurScan/src/error-chain.ts | 10 + augurScan/src/indexer-runtime.ts | 846 ++++++++ augurScan/src/indexer.ts | 1035 +--------- augurScan/src/rpc-request-queue.ts | 102 + augurScan/tests/indexer-lifecycle.test.ts | 60 + bots/liquidator/Dockerfile | 19 +- bots/liquidator/Dockerfile.dockerignore | 10 + .../liquidator/tests/docker-packaging.test.ts | 7 +- .../scripts/check-market-fixture.mts | 6 +- bots/shared/bun.lock | 5 +- bots/shared/package.json | 5 +- bots/shared/src/ethereum.ts | 38 +- bots/shared/src/ethereum/client.ts | 616 +----- bots/shared/src/ethereum/codec.ts | 799 -------- .../shared/src/ethereum/human-readable-abi.ts | 164 -- bots/shared/src/ethereum/rpc-normalization.ts | 136 -- bots/shared/src/ethereum/rpc-resilience.ts | 5 +- bots/shared/src/ethereum/rpc-transport.ts | 122 +- bots/shared/src/ethereum/types.ts | 364 ---- bots/shared/src/monitoring/connectivity.ts | 2 +- bots/shared/tests/shared-primitives.test.ts | 56 +- scripts/check-docs-reference-values.mts | 2 +- scripts/contract-reference-metadata.mts | 1801 ++++++++++++++++ ...enerate-contract-interaction-reference.mts | 1818 +---------------- shared/ts/ethereum.test.ts | 77 + shared/ts/ethereum.ts | 34 +- trading/ui/ts/features/LiveTrading.tsx | 1085 +--------- .../ui/ts/features/liveTradingController.ts | 1207 +++++++++++ 28 files changed, 4446 insertions(+), 5985 deletions(-) create mode 100644 augurScan/src/error-chain.ts create mode 100644 augurScan/src/indexer-runtime.ts create mode 100644 augurScan/src/rpc-request-queue.ts delete mode 100644 bots/shared/src/ethereum/codec.ts delete mode 100644 bots/shared/src/ethereum/human-readable-abi.ts delete mode 100644 bots/shared/src/ethereum/rpc-normalization.ts delete mode 100644 bots/shared/src/ethereum/types.ts create mode 100644 scripts/contract-reference-metadata.mts create mode 100644 trading/ui/ts/features/liveTradingController.ts diff --git a/augurScan/src/error-chain.ts b/augurScan/src/error-chain.ts new file mode 100644 index 000000000..29775b62d --- /dev/null +++ b/augurScan/src/error-chain.ts @@ -0,0 +1,10 @@ +export const errorChainIncludes = (error: unknown, names: ReadonlySet): boolean => { + const seen = new Set() + let current: unknown = error + while (typeof current === 'object' && current !== null && !seen.has(current)) { + seen.add(current) + if ('name' in current && typeof current.name === 'string' && names.has(current.name)) return true + current = 'cause' in current ? current.cause : undefined + } + return false +} diff --git a/augurScan/src/indexer-runtime.ts b/augurScan/src/indexer-runtime.ts new file mode 100644 index 000000000..36760fb1a --- /dev/null +++ b/augurScan/src/indexer-runtime.ts @@ -0,0 +1,846 @@ +import { type AddressActivity, DatabaseConsistencyError, databaseConsistencyDiagnosticMessage, type IndexerLease, type StoredTransaction } from './database.ts' +import { errorChainIncludes } from './error-chain.ts' +import { type Address, type Hash, type Log, type PublicClient, type TransactionReceipt, zeroAddress } from './ethereum.ts' +import { RpcRequestMethodError, rpcQueueSaturationFrom } from './rpc-request-queue.ts' +import type { ContractMetadata, StoredLog } from './types.ts' + +export const waitForIndexerDelay = (milliseconds: number, signal: AbortSignal): Promise => + new Promise((resolve) => { + const finish = (): void => { + clearTimeout(timeout) + signal.removeEventListener('abort', finish) + resolve() + } + const timeout = setTimeout(finish, milliseconds) + if (signal.aborted) finish() + else signal.addEventListener('abort', finish, { once: true }) + }) + +const normalizedRpcDescription = (value: string): string => + [...value] + .map((character) => { + const codePoint = character.codePointAt(0) + return codePoint !== undefined && (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) ? ' ' : character + }) + .join('') + .replace(/\p{Cf}/gu, ' ') + .replace(/\s+/gu, ' ') + .trim() + .toLowerCase() + +const withoutAnsiControlSequences = (value: string): string => { + const characters: string[] = [] + for (let index = 0; index < value.length; index++) { + if (value.codePointAt(index) === 0x1b && value[index + 1] === '[') { + index += 2 + while (index < value.length) { + const codePoint = value.codePointAt(index) + if (codePoint !== undefined && codePoint >= 0x40 && codePoint <= 0x7e) break + index++ + } + characters.push(' ') + } else characters.push(value[index] ?? '') + } + return characters.join('') +} + +const singleLineErrorDescription = (value: string): string => + [...withoutAnsiControlSequences(value)] + .map((character) => { + const codePoint = character.codePointAt(0) + return codePoint !== undefined && (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) ? ' ' : character + }) + .join('') + .replace(/\p{Cf}/gu, ' ') + .replace(/\s+/gu, ' ') + .trim() + +const classifiedRpcDescription = (value: string): string => + normalizedRpcDescription(value) + .replace(/[^\p{L}\p{N}]+/gu, ' ') + .trim() + +type RpcDescriptionCategory = 'block-range' | 'rate-limit' | 'response-size' | 'result-limit' | 'timeout' | 'too-many-logs' | 'too-many-results' + +const rpcDescriptionCategory = (value: string): RpcDescriptionCategory | undefined => { + const description = classifiedRpcDescription(value) + if ( + description.includes('rate limit') || + description.includes('too many requests') || + description.includes('request limit') || + description.includes('request rate') || + description.includes('request quota') || + description.includes('quota exceeded') || + /\bmore than\b.*\brequests?\b/u.test(description) || + /\brequests? per (?:second|minute|hour)\b/u.test(description) + ) + return 'rate-limit' + if (description.includes('too many logs') || /\bmore than\b.*\blogs\b/u.test(description)) return 'too-many-logs' + if (description.includes('too many results') || /\bmore than\b.*\bresults\b/u.test(description)) return 'too-many-results' + if (description.includes('response size') || description.includes('response too large') || description.includes('response body too large')) + return 'response-size' + if ( + description.includes('query timeout') || + description.includes('query timed out') || + description.includes('request timeout') || + description.includes('request timed out') + ) + return 'timeout' + if (description.includes('block range') || description.includes('too wide') || description.includes('please reduce')) return 'block-range' + if (description.includes('limit exceeded') || /\bexceeds? (?:the )?maximum\b/u.test(description) || description.includes('more than')) return 'result-limit' + return undefined +} + +const preferredRpcDescriptions = (value: object): readonly string[] => { + if ('details' in value && typeof value.details === 'string') return [value.details] + if ('name' in value && (value.name === 'ResponseBodyTooLargeError' || value.name === 'TimeoutError')) return [] + if ('shortMessage' in value && typeof value.shortMessage === 'string') return [value.shortMessage] + return 'message' in value && typeof value.message === 'string' ? [value.message] : [] +} + +const rpcErrorCategory = (error: unknown): RpcDescriptionCategory | undefined => { + const seen = new Set() + let firstCategory: RpcDescriptionCategory | undefined + let current: unknown = error + while (typeof current === 'object' && current !== null && !seen.has(current)) { + seen.add(current) + if ('status' in current && current.status === 429) return 'rate-limit' + for (const description of preferredRpcDescriptions(current)) { + const category = rpcDescriptionCategory(description) + if (category === 'rate-limit') return category + firstCategory ??= category + } + if ('name' in current && current.name === 'ResponseBodyTooLargeError') firstCategory ??= 'response-size' + if ('name' in current && current.name === 'TimeoutError') firstCategory ??= 'timeout' + if ('code' in current && current.code === -32005) firstCategory ??= 'result-limit' + current = 'cause' in current ? current.cause : undefined + } + return firstCategory +} + +export const isSplittableLogRangeError = (error: unknown): boolean => { + const category = rpcErrorCategory(error) + return category !== undefined && category !== 'rate-limit' +} + +export const labelsFrom = (contracts: ReadonlyMap): Map => + new Map([['0x0000000000000000000000000000000000000000', 'Zero address'], ...[...contracts].map(([address, contract]) => [address, contract.label] as const)]) + +export const jsonEvidence = (value: unknown): unknown => { + if (typeof value === 'bigint') return value.toString() + if (Array.isArray(value)) return value.map(jsonEvidence) + if (typeof value === 'object' && value !== null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonEvidence(item)])) + return value +} + +export const addressActivityFrom = ( + transactions: readonly StoredTransaction[], + logs: readonly StoredLog[], + contracts: ReadonlyMap, +): readonly AddressActivity[] => { + const result = new Map() + for (const transaction of transactions) { + const transactionLogs = logs.filter((log) => log.transactionHash === transaction.hash) + const referencedAddresses = [...(transaction.decoded.referencedAddresses ?? []), ...transactionLogs.flatMap((log) => log.decoded.referencedAddresses ?? [])] + const pools = new Set
() + if (transaction.to !== null && contracts.get(transaction.to.toLowerCase())?.kind === 'securityPool') pools.add(transaction.to) + for (const log of transactionLogs) if (contracts.get(log.address.toLowerCase())?.kind === 'securityPool') pools.add(log.address) + for (const candidate of referencedAddresses) { + if (contracts.get(candidate.toLowerCase())?.kind === 'securityPool') pools.add(candidate) + } + const participants = new Map() + participants.set(transaction.from.toLowerCase(), { address: transaction.from, role: 'sender' }) + for (const candidate of referencedAddresses) { + if (!participants.has(candidate.toLowerCase())) participants.set(candidate.toLowerCase(), { address: candidate, role: 'referenced' }) + } + const associatedPools: readonly (Address | undefined)[] = pools.size === 0 ? [undefined] : [...pools] + for (const participant of participants.values()) { + for (const poolAddress of associatedPools) { + const key = `${transaction.hash}:${participant.address.toLowerCase()}:${poolAddress?.toLowerCase() ?? zeroAddress}` + result.set(key, { + transactionHash: transaction.hash, + address: participant.address, + role: participant.role, + ...(poolAddress === undefined ? {} : { poolAddress }), + }) + } + } + } + return [...result.values()] +} + +export const requireLogPosition = (log: Log): { transactionHash: Hash; transactionIndex: number; logIndex: number; blockHash: Hash; blockNumber: bigint } => { + if ( + log.transactionHash === undefined || + log.transactionIndex === undefined || + log.logIndex === undefined || + log.blockHash === undefined || + log.blockNumber === undefined + ) { + throw new Error('RPC returned a pending log while indexing a confirmed block') + } + const transactionIndex = Number(log.transactionIndex) + const logIndex = Number(log.logIndex) + if (!Number.isSafeInteger(transactionIndex) || !Number.isSafeInteger(logIndex)) throw new Error('RPC returned a log position outside the safe integer range') + return { + transactionHash: log.transactionHash, + transactionIndex, + logIndex, + blockHash: log.blockHash, + blockNumber: log.blockNumber, + } +} + +export class ChainContinuityError extends Error {} +export class ChainConfigurationError extends Error {} +export class LeaseLostError extends Error {} + +export const queryCanonicalLogRange = async ( + throughBlock: bigint, + readEndBlockHash: () => Promise, + query: () => Promise, +): Promise<{ readonly items: readonly T[]; readonly endBlockHash: Hash }> => { + const before = await readEndBlockHash() + const items = await query() + const after = await readEndBlockHash() + if (before !== after) throw new ChainContinuityError(`Canonical chain changed while querying logs through block ${throughBlock}`) + return { items, endBlockHash: after } +} + +type ChainProvider = { readonly getChainId: () => Promise } +export type RpcProvider = ChainProvider & { readonly client: PublicClient; readonly endpoint: string; readonly number: number } + +export const rpcEndpointLabel = (rpcUrl: string): string => { + const url = new URL(rpcUrl) + const hostnameParts = url.hostname.split('.') + const isLocalOrIp = url.hostname === 'localhost' || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(url.hostname) || url.hostname.includes(':') + const hostname = !isLocalOrIp && hostnameParts.length > 2 ? `*.${hostnameParts.slice(-2).join('.')}` : url.hostname + return `${url.protocol}//${hostname}${url.port === '' ? '' : `:${url.port}`}` +} + +export const rpcProviderLabel = (rpcUrl: string, index: number): string => `#${index + 1} ${rpcEndpointLabel(rpcUrl)}` + +export const rpcFailureLogMessage = (message: string, endpoint: string, reason?: string): string => + `${message} (RPC: ${endpoint}${reason === undefined ? '' : `; reason: ${reason}`})` + +export const withVerifiedProvider = async ( + providers: readonly TProvider[], + chainId: number, + operation: (provider: TProvider) => Promise, + stopFailover = (_error: unknown): boolean => false, + onAttempt = (_provider: TProvider): void => {}, + verifiedProviders?: WeakSet, +): Promise => { + let lastFailure: unknown + for (const provider of providers) { + onAttempt(provider) + try { + if (verifiedProviders?.has(provider) !== true) { + const remoteChainId = await provider.getChainId() + if (remoteChainId !== chainId) throw new ChainConfigurationError(`RPC chain mismatch: configured ${chainId}, received ${remoteChainId}`) + verifiedProviders?.add(provider) + } + return await operation(provider) + } catch (error) { + if (stopFailover(error)) throw error + lastFailure = error + } + } + throw lastFailure ?? new ChainConfigurationError('No RPC provider is available for the configured network') +} + +export const confirmCanonicalBlock = async (number: bigint, expectedHash: Hash, lookup: (blockNumber: bigint) => Promise): Promise => { + const observedHash = await lookup(number) + if (observedHash !== expectedHash) throw new ChainContinuityError(`Block ${number} changed while it was being indexed`) +} + +export const commitCanonicalRead = async ( + number: bigint, + expectedHash: Hash, + read: () => Promise, + lookup: (blockNumber: bigint) => Promise, + commit: (value: T) => Promise, +): Promise => { + const value = await read() + await confirmCanonicalBlock(number, expectedHash, lookup) + await commit(value) +} + +export const databaseFailureMessage = 'Database request failed; retrying' +export const rpcQueueSaturatedMessage = 'RPC queue saturated; retrying' +const databaseFailureNames = new Set(['DatabaseConsistencyError', 'PostgresError']) +export const leaseFailureNames = new Set([...databaseFailureNames, 'LeaseLostError']) + +export const isLocalIndexerFailure = (error: unknown): boolean => + error instanceof LeaseLostError || rpcQueueSaturationFrom(error) !== undefined || errorChainIncludes(error, databaseFailureNames) + +export const indexingCompletion = (configuredStartBlock: bigint, indexedBlock: bigint, observedHead: bigint) => { + if (observedHead < configuredStartBlock) return { completedBlocks: 0n, percentage: '100.00', remainingBlocks: 0n, totalBlocks: 0n } + const boundedHead = observedHead + const totalBlocks = boundedHead - configuredStartBlock + 1n + const boundedIndexed = indexedBlock < configuredStartBlock ? configuredStartBlock - 1n : indexedBlock > boundedHead ? boundedHead : indexedBlock + const completedBlocks = boundedIndexed - configuredStartBlock + 1n + const remainingBlocks = totalBlocks - completedBlocks + const roundedHundredths = (completedBlocks * 10_000n + totalBlocks / 2n) / totalBlocks + const hundredths = remainingBlocks > 0n && roundedHundredths >= 10_000n ? 9_999n : roundedHundredths + return { + completedBlocks, + percentage: `${hundredths / 100n}.${String(hundredths % 100n).padStart(2, '0')}`, + remainingBlocks, + totalBlocks, + } +} + +export const compactIndexerDuration = (seconds: number): string => { + const rounded = Math.max(1, Math.ceil(seconds)) + if (rounded < 60) return `${rounded}s` + if (rounded < 3_600) return `${Math.floor(rounded / 60)}m ${rounded % 60}s` + const totalHours = Math.ceil(rounded / 3_600) + if (totalHours < 24) { + const totalMinutes = Math.ceil(rounded / 60) + const minutes = totalMinutes % 60 + return `${Math.floor(totalMinutes / 60)}h${minutes === 0 ? '' : ` ${minutes}m`}` + } + const hours = totalHours % 24 + return `${Math.floor(totalHours / 24)}d${hours === 0 ? '' : ` ${hours}h`}` +} + +export const indexerWaitingMessage = (networkId: string, configuredStartBlock: bigint, observedHead: bigint): string => + `[${networkId}] indexer state: live; observed head #${observedHead}; 100.00% complete; caught up; waiting for configured start block #${configuredStartBlock}` + +export const indexerProgressMessage = ( + networkId: string, + startBlock: bigint, + endBlock: bigint, + observedHead: bigint, + configuredStartBlock: bigint, + blocksPerSecond?: number, +): string => { + const state = endBlock >= observedHead ? 'live' : 'backfilling' + const indexed = startBlock === endBlock ? `indexed block #${endBlock}` : `indexed blocks #${startBlock}–#${endBlock}` + const completion = indexingCompletion(configuredStartBlock, endBlock, observedHead) + const progress = + state === 'live' + ? 'caught up' + : `${completion.remainingBlocks} blocks behind; ${blocksPerSecond === undefined ? 'estimating ETA' : `ETA ${compactIndexerDuration(Number(completion.remainingBlocks) / blocksPerSecond)}`}` + return `[${networkId}] indexer state: ${state}; ${indexed}; observed head #${observedHead}; ${completion.percentage}% complete; ${progress}` +} + +export const safeIndexerFailure = (error: unknown): string => { + if (error instanceof ChainConfigurationError) return error.message + if (error instanceof ChainContinuityError) return 'The remote canonical chain changed while indexing; retrying' + if (rpcQueueSaturationFrom(error) !== undefined) return rpcQueueSaturatedMessage + if (errorChainIncludes(error, databaseFailureNames)) return databaseFailureMessage + return 'RPC request failed; retrying' +} + +const safeErrorNames = new Set([ + 'AbortError', + 'ChainConfigurationError', + 'ChainContinuityError', + 'ConnectTimeoutError', + 'ContractFunctionExecutionError', + 'ContractFunctionRevertedError', + 'DatabaseConsistencyError', + 'Error', + 'HeadersTimeoutError', + 'HttpRequestError', + 'IndexerOwnershipStageError', + 'LeaseLostError', + 'LimitExceededRpcError', + 'PostgresError', + 'ResponseBodyTooLargeError', + 'ResourceUnavailableRpcError', + 'RpcQueueSaturatedError', + 'RpcRequestError', + 'SocketError', + 'TimeoutError', + 'TypeError', + 'UnknownNodeError', + 'UnknownRpcError', +]) + +const safeErrorIdentifier = (value: unknown): string | undefined => (typeof value === 'string' && safeErrorNames.has(value) ? value : undefined) + +const safeNamedErrorCodes = new Set(['ECONNREFUSED', 'ECONNRESET', 'ENETUNREACH', 'ENOTFOUND', 'ETIMEDOUT', 'ERR_POSTGRES_CONNECTION_CLOSED']) + +const safeErrorCode = (value: unknown): string | undefined => { + if ( + typeof value === 'number' && + Number.isSafeInteger(value) && + (value === -32700 || (value >= -32603 && value <= -32600) || (value >= -32099 && value <= -32000)) + ) + return value.toString() + return typeof value === 'string' && (/^HTTP_[1-5][0-9]{2}$/.test(value) || safeNamedErrorCodes.has(value)) ? value : undefined +} + +const safeStandardRpcMessages = new Map([ + ['parse error', 'Parse error'], + ['invalid request', 'Invalid Request'], + ['method not found', 'Method not found'], + ['invalid params', 'Invalid params'], + ['internal error', 'Internal error'], +]) + +const safeRpcCategoryMessages: Readonly> = { + 'block-range': 'provider rejected the requested block range', + 'rate-limit': 'provider rate limit exceeded', + 'response-size': 'provider response size limit exceeded', + 'result-limit': 'provider result limit exceeded', + timeout: 'provider request timed out', + 'too-many-logs': 'provider returned too many logs', + 'too-many-results': 'provider returned too many results', +} + +const safeStandardRpcProviderMessage = (value: unknown): string | undefined => { + if (typeof value !== 'string') return undefined + const normalized = normalizedRpcDescription(value) + return safeStandardRpcMessages.get(normalized.replace(/[.!]$/u, '')) +} + +const safeRpcRequestMethod = (value: unknown): string | undefined => + typeof value === 'string' && /^(?:eth|net|web3)_[A-Za-z0-9_]+$/u.test(value) ? value : undefined + +const rpcRequestMethodFrom = (error: unknown): string | undefined => { + const seen = new Set() + let current: unknown = error + while (typeof current === 'object' && current !== null && !seen.has(current)) { + seen.add(current) + const method = current instanceof RpcRequestMethodError ? safeRpcRequestMethod(current.method) : undefined + if (method !== undefined) return method + current = 'cause' in current ? current.cause : undefined + } + return undefined +} + +const indexerFailureReason = (error: unknown, includeErrorDescriptions: boolean): string => { + const saturation = rpcQueueSaturationFrom(error) + if (saturation !== undefined) + return `RpcQueueSaturatedError; active ${saturation.active}; queued ${saturation.pending}; maximum queued ${saturation.maximumPending}; high-water mark ${saturation.highWaterMark}; saturation count ${saturation.saturationCount}` + const names: string[] = [] + const descriptions: string[] = [] + let status: number | undefined + let code: string | undefined + let standardMessage: string | undefined + let previousDescriptionName: string | undefined + let previousDescriptionMessage: string | undefined + const seen = new Set() + let current: unknown = error + while (current !== undefined && !seen.has(current)) { + seen.add(current) + if (typeof current !== 'object' || current === null) { + descriptions.push(`UnknownError: ${singleLineErrorDescription(String(current))}`) + break + } + const actualName = 'name' in current && typeof current.name === 'string' ? singleLineErrorDescription(current.name) || undefined : undefined + const name = safeErrorIdentifier(actualName) + if (name !== undefined && names.at(-1) !== name) names.push(name) + const actualMessage = + singleLineErrorDescription( + preferredRpcDescriptions(current) + .find((value) => value.trim() !== '') + ?.trim() ?? '', + ) || undefined + if (actualName !== undefined || actualMessage !== undefined) { + if (actualMessage !== undefined && actualMessage === previousDescriptionMessage) { + if (actualName !== undefined && actualName !== previousDescriptionName) descriptions.push(actualName) + } else { + const descriptionName = actualName ?? 'UnknownError' + descriptions.push(actualMessage === undefined ? descriptionName : `${descriptionName}: ${actualMessage}`) + } + previousDescriptionName = actualName + previousDescriptionMessage = actualMessage + } + if ( + status === undefined && + 'status' in current && + typeof current.status === 'number' && + Number.isInteger(current.status) && + current.status >= 100 && + current.status <= 599 + ) + status = current.status + if (code === undefined && 'code' in current) code = safeErrorCode(current.code) + if (standardMessage === undefined && name === 'RpcRequestError' && 'details' in current) standardMessage = safeStandardRpcProviderMessage(current.details) + current = 'cause' in current ? current.cause : undefined + } + const category = rpcErrorCategory(error) + const message = category === undefined ? standardMessage : safeRpcCategoryMessages[category] + const fallbackDescription = descriptions.length === 0 ? 'UnknownError' : descriptions.slice(0, 4).join(' caused by ') + const details = [ + includeErrorDescriptions && message === undefined ? fallbackDescription : names.length === 0 ? 'UnknownError' : names.slice(0, 4).join(' caused by '), + ] + const method = rpcRequestMethodFrom(error) + if (method !== undefined) details.push(`method ${method}`) + if (status !== undefined) details.push(`HTTP ${status}`) + if (code !== undefined) details.push(`code ${code}`) + if (message !== undefined) details.push(`message: ${message}`) + return details.join('; ') +} + +export const safeIndexerFailureReason = (error: unknown): string => indexerFailureReason(error, false) + +export const rpcIndexerFailureReason = (error: unknown): string => indexerFailureReason(error, true) + +const rpcFailureReason = (error: unknown, rpcNumber: number): string => `RPC #${rpcNumber}: ${rpcIndexerFailureReason(error)}` + +type RpcDiagnosticProvider = Pick + +export const createRpcDiagnosticContext = (initialProvider: RpcDiagnosticProvider) => { + let activeProvider = initialProvider + return { + activeEndpoint: (): string => activeProvider.endpoint, + activeNumber: (): number => activeProvider.number, + failureReason: (error: unknown): string => rpcFailureReason(error, activeProvider.number), + select: (provider: RpcDiagnosticProvider): void => { + activeProvider = provider + }, + } +} + +export const indexerOperationFailureReason = (error: unknown, rpcNumber: number, source: 'rpc' | 'storage'): string => + source === 'rpc' ? rpcFailureReason(error, rpcNumber) : safeIndexerFailureReason(error) + +const deploymentReadTimeoutError = (): Error => { + const error = new Error('Contract deployment history read timed out') + error.name = 'TimeoutError' + return error +} + +export const boundedDeploymentRead = async (read: () => Promise, timeoutMs: number): Promise => + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(deploymentReadTimeoutError()) + }, timeoutMs) + void read() + .then(resolve, reject) + .finally(() => clearTimeout(timeout)) + }) + +export const deploymentReadBudget = (timeoutMs = 5_000, now = Date.now): ((read: () => Promise) => Promise) => { + const deadline = now() + timeoutMs + return async (read: () => Promise): Promise => { + const remaining = deadline - now() + if (remaining <= 0) throw deploymentReadTimeoutError() + const value = await boundedDeploymentRead(read, remaining) + if (now() > deadline) throw deploymentReadTimeoutError() + return value + } +} + +export const contractDeploymentScanDue = (lastCompletedAt: number | undefined, now: number, cooldownMs = 60_000): boolean => + lastCompletedAt === undefined || now - lastCompletedAt >= cooldownMs + +type NetworkLifecycle = { + readonly verify: () => Promise + readonly poll: () => Promise + readonly failure: (message: string, nextRetryAt: Date, reason: string) => Promise + readonly intervalMs: number + readonly signal: AbortSignal + readonly random?: () => number + readonly shouldRethrow?: (error: unknown) => boolean +} + +export class IndexerOwnershipStageError extends Error { + override name = 'IndexerOwnershipStageError' + + constructor( + readonly stage: OwnershipStage, + cause: unknown, + ) { + super(`Indexer ownership stage failed: ${stage}`, { cause }) + } +} + +export const retryDelayMs = (consecutiveFailures: number, intervalMs: number, random = Math.random): number => { + const exponent = Math.min(Math.max(consecutiveFailures - 1, 0), 8) + const base = Math.min(intervalMs * 2 ** exponent, 300_000) + return Math.min(Math.round(base * (0.8 + random() * 0.4)), 300_000) +} + +export const runNetworkLifecycle = async ({ verify, poll, failure, intervalMs, signal, random, shouldRethrow }: NetworkLifecycle): Promise => { + let verified = false + let consecutiveFailures = 0 + while (!signal.aborted) { + const startedAt = Date.now() + let caughtUp = true + let delayAfterFailure: number | undefined + try { + if (!verified) { + await verify() + verified = true + } + caughtUp = await poll() + consecutiveFailures = 0 + } catch (error) { + if (error instanceof LeaseLostError || shouldRethrow?.(error) === true) throw error + consecutiveFailures++ + delayAfterFailure = retryDelayMs(consecutiveFailures, intervalMs, random) + try { + const failureMessage = safeIndexerFailure(error) + const failureReason = failureMessage === 'RPC request failed; retrying' ? rpcIndexerFailureReason(error) : safeIndexerFailureReason(error) + await failure(failureMessage, new Date(Date.now() + delayAfterFailure), failureReason) + } catch (failureError) { + throw new IndexerOwnershipStageError('record-failure', failureError) + } + } + await waitForIndexerDelay(delayAfterFailure ?? (caughtUp ? Math.max(0, intervalMs - (Date.now() - startedAt)) : 0), signal) + } +} + +type OwnedNetworkLifecycle = Omit & { + readonly reconcile: () => Promise + readonly poll: () => Promise + readonly runWithProvider: (operation: () => Promise) => Promise +} + +export const runOwnedNetworkLifecycle = async ({ reconcile, poll, runWithProvider, ...lifecycle }: OwnedNetworkLifecycle): Promise => + await runNetworkLifecycle({ + ...lifecycle, + verify: () => runWithProvider(reconcile), + poll: () => runWithProvider(poll), + shouldRethrow: (error) => error instanceof DatabaseConsistencyError || lifecycle.shouldRethrow?.(error) === true, + }) + +type LeaseControl = Pick & { readonly backendPid?: number } + +type OwnershipStage = 'acquire' | 'verify' | 'seed' | 'owned-run' | 'record-failure' | 'release' + +export type IndexerOwnershipEvent = + | { + readonly type: 'failure' + readonly stage: OwnershipStage + readonly consecutiveFailures: number + readonly retryDelayMs: number + readonly backendPid?: number + } + | { readonly type: 'acquired'; readonly backendPid?: number; readonly recoveredAfterFailures: number; readonly acquiredAfterStandby: boolean } + | { readonly type: 'released'; readonly backendPid?: number } + | { readonly type: 'standby' } + +export type IndexerOwnershipStatus = { + readonly networkId: string + readonly active: boolean + readonly backendPid?: number + readonly failuresTotal: number + readonly reacquisitionsTotal: number + readonly consecutiveFailures: number + readonly lastFailureAt?: string + readonly lastFailureStage?: OwnershipStage +} + +const ownershipStatuses = new Map() + +export const nextIndexerOwnershipStatus = ( + networkId: string, + current: IndexerOwnershipStatus | undefined, + event: IndexerOwnershipEvent, + now = new Date(), +): IndexerOwnershipStatus => { + const previous = current ?? { + networkId, + active: false, + failuresTotal: 0, + reacquisitionsTotal: 0, + consecutiveFailures: 0, + } + if (event.type === 'failure') { + return { + ...previous, + active: false, + ...(event.backendPid === undefined ? {} : { backendPid: event.backendPid }), + failuresTotal: previous.failuresTotal + 1, + consecutiveFailures: event.consecutiveFailures, + lastFailureAt: now.toISOString(), + lastFailureStage: event.stage, + } + } + if (event.type === 'acquired') { + return { + ...previous, + active: true, + ...(event.backendPid === undefined ? {} : { backendPid: event.backendPid }), + reacquisitionsTotal: previous.reacquisitionsTotal + (event.recoveredAfterFailures > 0 || event.acquiredAfterStandby ? 1 : 0), + consecutiveFailures: 0, + } + } + return { + ...previous, + active: false, + backendPid: undefined, + consecutiveFailures: event.type === 'standby' ? 0 : previous.consecutiveFailures, + } +} + +export const recordOwnershipEvent = (networkId: string, event: IndexerOwnershipEvent): void => { + ownershipStatuses.set(networkId, nextIndexerOwnershipStatus(networkId, ownershipStatuses.get(networkId), event)) +} + +export const indexerOwnershipStatuses = (): readonly IndexerOwnershipStatus[] => + [...ownershipStatuses.values()].sort((left, right) => left.networkId.localeCompare(right.networkId)) + +const ownershipFailureReason = (error: unknown): string => { + const reason = safeIndexerFailureReason(error) + const seen = new Set() + let current: unknown = error + while (typeof current === 'object' && current !== null && !seen.has(current)) { + seen.add(current) + if (current instanceof DatabaseConsistencyError) { + const detail = databaseConsistencyDiagnosticMessage(current) + if (detail !== undefined) return `${reason}: ${detail}` + } + current = 'cause' in current ? current.cause : undefined + } + return reason +} + +export const ownershipFailureLogMessage = ( + networkId: string, + stage: OwnershipStage, + error: unknown, + consecutiveFailures: number, + retryDelay: number, + backendPid?: number, +): string => + `[${networkId}] indexer ownership failed; stage: ${stage}; consecutive failures: ${consecutiveFailures}; retry delay: ${retryDelay}ms; backend PID: ${backendPid ?? 'unavailable'}; reason: ${ownershipFailureReason(error)}` + +type OwnershipLifecycle = { + readonly networkId: string + readonly acquire: () => Promise + readonly seed: (lease: TLease) => Promise + readonly runOwned: (lease: TLease) => Promise + readonly failure: (message: string, lease: TLease | undefined) => Promise + readonly standby: () => void + readonly intervalMs: number + readonly now?: () => number + readonly onEvent?: (event: IndexerOwnershipEvent) => void + readonly random?: () => number + readonly wait?: (milliseconds: number, signal: AbortSignal) => Promise + readonly signal: AbortSignal +} + +export const runIndexerOwnershipLifecycle = async ({ + networkId, + acquire, + seed, + runOwned, + failure, + standby, + intervalMs, + now = Date.now, + onEvent = () => {}, + random, + wait = waitForIndexerDelay, + signal, +}: OwnershipLifecycle): Promise => { + let standbyReported = false + let wasStandby = false + let consecutiveFailures = 0 + while (!signal.aborted) { + let lease: TLease | undefined + let ownedRunStartedAt: number | undefined + let stage: OwnershipStage = 'acquire' + let retryDelay: number | undefined + try { + lease = await acquire() + if (lease === undefined) { + consecutiveFailures = 0 + wasStandby = true + if (!standbyReported) { + standby() + onEvent({ type: 'standby' }) + standbyReported = true + } + } else { + standbyReported = false + stage = 'verify' + await lease.assertHeld() + stage = 'seed' + await seed(lease) + const recoveredAfterFailures = consecutiveFailures + const acquiredAfterStandby = wasStandby + onEvent({ + type: 'acquired', + ...(lease.backendPid === undefined ? {} : { backendPid: lease.backendPid }), + recoveredAfterFailures, + acquiredAfterStandby, + }) + if (recoveredAfterFailures > 0 || acquiredAfterStandby) { + const source = acquiredAfterStandby ? (recoveredAfterFailures > 0 ? 'standby and failures' : 'standby') : 'failures' + console.info( + `[${networkId}] indexer ownership reacquired; backend PID: ${lease.backendPid ?? 'unavailable'}; source: ${source}; previous consecutive failures: ${recoveredAfterFailures}`, + ) + } + wasStandby = false + stage = 'owned-run' + ownedRunStartedAt = now() + await runOwned(lease) + consecutiveFailures = 0 + } + } catch (error) { + const failureStage = error instanceof IndexerOwnershipStageError ? error.stage : stage + if (failureStage === 'owned-run' && ownedRunStartedAt !== undefined && now() - ownedRunStartedAt >= Math.max(intervalMs * 4, 60_000)) + consecutiveFailures = 0 + consecutiveFailures++ + retryDelay = retryDelayMs(consecutiveFailures, intervalMs, random) + onEvent({ + type: 'failure', + stage: failureStage, + consecutiveFailures, + retryDelayMs: retryDelay, + ...(lease?.backendPid === undefined ? {} : { backendPid: lease.backendPid }), + }) + console.error(ownershipFailureLogMessage(networkId, failureStage, error, consecutiveFailures, retryDelay, lease?.backendPid)) + try { + await failure(databaseFailureMessage, lease) + } catch (error) { + console.error(ownershipFailureLogMessage(networkId, 'record-failure', error, consecutiveFailures, retryDelay, lease?.backendPid)) + // A database outage can prevent status recording too; retry ownership regardless. + } + } finally { + try { + await lease?.release() + } catch (error) { + if (retryDelay === undefined) { + consecutiveFailures++ + retryDelay = retryDelayMs(consecutiveFailures, intervalMs, random) + onEvent({ + type: 'failure', + stage: 'release', + consecutiveFailures, + retryDelayMs: retryDelay, + ...(lease?.backendPid === undefined ? {} : { backendPid: lease.backendPid }), + }) + } + console.error(ownershipFailureLogMessage(networkId, 'release', error, consecutiveFailures, retryDelay, lease?.backendPid)) + // PostgreSQL already releases advisory locks when their session is lost. + } + if (lease !== undefined) onEvent({ type: 'released', ...(lease.backendPid === undefined ? {} : { backendPid: lease.backendPid }) }) + } + if (!signal.aborted) await wait(retryDelay ?? intervalMs, signal) + } +} + +export const isProtocolActivitySource = (contract: ContractMetadata | undefined): boolean => + contract !== undefined && + contract.kind !== 'weth' && + contract.kind !== 'reputationToken' && + contract.kind !== 'multicall3' && + contract.kind !== 'proxyDeployer' + +export const requiresManifestHistoryCoverage = (contract: ContractMetadata | undefined): boolean => + isProtocolActivitySource(contract) || contract?.kind === 'reputationToken' || contract?.kind === 'weth' + +export const isProtocolEvidenceEmitter = (contract: ContractMetadata | undefined): contract is ContractMetadata => contract !== undefined + +export const requireReceiptPosition = (receipt: TransactionReceipt, blockHash: Hash, blockNumber: bigint): void => { + if (receipt.blockHash !== blockHash || receipt.blockNumber !== blockNumber) { + throw new ChainContinuityError(`Receipt ${receipt.transactionHash} no longer belongs to block ${blockNumber}`) + } + for (const log of receipt.logs) { + const position = requireLogPosition(log) + if (position.blockHash !== blockHash || position.blockNumber !== blockNumber) { + throw new ChainContinuityError(`Log ${position.transactionHash}:${position.logIndex} no longer belongs to block ${blockNumber}`) + } + } +} diff --git a/augurScan/src/indexer.ts b/augurScan/src/indexer.ts index 1b10c613a..dfe998d73 100644 --- a/augurScan/src/indexer.ts +++ b/augurScan/src/indexer.ts @@ -1,9 +1,86 @@ import { runtimeConfig } from './config.ts' +import { errorChainIncludes } from './error-chain.ts' +import { + addressActivityFrom, + ChainConfigurationError, + ChainContinuityError, + commitCanonicalRead, + confirmCanonicalBlock, + contractDeploymentScanDue, + createRpcDiagnosticContext, + databaseFailureMessage, + deploymentReadBudget, + indexerOperationFailureReason, + indexerProgressMessage, + indexerWaitingMessage, + indexingCompletion, + isLocalIndexerFailure, + isProtocolActivitySource, + isProtocolEvidenceEmitter, + isSplittableLogRangeError, + jsonEvidence, + LeaseLostError, + labelsFrom, + leaseFailureNames, + queryCanonicalLogRange, + type RpcProvider, + recordOwnershipEvent, + requireLogPosition, + requireReceiptPosition, + requiresManifestHistoryCoverage, + rpcFailureLogMessage, + rpcProviderLabel, + rpcQueueSaturatedMessage, + runIndexerOwnershipLifecycle, + runOwnedNetworkLifecycle, + safeIndexerFailure, + withVerifiedProvider, +} from './indexer-runtime.ts' +import { createRpcRequestQueue, rpcQueueSaturationFrom, withRpcRequestQueue } from './rpc-request-queue.ts' + +export { + addressActivityFrom, + boundedDeploymentRead, + commitCanonicalRead, + compactIndexerDuration, + confirmCanonicalBlock, + contractDeploymentScanDue, + createRpcDiagnosticContext, + deploymentReadBudget, + type IndexerOwnershipEvent, + IndexerOwnershipStageError, + type IndexerOwnershipStatus, + indexerOperationFailureReason, + indexerOwnershipStatuses, + indexerProgressMessage, + indexerWaitingMessage, + indexingCompletion, + isLocalIndexerFailure, + isProtocolActivitySource, + isProtocolEvidenceEmitter, + isSplittableLogRangeError, + nextIndexerOwnershipStatus, + ownershipFailureLogMessage, + queryCanonicalLogRange, + requiresManifestHistoryCoverage, + retryDelayMs, + rpcEndpointLabel, + rpcFailureLogMessage, + rpcIndexerFailureReason, + rpcProviderLabel, + runIndexerOwnershipLifecycle, + runNetworkLifecycle, + runOwnedNetworkLifecycle, + safeIndexerFailure, + safeIndexerFailureReason, + waitForIndexerDelay, + withVerifiedProvider, +} from './indexer-runtime.ts' +export { createRpcRequestQueue, RpcQueueSaturatedError, withRpcRequestQueue } from './rpc-request-queue.ts' + import { - type AddressActivity, type ContractDeploymentObservation, DatabaseConsistencyError, - databaseConsistencyDiagnosticMessage, type IndexedBlock, type IndexerLease, type LogScanCursor, @@ -25,7 +102,6 @@ import { parseAbiItem, type Transaction, type TransactionReceipt, - type Transport, zeroAddress, } from './ethereum.ts' import { decodeAction, decodeLogRecord, discoveriesFrom, tokenAddressesFrom } from './metadata.ts' @@ -89,17 +165,6 @@ type TokenMetadataCalls = { const unavailableMetadataErrors = new Set(['AbiDecodingError', 'ContractFunctionRevertedError', 'ContractFunctionZeroDataError']) -const errorChainIncludes = (error: unknown, names: ReadonlySet): boolean => { - const seen = new Set() - let current: unknown = error - while (typeof current === 'object' && current !== null && !seen.has(current)) { - seen.add(current) - if ('name' in current && typeof current.name === 'string' && names.has(current.name)) return true - current = 'cause' in current ? current.cause : undefined - } - return false -} - const isUnavailableMetadataCall = (error: unknown): boolean => errorChainIncludes(error, unavailableMetadataErrors) const metadataCall = async (call: () => Promise): Promise => { @@ -119,18 +184,6 @@ export const readTokenMetadata = async (address: Address, blockNumber: bigint, c return { address, decimals, ...(name === undefined ? {} : { name }), ...(symbol === undefined ? {} : { symbol }), readBlock: blockNumber } } -export const waitForIndexerDelay = (milliseconds: number, signal: AbortSignal): Promise => - new Promise((resolve) => { - const finish = (): void => { - clearTimeout(timeout) - signal.removeEventListener('abort', finish) - resolve() - } - const timeout = setTimeout(finish, milliseconds) - if (signal.aborted) finish() - else signal.addEventListener('abort', finish, { once: true }) - }) - export const findContractDeploymentBlock = async ( startBlock: bigint, observedHead: bigint, @@ -159,107 +212,6 @@ const chunks = (items: readonly T[], size: number): T[][] => { return result } -type RpcRequestQueue = { - readonly run: (operation: () => Promise) => Promise -} - -class RpcRequestMethodError extends Error { - override name = 'RpcRequestMethodError' - - constructor( - readonly method: string, - cause: unknown, - ) { - super('RPC method failed', { cause }) - } -} - -type RpcQueueSaturation = { - readonly active: number - readonly pending: number - readonly maximumPending: number - readonly highWaterMark: number - readonly saturationCount: number -} - -export class RpcQueueSaturatedError extends Error { - readonly active: number - readonly pending: number - readonly maximumPending: number - readonly highWaterMark: number - readonly saturationCount: number - - constructor(status: RpcQueueSaturation) { - super('RPC queue reached its pending capacity') - this.name = 'RpcQueueSaturatedError' - this.active = status.active - this.pending = status.pending - this.maximumPending = status.maximumPending - this.highWaterMark = status.highWaterMark - this.saturationCount = status.saturationCount - } -} - -const rpcQueueSaturationFrom = (error: unknown): RpcQueueSaturatedError | undefined => { - const seen = new Set() - let current: unknown = error - while (typeof current === 'object' && current !== null && !seen.has(current)) { - seen.add(current) - if (current instanceof RpcQueueSaturatedError) return current - current = 'cause' in current ? current.cause : undefined - } - return undefined -} - -export const createRpcRequestQueue = (concurrency: number, maximumPending = RPC_MAX_PENDING): RpcRequestQueue => { - if (!Number.isSafeInteger(concurrency) || concurrency < 1) throw new Error('RPC concurrency must be a positive safe integer') - if (!Number.isSafeInteger(maximumPending) || maximumPending < 0) throw new Error('RPC maximum pending count must be a non-negative safe integer') - let active = 0 - let highWaterMark = 0 - let saturationCount = 0 - const pending: Array<() => void> = [] - const drain = (): void => { - while (active < concurrency) { - const start = pending.shift() - if (start === undefined) return - active++ - start() - } - } - return { - run: (operation: () => Promise) => { - if (active >= concurrency && pending.length >= maximumPending) { - saturationCount++ - return Promise.reject(new RpcQueueSaturatedError({ active, pending: pending.length, maximumPending, highWaterMark, saturationCount })) - } - return new Promise((resolve, reject) => { - pending.push(() => { - void Promise.resolve() - .then(operation) - .then(resolve, reject) - .finally(() => { - active-- - drain() - }) - }) - drain() - highWaterMark = Math.max(highWaterMark, pending.length) - }) - }, - } -} - -export const withRpcRequestQueue = (transport: Transport, queue: RpcRequestQueue): Transport => ({ - ...transport, - requestScheduler: async (method: string, operation: () => Promise): Promise => { - try { - return await queue.run(() => (transport.requestScheduler === undefined ? operation() : transport.requestScheduler(method, operation))) - } catch (error) { - throw new RpcRequestMethodError(method, error) - } - }, -}) - const rpcRequestQueue = createRpcRequestQueue(RPC_CONCURRENCY, RPC_MAX_PENDING) export const rpcLogAddressGroups = (addresses: readonly T[]): readonly T[][] => chunks(addresses, 5) @@ -449,835 +401,6 @@ export const queryAdaptiveLogRange = async ( } } -const normalizedRpcDescription = (value: string): string => - [...value] - .map((character) => { - const codePoint = character.codePointAt(0) - return codePoint !== undefined && (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) ? ' ' : character - }) - .join('') - .replace(/\p{Cf}/gu, ' ') - .replace(/\s+/gu, ' ') - .trim() - .toLowerCase() - -const withoutAnsiControlSequences = (value: string): string => { - const characters: string[] = [] - for (let index = 0; index < value.length; index++) { - if (value.codePointAt(index) === 0x1b && value[index + 1] === '[') { - index += 2 - while (index < value.length) { - const codePoint = value.codePointAt(index) - if (codePoint !== undefined && codePoint >= 0x40 && codePoint <= 0x7e) break - index++ - } - characters.push(' ') - } else characters.push(value[index] ?? '') - } - return characters.join('') -} - -const singleLineErrorDescription = (value: string): string => - [...withoutAnsiControlSequences(value)] - .map((character) => { - const codePoint = character.codePointAt(0) - return codePoint !== undefined && (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) ? ' ' : character - }) - .join('') - .replace(/\p{Cf}/gu, ' ') - .replace(/\s+/gu, ' ') - .trim() - -const classifiedRpcDescription = (value: string): string => - normalizedRpcDescription(value) - .replace(/[^\p{L}\p{N}]+/gu, ' ') - .trim() - -type RpcDescriptionCategory = 'block-range' | 'rate-limit' | 'response-size' | 'result-limit' | 'timeout' | 'too-many-logs' | 'too-many-results' - -const rpcDescriptionCategory = (value: string): RpcDescriptionCategory | undefined => { - const description = classifiedRpcDescription(value) - if ( - description.includes('rate limit') || - description.includes('too many requests') || - description.includes('request limit') || - description.includes('request rate') || - description.includes('request quota') || - description.includes('quota exceeded') || - /\bmore than\b.*\brequests?\b/u.test(description) || - /\brequests? per (?:second|minute|hour)\b/u.test(description) - ) - return 'rate-limit' - if (description.includes('too many logs') || /\bmore than\b.*\blogs\b/u.test(description)) return 'too-many-logs' - if (description.includes('too many results') || /\bmore than\b.*\bresults\b/u.test(description)) return 'too-many-results' - if (description.includes('response size') || description.includes('response too large') || description.includes('response body too large')) - return 'response-size' - if ( - description.includes('query timeout') || - description.includes('query timed out') || - description.includes('request timeout') || - description.includes('request timed out') - ) - return 'timeout' - if (description.includes('block range') || description.includes('too wide') || description.includes('please reduce')) return 'block-range' - if (description.includes('limit exceeded') || /\bexceeds? (?:the )?maximum\b/u.test(description) || description.includes('more than')) return 'result-limit' - return undefined -} - -const preferredRpcDescriptions = (value: object): readonly string[] => { - if ('details' in value && typeof value.details === 'string') return [value.details] - if ('name' in value && (value.name === 'ResponseBodyTooLargeError' || value.name === 'TimeoutError')) return [] - if ('shortMessage' in value && typeof value.shortMessage === 'string') return [value.shortMessage] - return 'message' in value && typeof value.message === 'string' ? [value.message] : [] -} - -const rpcErrorCategory = (error: unknown): RpcDescriptionCategory | undefined => { - const seen = new Set() - let firstCategory: RpcDescriptionCategory | undefined - let current: unknown = error - while (typeof current === 'object' && current !== null && !seen.has(current)) { - seen.add(current) - if ('status' in current && current.status === 429) return 'rate-limit' - for (const description of preferredRpcDescriptions(current)) { - const category = rpcDescriptionCategory(description) - if (category === 'rate-limit') return category - firstCategory ??= category - } - if ('name' in current && current.name === 'ResponseBodyTooLargeError') firstCategory ??= 'response-size' - if ('name' in current && current.name === 'TimeoutError') firstCategory ??= 'timeout' - if ('code' in current && current.code === -32005) firstCategory ??= 'result-limit' - current = 'cause' in current ? current.cause : undefined - } - return firstCategory -} - -export const isSplittableLogRangeError = (error: unknown): boolean => { - const category = rpcErrorCategory(error) - return category !== undefined && category !== 'rate-limit' -} - -const labelsFrom = (contracts: ReadonlyMap): Map => - new Map([['0x0000000000000000000000000000000000000000', 'Zero address'], ...[...contracts].map(([address, contract]) => [address, contract.label] as const)]) - -const jsonEvidence = (value: unknown): unknown => { - if (typeof value === 'bigint') return value.toString() - if (Array.isArray(value)) return value.map(jsonEvidence) - if (typeof value === 'object' && value !== null) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonEvidence(item)])) - return value -} - -export const addressActivityFrom = ( - transactions: readonly StoredTransaction[], - logs: readonly StoredLog[], - contracts: ReadonlyMap, -): readonly AddressActivity[] => { - const result = new Map() - for (const transaction of transactions) { - const transactionLogs = logs.filter((log) => log.transactionHash === transaction.hash) - const referencedAddresses = [...(transaction.decoded.referencedAddresses ?? []), ...transactionLogs.flatMap((log) => log.decoded.referencedAddresses ?? [])] - const pools = new Set
() - if (transaction.to !== null && contracts.get(transaction.to.toLowerCase())?.kind === 'securityPool') pools.add(transaction.to) - for (const log of transactionLogs) if (contracts.get(log.address.toLowerCase())?.kind === 'securityPool') pools.add(log.address) - for (const candidate of referencedAddresses) { - if (contracts.get(candidate.toLowerCase())?.kind === 'securityPool') pools.add(candidate) - } - const participants = new Map() - participants.set(transaction.from.toLowerCase(), { address: transaction.from, role: 'sender' }) - for (const candidate of referencedAddresses) { - if (!participants.has(candidate.toLowerCase())) participants.set(candidate.toLowerCase(), { address: candidate, role: 'referenced' }) - } - const associatedPools: readonly (Address | undefined)[] = pools.size === 0 ? [undefined] : [...pools] - for (const participant of participants.values()) { - for (const poolAddress of associatedPools) { - const key = `${transaction.hash}:${participant.address.toLowerCase()}:${poolAddress?.toLowerCase() ?? zeroAddress}` - result.set(key, { - transactionHash: transaction.hash, - address: participant.address, - role: participant.role, - ...(poolAddress === undefined ? {} : { poolAddress }), - }) - } - } - } - return [...result.values()] -} - -const requireLogPosition = (log: Log): { transactionHash: Hash; transactionIndex: number; logIndex: number; blockHash: Hash; blockNumber: bigint } => { - if ( - log.transactionHash === undefined || - log.transactionIndex === undefined || - log.logIndex === undefined || - log.blockHash === undefined || - log.blockNumber === undefined - ) { - throw new Error('RPC returned a pending log while indexing a confirmed block') - } - const transactionIndex = Number(log.transactionIndex) - const logIndex = Number(log.logIndex) - if (!Number.isSafeInteger(transactionIndex) || !Number.isSafeInteger(logIndex)) throw new Error('RPC returned a log position outside the safe integer range') - return { - transactionHash: log.transactionHash, - transactionIndex, - logIndex, - blockHash: log.blockHash, - blockNumber: log.blockNumber, - } -} - -class ChainContinuityError extends Error {} -class ChainConfigurationError extends Error {} -class LeaseLostError extends Error {} - -export const queryCanonicalLogRange = async ( - throughBlock: bigint, - readEndBlockHash: () => Promise, - query: () => Promise, -): Promise<{ readonly items: readonly T[]; readonly endBlockHash: Hash }> => { - const before = await readEndBlockHash() - const items = await query() - const after = await readEndBlockHash() - if (before !== after) throw new ChainContinuityError(`Canonical chain changed while querying logs through block ${throughBlock}`) - return { items, endBlockHash: after } -} - -type ChainProvider = { readonly getChainId: () => Promise } -type RpcProvider = ChainProvider & { readonly client: PublicClient; readonly endpoint: string; readonly number: number } - -export const rpcEndpointLabel = (rpcUrl: string): string => { - const url = new URL(rpcUrl) - const hostnameParts = url.hostname.split('.') - const isLocalOrIp = url.hostname === 'localhost' || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(url.hostname) || url.hostname.includes(':') - const hostname = !isLocalOrIp && hostnameParts.length > 2 ? `*.${hostnameParts.slice(-2).join('.')}` : url.hostname - return `${url.protocol}//${hostname}${url.port === '' ? '' : `:${url.port}`}` -} - -export const rpcProviderLabel = (rpcUrl: string, index: number): string => `#${index + 1} ${rpcEndpointLabel(rpcUrl)}` - -export const rpcFailureLogMessage = (message: string, endpoint: string, reason?: string): string => - `${message} (RPC: ${endpoint}${reason === undefined ? '' : `; reason: ${reason}`})` - -export const withVerifiedProvider = async ( - providers: readonly TProvider[], - chainId: number, - operation: (provider: TProvider) => Promise, - stopFailover = (_error: unknown): boolean => false, - onAttempt = (_provider: TProvider): void => {}, - verifiedProviders?: WeakSet, -): Promise => { - let lastFailure: unknown - for (const provider of providers) { - onAttempt(provider) - try { - if (verifiedProviders?.has(provider) !== true) { - const remoteChainId = await provider.getChainId() - if (remoteChainId !== chainId) throw new ChainConfigurationError(`RPC chain mismatch: configured ${chainId}, received ${remoteChainId}`) - verifiedProviders?.add(provider) - } - return await operation(provider) - } catch (error) { - if (stopFailover(error)) throw error - lastFailure = error - } - } - throw lastFailure ?? new ChainConfigurationError('No RPC provider is available for the configured network') -} - -export const confirmCanonicalBlock = async (number: bigint, expectedHash: Hash, lookup: (blockNumber: bigint) => Promise): Promise => { - const observedHash = await lookup(number) - if (observedHash !== expectedHash) throw new ChainContinuityError(`Block ${number} changed while it was being indexed`) -} - -export const commitCanonicalRead = async ( - number: bigint, - expectedHash: Hash, - read: () => Promise, - lookup: (blockNumber: bigint) => Promise, - commit: (value: T) => Promise, -): Promise => { - const value = await read() - await confirmCanonicalBlock(number, expectedHash, lookup) - await commit(value) -} - -const databaseFailureMessage = 'Database request failed; retrying' -const rpcQueueSaturatedMessage = 'RPC queue saturated; retrying' -const databaseFailureNames = new Set(['DatabaseConsistencyError', 'PostgresError']) -const leaseFailureNames = new Set([...databaseFailureNames, 'LeaseLostError']) - -export const isLocalIndexerFailure = (error: unknown): boolean => - error instanceof LeaseLostError || rpcQueueSaturationFrom(error) !== undefined || errorChainIncludes(error, databaseFailureNames) - -export const indexingCompletion = (configuredStartBlock: bigint, indexedBlock: bigint, observedHead: bigint) => { - if (observedHead < configuredStartBlock) return { completedBlocks: 0n, percentage: '100.00', remainingBlocks: 0n, totalBlocks: 0n } - const boundedHead = observedHead - const totalBlocks = boundedHead - configuredStartBlock + 1n - const boundedIndexed = indexedBlock < configuredStartBlock ? configuredStartBlock - 1n : indexedBlock > boundedHead ? boundedHead : indexedBlock - const completedBlocks = boundedIndexed - configuredStartBlock + 1n - const remainingBlocks = totalBlocks - completedBlocks - const roundedHundredths = (completedBlocks * 10_000n + totalBlocks / 2n) / totalBlocks - const hundredths = remainingBlocks > 0n && roundedHundredths >= 10_000n ? 9_999n : roundedHundredths - return { - completedBlocks, - percentage: `${hundredths / 100n}.${String(hundredths % 100n).padStart(2, '0')}`, - remainingBlocks, - totalBlocks, - } -} - -export const compactIndexerDuration = (seconds: number): string => { - const rounded = Math.max(1, Math.ceil(seconds)) - if (rounded < 60) return `${rounded}s` - if (rounded < 3_600) return `${Math.floor(rounded / 60)}m ${rounded % 60}s` - const totalHours = Math.ceil(rounded / 3_600) - if (totalHours < 24) { - const totalMinutes = Math.ceil(rounded / 60) - const minutes = totalMinutes % 60 - return `${Math.floor(totalMinutes / 60)}h${minutes === 0 ? '' : ` ${minutes}m`}` - } - const hours = totalHours % 24 - return `${Math.floor(totalHours / 24)}d${hours === 0 ? '' : ` ${hours}h`}` -} - -export const indexerWaitingMessage = (networkId: string, configuredStartBlock: bigint, observedHead: bigint): string => - `[${networkId}] indexer state: live; observed head #${observedHead}; 100.00% complete; caught up; waiting for configured start block #${configuredStartBlock}` - -export const indexerProgressMessage = ( - networkId: string, - startBlock: bigint, - endBlock: bigint, - observedHead: bigint, - configuredStartBlock: bigint, - blocksPerSecond?: number, -): string => { - const state = endBlock >= observedHead ? 'live' : 'backfilling' - const indexed = startBlock === endBlock ? `indexed block #${endBlock}` : `indexed blocks #${startBlock}–#${endBlock}` - const completion = indexingCompletion(configuredStartBlock, endBlock, observedHead) - const progress = - state === 'live' - ? 'caught up' - : `${completion.remainingBlocks} blocks behind; ${blocksPerSecond === undefined ? 'estimating ETA' : `ETA ${compactIndexerDuration(Number(completion.remainingBlocks) / blocksPerSecond)}`}` - return `[${networkId}] indexer state: ${state}; ${indexed}; observed head #${observedHead}; ${completion.percentage}% complete; ${progress}` -} - -export const safeIndexerFailure = (error: unknown): string => { - if (error instanceof ChainConfigurationError) return error.message - if (error instanceof ChainContinuityError) return 'The remote canonical chain changed while indexing; retrying' - if (rpcQueueSaturationFrom(error) !== undefined) return rpcQueueSaturatedMessage - if (errorChainIncludes(error, databaseFailureNames)) return databaseFailureMessage - return 'RPC request failed; retrying' -} - -const safeErrorNames = new Set([ - 'AbortError', - 'ChainConfigurationError', - 'ChainContinuityError', - 'ConnectTimeoutError', - 'ContractFunctionExecutionError', - 'ContractFunctionRevertedError', - 'DatabaseConsistencyError', - 'Error', - 'HeadersTimeoutError', - 'HttpRequestError', - 'IndexerOwnershipStageError', - 'LeaseLostError', - 'LimitExceededRpcError', - 'PostgresError', - 'ResponseBodyTooLargeError', - 'ResourceUnavailableRpcError', - 'RpcQueueSaturatedError', - 'RpcRequestError', - 'SocketError', - 'TimeoutError', - 'TypeError', - 'UnknownNodeError', - 'UnknownRpcError', -]) - -const safeErrorIdentifier = (value: unknown): string | undefined => (typeof value === 'string' && safeErrorNames.has(value) ? value : undefined) - -const safeNamedErrorCodes = new Set(['ECONNREFUSED', 'ECONNRESET', 'ENETUNREACH', 'ENOTFOUND', 'ETIMEDOUT', 'ERR_POSTGRES_CONNECTION_CLOSED']) - -const safeErrorCode = (value: unknown): string | undefined => { - if ( - typeof value === 'number' && - Number.isSafeInteger(value) && - (value === -32700 || (value >= -32603 && value <= -32600) || (value >= -32099 && value <= -32000)) - ) - return value.toString() - return typeof value === 'string' && (/^HTTP_[1-5][0-9]{2}$/.test(value) || safeNamedErrorCodes.has(value)) ? value : undefined -} - -const safeStandardRpcMessages = new Map([ - ['parse error', 'Parse error'], - ['invalid request', 'Invalid Request'], - ['method not found', 'Method not found'], - ['invalid params', 'Invalid params'], - ['internal error', 'Internal error'], -]) - -const safeRpcCategoryMessages: Readonly> = { - 'block-range': 'provider rejected the requested block range', - 'rate-limit': 'provider rate limit exceeded', - 'response-size': 'provider response size limit exceeded', - 'result-limit': 'provider result limit exceeded', - timeout: 'provider request timed out', - 'too-many-logs': 'provider returned too many logs', - 'too-many-results': 'provider returned too many results', -} - -const safeStandardRpcProviderMessage = (value: unknown): string | undefined => { - if (typeof value !== 'string') return undefined - const normalized = normalizedRpcDescription(value) - return safeStandardRpcMessages.get(normalized.replace(/[.!]$/u, '')) -} - -const safeRpcRequestMethod = (value: unknown): string | undefined => - typeof value === 'string' && /^(?:eth|net|web3)_[A-Za-z0-9_]+$/u.test(value) ? value : undefined - -const rpcRequestMethodFrom = (error: unknown): string | undefined => { - const seen = new Set() - let current: unknown = error - while (typeof current === 'object' && current !== null && !seen.has(current)) { - seen.add(current) - const method = current instanceof RpcRequestMethodError ? safeRpcRequestMethod(current.method) : undefined - if (method !== undefined) return method - current = 'cause' in current ? current.cause : undefined - } - return undefined -} - -const indexerFailureReason = (error: unknown, includeErrorDescriptions: boolean): string => { - const saturation = rpcQueueSaturationFrom(error) - if (saturation !== undefined) - return `RpcQueueSaturatedError; active ${saturation.active}; queued ${saturation.pending}; maximum queued ${saturation.maximumPending}; high-water mark ${saturation.highWaterMark}; saturation count ${saturation.saturationCount}` - const names: string[] = [] - const descriptions: string[] = [] - let status: number | undefined - let code: string | undefined - let standardMessage: string | undefined - let previousDescriptionName: string | undefined - let previousDescriptionMessage: string | undefined - const seen = new Set() - let current: unknown = error - while (current !== undefined && !seen.has(current)) { - seen.add(current) - if (typeof current !== 'object' || current === null) { - descriptions.push(`UnknownError: ${singleLineErrorDescription(String(current))}`) - break - } - const actualName = 'name' in current && typeof current.name === 'string' ? singleLineErrorDescription(current.name) || undefined : undefined - const name = safeErrorIdentifier(actualName) - if (name !== undefined && names.at(-1) !== name) names.push(name) - const actualMessage = - singleLineErrorDescription( - preferredRpcDescriptions(current) - .find((value) => value.trim() !== '') - ?.trim() ?? '', - ) || undefined - if (actualName !== undefined || actualMessage !== undefined) { - if (actualMessage !== undefined && actualMessage === previousDescriptionMessage) { - if (actualName !== undefined && actualName !== previousDescriptionName) descriptions.push(actualName) - } else { - const descriptionName = actualName ?? 'UnknownError' - descriptions.push(actualMessage === undefined ? descriptionName : `${descriptionName}: ${actualMessage}`) - } - previousDescriptionName = actualName - previousDescriptionMessage = actualMessage - } - if ( - status === undefined && - 'status' in current && - typeof current.status === 'number' && - Number.isInteger(current.status) && - current.status >= 100 && - current.status <= 599 - ) - status = current.status - if (code === undefined && 'code' in current) code = safeErrorCode(current.code) - if (standardMessage === undefined && name === 'RpcRequestError' && 'details' in current) standardMessage = safeStandardRpcProviderMessage(current.details) - current = 'cause' in current ? current.cause : undefined - } - const category = rpcErrorCategory(error) - const message = category === undefined ? standardMessage : safeRpcCategoryMessages[category] - const fallbackDescription = descriptions.length === 0 ? 'UnknownError' : descriptions.slice(0, 4).join(' caused by ') - const details = [ - includeErrorDescriptions && message === undefined ? fallbackDescription : names.length === 0 ? 'UnknownError' : names.slice(0, 4).join(' caused by '), - ] - const method = rpcRequestMethodFrom(error) - if (method !== undefined) details.push(`method ${method}`) - if (status !== undefined) details.push(`HTTP ${status}`) - if (code !== undefined) details.push(`code ${code}`) - if (message !== undefined) details.push(`message: ${message}`) - return details.join('; ') -} - -export const safeIndexerFailureReason = (error: unknown): string => indexerFailureReason(error, false) - -export const rpcIndexerFailureReason = (error: unknown): string => indexerFailureReason(error, true) - -const rpcFailureReason = (error: unknown, rpcNumber: number): string => `RPC #${rpcNumber}: ${rpcIndexerFailureReason(error)}` - -type RpcDiagnosticProvider = Pick - -export const createRpcDiagnosticContext = (initialProvider: RpcDiagnosticProvider) => { - let activeProvider = initialProvider - return { - activeEndpoint: (): string => activeProvider.endpoint, - activeNumber: (): number => activeProvider.number, - failureReason: (error: unknown): string => rpcFailureReason(error, activeProvider.number), - select: (provider: RpcDiagnosticProvider): void => { - activeProvider = provider - }, - } -} - -export const indexerOperationFailureReason = (error: unknown, rpcNumber: number, source: 'rpc' | 'storage'): string => - source === 'rpc' ? rpcFailureReason(error, rpcNumber) : safeIndexerFailureReason(error) - -const deploymentReadTimeoutError = (): Error => { - const error = new Error('Contract deployment history read timed out') - error.name = 'TimeoutError' - return error -} - -export const boundedDeploymentRead = async (read: () => Promise, timeoutMs: number): Promise => - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(deploymentReadTimeoutError()) - }, timeoutMs) - void read() - .then(resolve, reject) - .finally(() => clearTimeout(timeout)) - }) - -export const deploymentReadBudget = (timeoutMs = 5_000, now = Date.now): ((read: () => Promise) => Promise) => { - const deadline = now() + timeoutMs - return async (read: () => Promise): Promise => { - const remaining = deadline - now() - if (remaining <= 0) throw deploymentReadTimeoutError() - const value = await boundedDeploymentRead(read, remaining) - if (now() > deadline) throw deploymentReadTimeoutError() - return value - } -} - -export const contractDeploymentScanDue = (lastCompletedAt: number | undefined, now: number, cooldownMs = 60_000): boolean => - lastCompletedAt === undefined || now - lastCompletedAt >= cooldownMs - -type NetworkLifecycle = { - readonly verify: () => Promise - readonly poll: () => Promise - readonly failure: (message: string, nextRetryAt: Date, reason: string) => Promise - readonly intervalMs: number - readonly signal: AbortSignal - readonly random?: () => number - readonly shouldRethrow?: (error: unknown) => boolean -} - -export class IndexerOwnershipStageError extends Error { - override name = 'IndexerOwnershipStageError' - - constructor( - readonly stage: OwnershipStage, - cause: unknown, - ) { - super(`Indexer ownership stage failed: ${stage}`, { cause }) - } -} - -export const retryDelayMs = (consecutiveFailures: number, intervalMs: number, random = Math.random): number => { - const exponent = Math.min(Math.max(consecutiveFailures - 1, 0), 8) - const base = Math.min(intervalMs * 2 ** exponent, 300_000) - return Math.min(Math.round(base * (0.8 + random() * 0.4)), 300_000) -} - -export const runNetworkLifecycle = async ({ verify, poll, failure, intervalMs, signal, random, shouldRethrow }: NetworkLifecycle): Promise => { - let verified = false - let consecutiveFailures = 0 - while (!signal.aborted) { - const startedAt = Date.now() - let caughtUp = true - let delayAfterFailure: number | undefined - try { - if (!verified) { - await verify() - verified = true - } - caughtUp = await poll() - consecutiveFailures = 0 - } catch (error) { - if (error instanceof LeaseLostError || shouldRethrow?.(error) === true) throw error - consecutiveFailures++ - delayAfterFailure = retryDelayMs(consecutiveFailures, intervalMs, random) - try { - const failureMessage = safeIndexerFailure(error) - const failureReason = failureMessage === 'RPC request failed; retrying' ? rpcIndexerFailureReason(error) : safeIndexerFailureReason(error) - await failure(failureMessage, new Date(Date.now() + delayAfterFailure), failureReason) - } catch (failureError) { - throw new IndexerOwnershipStageError('record-failure', failureError) - } - } - await waitForIndexerDelay(delayAfterFailure ?? (caughtUp ? Math.max(0, intervalMs - (Date.now() - startedAt)) : 0), signal) - } -} - -type OwnedNetworkLifecycle = Omit & { - readonly reconcile: () => Promise - readonly poll: () => Promise - readonly runWithProvider: (operation: () => Promise) => Promise -} - -export const runOwnedNetworkLifecycle = async ({ reconcile, poll, runWithProvider, ...lifecycle }: OwnedNetworkLifecycle): Promise => - await runNetworkLifecycle({ - ...lifecycle, - verify: () => runWithProvider(reconcile), - poll: () => runWithProvider(poll), - shouldRethrow: (error) => error instanceof DatabaseConsistencyError || lifecycle.shouldRethrow?.(error) === true, - }) - -type LeaseControl = Pick & { readonly backendPid?: number } - -type OwnershipStage = 'acquire' | 'verify' | 'seed' | 'owned-run' | 'record-failure' | 'release' - -export type IndexerOwnershipEvent = - | { - readonly type: 'failure' - readonly stage: OwnershipStage - readonly consecutiveFailures: number - readonly retryDelayMs: number - readonly backendPid?: number - } - | { readonly type: 'acquired'; readonly backendPid?: number; readonly recoveredAfterFailures: number; readonly acquiredAfterStandby: boolean } - | { readonly type: 'released'; readonly backendPid?: number } - | { readonly type: 'standby' } - -export type IndexerOwnershipStatus = { - readonly networkId: string - readonly active: boolean - readonly backendPid?: number - readonly failuresTotal: number - readonly reacquisitionsTotal: number - readonly consecutiveFailures: number - readonly lastFailureAt?: string - readonly lastFailureStage?: OwnershipStage -} - -const ownershipStatuses = new Map() - -export const nextIndexerOwnershipStatus = ( - networkId: string, - current: IndexerOwnershipStatus | undefined, - event: IndexerOwnershipEvent, - now = new Date(), -): IndexerOwnershipStatus => { - const previous = current ?? { - networkId, - active: false, - failuresTotal: 0, - reacquisitionsTotal: 0, - consecutiveFailures: 0, - } - if (event.type === 'failure') { - return { - ...previous, - active: false, - ...(event.backendPid === undefined ? {} : { backendPid: event.backendPid }), - failuresTotal: previous.failuresTotal + 1, - consecutiveFailures: event.consecutiveFailures, - lastFailureAt: now.toISOString(), - lastFailureStage: event.stage, - } - } - if (event.type === 'acquired') { - return { - ...previous, - active: true, - ...(event.backendPid === undefined ? {} : { backendPid: event.backendPid }), - reacquisitionsTotal: previous.reacquisitionsTotal + (event.recoveredAfterFailures > 0 || event.acquiredAfterStandby ? 1 : 0), - consecutiveFailures: 0, - } - } - return { - ...previous, - active: false, - backendPid: undefined, - consecutiveFailures: event.type === 'standby' ? 0 : previous.consecutiveFailures, - } -} - -const recordOwnershipEvent = (networkId: string, event: IndexerOwnershipEvent): void => { - ownershipStatuses.set(networkId, nextIndexerOwnershipStatus(networkId, ownershipStatuses.get(networkId), event)) -} - -export const indexerOwnershipStatuses = (): readonly IndexerOwnershipStatus[] => - [...ownershipStatuses.values()].sort((left, right) => left.networkId.localeCompare(right.networkId)) - -const ownershipFailureReason = (error: unknown): string => { - const reason = safeIndexerFailureReason(error) - const seen = new Set() - let current: unknown = error - while (typeof current === 'object' && current !== null && !seen.has(current)) { - seen.add(current) - if (current instanceof DatabaseConsistencyError) { - const detail = databaseConsistencyDiagnosticMessage(current) - if (detail !== undefined) return `${reason}: ${detail}` - } - current = 'cause' in current ? current.cause : undefined - } - return reason -} - -export const ownershipFailureLogMessage = ( - networkId: string, - stage: OwnershipStage, - error: unknown, - consecutiveFailures: number, - retryDelay: number, - backendPid?: number, -): string => - `[${networkId}] indexer ownership failed; stage: ${stage}; consecutive failures: ${consecutiveFailures}; retry delay: ${retryDelay}ms; backend PID: ${backendPid ?? 'unavailable'}; reason: ${ownershipFailureReason(error)}` - -type OwnershipLifecycle = { - readonly networkId: string - readonly acquire: () => Promise - readonly seed: (lease: TLease) => Promise - readonly runOwned: (lease: TLease) => Promise - readonly failure: (message: string, lease: TLease | undefined) => Promise - readonly standby: () => void - readonly intervalMs: number - readonly now?: () => number - readonly onEvent?: (event: IndexerOwnershipEvent) => void - readonly random?: () => number - readonly wait?: (milliseconds: number, signal: AbortSignal) => Promise - readonly signal: AbortSignal -} - -export const runIndexerOwnershipLifecycle = async ({ - networkId, - acquire, - seed, - runOwned, - failure, - standby, - intervalMs, - now = Date.now, - onEvent = () => {}, - random, - wait = waitForIndexerDelay, - signal, -}: OwnershipLifecycle): Promise => { - let standbyReported = false - let wasStandby = false - let consecutiveFailures = 0 - while (!signal.aborted) { - let lease: TLease | undefined - let ownedRunStartedAt: number | undefined - let stage: OwnershipStage = 'acquire' - let retryDelay: number | undefined - try { - lease = await acquire() - if (lease === undefined) { - consecutiveFailures = 0 - wasStandby = true - if (!standbyReported) { - standby() - onEvent({ type: 'standby' }) - standbyReported = true - } - } else { - standbyReported = false - stage = 'verify' - await lease.assertHeld() - stage = 'seed' - await seed(lease) - const recoveredAfterFailures = consecutiveFailures - const acquiredAfterStandby = wasStandby - onEvent({ - type: 'acquired', - ...(lease.backendPid === undefined ? {} : { backendPid: lease.backendPid }), - recoveredAfterFailures, - acquiredAfterStandby, - }) - if (recoveredAfterFailures > 0 || acquiredAfterStandby) { - const source = acquiredAfterStandby ? (recoveredAfterFailures > 0 ? 'standby and failures' : 'standby') : 'failures' - console.info( - `[${networkId}] indexer ownership reacquired; backend PID: ${lease.backendPid ?? 'unavailable'}; source: ${source}; previous consecutive failures: ${recoveredAfterFailures}`, - ) - } - wasStandby = false - stage = 'owned-run' - ownedRunStartedAt = now() - await runOwned(lease) - consecutiveFailures = 0 - } - } catch (error) { - const failureStage = error instanceof IndexerOwnershipStageError ? error.stage : stage - if (failureStage === 'owned-run' && ownedRunStartedAt !== undefined && now() - ownedRunStartedAt >= Math.max(intervalMs * 4, 60_000)) - consecutiveFailures = 0 - consecutiveFailures++ - retryDelay = retryDelayMs(consecutiveFailures, intervalMs, random) - onEvent({ - type: 'failure', - stage: failureStage, - consecutiveFailures, - retryDelayMs: retryDelay, - ...(lease?.backendPid === undefined ? {} : { backendPid: lease.backendPid }), - }) - console.error(ownershipFailureLogMessage(networkId, failureStage, error, consecutiveFailures, retryDelay, lease?.backendPid)) - try { - await failure(databaseFailureMessage, lease) - } catch (error) { - console.error(ownershipFailureLogMessage(networkId, 'record-failure', error, consecutiveFailures, retryDelay, lease?.backendPid)) - // A database outage can prevent status recording too; retry ownership regardless. - } - } finally { - try { - await lease?.release() - } catch (error) { - if (retryDelay === undefined) { - consecutiveFailures++ - retryDelay = retryDelayMs(consecutiveFailures, intervalMs, random) - onEvent({ - type: 'failure', - stage: 'release', - consecutiveFailures, - retryDelayMs: retryDelay, - ...(lease?.backendPid === undefined ? {} : { backendPid: lease.backendPid }), - }) - } - console.error(ownershipFailureLogMessage(networkId, 'release', error, consecutiveFailures, retryDelay, lease?.backendPid)) - // PostgreSQL already releases advisory locks when their session is lost. - } - if (lease !== undefined) onEvent({ type: 'released', ...(lease.backendPid === undefined ? {} : { backendPid: lease.backendPid }) }) - } - if (!signal.aborted) await wait(retryDelay ?? intervalMs, signal) - } -} - -export const isProtocolActivitySource = (contract: ContractMetadata | undefined): boolean => - contract !== undefined && - contract.kind !== 'weth' && - contract.kind !== 'reputationToken' && - contract.kind !== 'multicall3' && - contract.kind !== 'proxyDeployer' - -export const requiresManifestHistoryCoverage = (contract: ContractMetadata | undefined): boolean => - isProtocolActivitySource(contract) || contract?.kind === 'reputationToken' || contract?.kind === 'weth' - -export const isProtocolEvidenceEmitter = (contract: ContractMetadata | undefined): contract is ContractMetadata => contract !== undefined - -const requireReceiptPosition = (receipt: TransactionReceipt, blockHash: Hash, blockNumber: bigint): void => { - if (receipt.blockHash !== blockHash || receipt.blockNumber !== blockNumber) { - throw new ChainContinuityError(`Receipt ${receipt.transactionHash} no longer belongs to block ${blockNumber}`) - } - for (const log of receipt.logs) { - const position = requireLogPosition(log) - if (position.blockHash !== blockHash || position.blockNumber !== blockNumber) { - throw new ChainContinuityError(`Log ${position.transactionHash}:${position.logIndex} no longer belongs to block ${blockNumber}`) - } - } -} - class NetworkIndexer { readonly #network: NetworkConfig readonly #database: ScannerDatabase diff --git a/augurScan/src/rpc-request-queue.ts b/augurScan/src/rpc-request-queue.ts new file mode 100644 index 000000000..8da34eb60 --- /dev/null +++ b/augurScan/src/rpc-request-queue.ts @@ -0,0 +1,102 @@ +import type { Transport } from './ethereum.ts' + +export type RpcRequestQueue = { + readonly run: (operation: () => Promise) => Promise +} + +export class RpcRequestMethodError extends Error { + override name = 'RpcRequestMethodError' + + constructor( + readonly method: string, + cause: unknown, + ) { + super('RPC method failed', { cause }) + } +} + +type RpcQueueSaturation = { + readonly active: number + readonly pending: number + readonly maximumPending: number + readonly highWaterMark: number + readonly saturationCount: number +} + +export class RpcQueueSaturatedError extends Error { + readonly active: number + readonly pending: number + readonly maximumPending: number + readonly highWaterMark: number + readonly saturationCount: number + + constructor(status: RpcQueueSaturation) { + super('RPC queue reached its pending capacity') + this.name = 'RpcQueueSaturatedError' + this.active = status.active + this.pending = status.pending + this.maximumPending = status.maximumPending + this.highWaterMark = status.highWaterMark + this.saturationCount = status.saturationCount + } +} + +export const rpcQueueSaturationFrom = (error: unknown): RpcQueueSaturatedError | undefined => { + const seen = new Set() + let current: unknown = error + while (typeof current === 'object' && current !== null && !seen.has(current)) { + seen.add(current) + if (current instanceof RpcQueueSaturatedError) return current + current = 'cause' in current ? current.cause : undefined + } + return undefined +} + +export const createRpcRequestQueue = (concurrency: number, maximumPending = 100): RpcRequestQueue => { + if (!Number.isSafeInteger(concurrency) || concurrency < 1) throw new Error('RPC concurrency must be a positive safe integer') + if (!Number.isSafeInteger(maximumPending) || maximumPending < 0) throw new Error('RPC maximum pending count must be a non-negative safe integer') + let active = 0 + let highWaterMark = 0 + let saturationCount = 0 + const pending: Array<() => void> = [] + const drain = (): void => { + while (active < concurrency) { + const start = pending.shift() + if (start === undefined) return + active++ + start() + } + } + return { + run: (operation: () => Promise) => { + if (active >= concurrency && pending.length >= maximumPending) { + saturationCount++ + return Promise.reject(new RpcQueueSaturatedError({ active, pending: pending.length, maximumPending, highWaterMark, saturationCount })) + } + return new Promise((resolve, reject) => { + pending.push(() => { + void Promise.resolve() + .then(operation) + .then(resolve, reject) + .finally(() => { + active-- + drain() + }) + }) + drain() + highWaterMark = Math.max(highWaterMark, pending.length) + }) + }, + } +} + +export const withRpcRequestQueue = (transport: Transport, queue: RpcRequestQueue): Transport => ({ + ...transport, + requestScheduler: async (method: string, operation: () => Promise): Promise => { + try { + return await queue.run(() => (transport.requestScheduler === undefined ? operation() : transport.requestScheduler(method, operation))) + } catch (error) { + throw new RpcRequestMethodError(method, error) + } + }, +}) diff --git a/augurScan/tests/indexer-lifecycle.test.ts b/augurScan/tests/indexer-lifecycle.test.ts index d2e655f6b..a1291c90a 100644 --- a/augurScan/tests/indexer-lifecycle.test.ts +++ b/augurScan/tests/indexer-lifecycle.test.ts @@ -142,6 +142,66 @@ const parseRpcRequestBody = (value: unknown): { readonly id: number | string | n } describe('network indexer lifecycle', () => { + test('keeps extracted internals out of the public indexer facade', async () => { + const indexerFacade = await import('../src/indexer.ts') + expect(Object.keys(indexerFacade).sort()).toEqual( + [ + 'IndexerOwnershipStageError', + 'RpcQueueSaturatedError', + 'addressActivityFrom', + 'boundedDeploymentRead', + 'commitCanonicalRead', + 'compactIndexerDuration', + 'confirmCanonicalBlock', + 'contractDeploymentScanDue', + 'createRpcDiagnosticContext', + 'createRpcRequestQueue', + 'deploymentReadBudget', + 'findContractDeploymentBlock', + 'findManifestContractDeployment', + 'indexerOperationFailureReason', + 'indexerOwnershipStatuses', + 'indexerProgressMessage', + 'indexerWaitingMessage', + 'indexingCompletion', + 'isLocalIndexerFailure', + 'isProtocolActivitySource', + 'isProtocolEvidenceEmitter', + 'isSplittableLogRangeError', + 'logScanCursorUpdates', + 'nextIndexerOwnershipStatus', + 'ownershipFailureLogMessage', + 'planDeploymentAwareLogScan', + 'planManifestBackfill', + 'queryAdaptiveLogRange', + 'queryCanonicalLogRange', + 'readTokenMetadata', + 'reorgSearchFloor', + 'requiresManifestHistoryCoverage', + 'requiresParentLookup', + 'retryDelayMs', + 'rpcEndpointLabel', + 'rpcFailureLogMessage', + 'rpcIndexerFailureReason', + 'rpcLogAddressGroups', + 'rpcLogQueryGroups', + 'rpcProviderLabel', + 'runIndexerOwnershipLifecycle', + 'runIndexerTask', + 'runNetworkLifecycle', + 'runOwnedNetworkLifecycle', + 'safeIndexerFailure', + 'safeIndexerFailureReason', + 'startIndexers', + 'tokenMetadataNeedsRead', + 'uniswapV4PoolIds', + 'waitForIndexerDelay', + 'withRpcRequestQueue', + 'withVerifiedProvider', + ].sort(), + ) + }) + test('queues RPC work above the configured concurrency limit', async () => { const queue = createRpcRequestQueue(5) let active = 0 diff --git a/bots/liquidator/Dockerfile b/bots/liquidator/Dockerfile index ea24ec749..9fb7bbd20 100644 --- a/bots/liquidator/Dockerfile +++ b/bots/liquidator/Dockerfile @@ -1,14 +1,31 @@ ARG BUN_VERSION=1.3.14 +FROM oven/bun:${BUN_VERSION}-alpine AS shared-builder + +RUN apk add --no-cache git +WORKDIR /source +RUN git init + +COPY package.json bun.lock ./ +COPY scripts/ ./scripts/ +COPY shared/package.json shared/bun.lock shared/tsconfig.json ./shared/ +COPY shared/ts/ ./shared/ts/ +RUN mkdir -p shared/js \ + && bun install --frozen-lockfile \ + && bun run shared:build + FROM oven/bun:${BUN_VERSION}-alpine ENV BUN_ENV=production WORKDIR /app +COPY --from=shared-builder /source/shared/ ./shared/ COPY bots/shared/package.json bots/shared/bun.lock ./bots/shared/ COPY bots/shared/src/ ./bots/shared/src/ COPY bots/liquidator/package.json bots/liquidator/bun.lock ./bots/liquidator/ -RUN cd bots/shared \ +RUN cd shared \ + && bun install --frozen-lockfile --production \ + && cd ../bots/shared \ && bun install --frozen-lockfile --production \ && cd ../liquidator \ && bun install --frozen-lockfile --production diff --git a/bots/liquidator/Dockerfile.dockerignore b/bots/liquidator/Dockerfile.dockerignore index 235f39ffc..006b60500 100644 --- a/bots/liquidator/Dockerfile.dockerignore +++ b/bots/liquidator/Dockerfile.dockerignore @@ -1,4 +1,14 @@ ** +!package.json +!bun.lock +!scripts/ +!scripts/** +!shared/ +!shared/package.json +!shared/bun.lock +!shared/tsconfig.json +!shared/ts/ +!shared/ts/** !bots/shared/package.json !bots/shared/bun.lock !bots/shared/src/** diff --git a/bots/liquidator/tests/docker-packaging.test.ts b/bots/liquidator/tests/docker-packaging.test.ts index 781248c29..831d7fe79 100644 --- a/bots/liquidator/tests/docker-packaging.test.ts +++ b/bots/liquidator/tests/docker-packaging.test.ts @@ -36,9 +36,12 @@ describe('Docker packaging', () => { expect(source).toContain('docker compose up --build --force-recreate\nset "exit_code=%errorlevel%"\npopd\npause\nexit /b %exit_code%') }) - test('installs production dependencies where shared bot sources can resolve them', async () => { + test('builds and installs both shared packages where bot sources can resolve them', async () => { const source = await readFile(dockerfile, 'utf8') - expect(source).toContain('cd bots/shared \\\n\t&& bun install --frozen-lockfile --production') + expect(source).toContain('-alpine AS shared-builder') + expect(source).toContain('&& bun run shared:build') + expect(source).toContain('COPY --from=shared-builder /source/shared/ ./shared/') + expect(source).toContain('cd shared \\\n\t&& bun install --frozen-lockfile --production \\\n\t&& cd ../bots/shared \\\n\t&& bun install --frozen-lockfile --production') }) test('starts without host UID, GID, or .env configuration', async () => { diff --git a/bots/open-oracle-arbitrager/scripts/check-market-fixture.mts b/bots/open-oracle-arbitrager/scripts/check-market-fixture.mts index 972fb81a5..b4c463ac8 100644 --- a/bots/open-oracle-arbitrager/scripts/check-market-fixture.mts +++ b/bots/open-oracle-arbitrager/scripts/check-market-fixture.mts @@ -111,15 +111,15 @@ async function verifyDocumentedFixture() { const expected: Record = { baseFeeAttoEthPerGas: fixture.baseFeeAttoEthPerGas, blockNumber: fixture.blockNumber, - buyProfitAttoWeth: buy.profitBeforeGasAttoWeth, + buyProfitWethAttoEth: buy.profitBeforeGasAttoWeth, buyReportAttoRep: fixture.expensiveRepAmount, - gasCostAttoWeth: fixture.gasCostAttoWeth, + gasCostWethAttoEth: fixture.gasCostAttoWeth, midReportAttoRep: fixture.midReportRep, minimumWethReportAttoEth: minimumWeth, protocolFee: fixture.protocolFee, reportDeviationBps: fixture.reportDeviationBps, reporterFee: fixture.feePercentage, - sellProfitAttoWeth: sell.profitBeforeGasAttoWeth, + sellProfitWethAttoEth: sell.profitBeforeGasAttoWeth, sellReportAttoRep: fixture.cheapRepAmount, uniswapPoolFee: fixture.poolFee, } diff --git a/bots/shared/bun.lock b/bots/shared/bun.lock index ba60049c8..5b7471a62 100644 --- a/bots/shared/bun.lock +++ b/bots/shared/bun.lock @@ -5,9 +5,8 @@ "": { "name": "@zoltar/bot-shared", "dependencies": { - "@noble/hashes": "2.2.0", + "@zoltar/shared": "file:../../shared", "ccxt": "4.5.70", - "micro-eth-signer": "0.18.1", }, "devDependencies": { "@biomejs/biome": "2.3.11", @@ -45,6 +44,8 @@ "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], + "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], + "bufferutil": ["bufferutil@4.1.0", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw=="], "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], diff --git a/bots/shared/package.json b/bots/shared/package.json index d373582cd..e0930487d 100644 --- a/bots/shared/package.json +++ b/bots/shared/package.json @@ -27,9 +27,8 @@ "typecheck": "bun x tsc --project tsconfig.json --noEmit" }, "dependencies": { - "@noble/hashes": "2.2.0", - "ccxt": "4.5.70", - "micro-eth-signer": "0.18.1" + "@zoltar/shared": "file:../../shared", + "ccxt": "4.5.70" }, "devDependencies": { "@biomejs/biome": "2.3.11", diff --git a/bots/shared/src/ethereum.ts b/bots/shared/src/ethereum.ts index 0d4ee71c6..c2e9533d1 100644 --- a/bots/shared/src/ethereum.ts +++ b/bots/shared/src/ethereum.ts @@ -21,26 +21,10 @@ export type { TransactionReceipt, TransactionReplacement, Transport, -} from './ethereum/types' -export type { PublicActions, PublicClient, WalletClient } from './ethereum/client' -export type { RpcEndpointHealth, RpcEndpointPoolOptions, RpcEndpointStatus } from './ethereum/rpc-resilience' +} from '@zoltar/shared/ethereum' export { - createPublicClient, - createWalletClient, - custom, - defineChain, - getBalanceAtBlock, - getTransactionCountAtBlock, - http, - mainnet, - publicActions, - readContractAtBlock, - RpcError, -} from './ethereum/client' -export { createRpcEndpointPool, RpcEndpointPoolFailure } from './ethereum/rpc-resilience' -export { - bytesToHex, bigintToSafeNumber, + bytesToHex, concatHex, decodeEventLog, decodeFunctionData, @@ -68,4 +52,20 @@ export { toHex, zeroAddress, zeroHash, -} from './ethereum/codec' +} from '@zoltar/shared/ethereum' +export type { PublicActions, PublicClient, WalletClient } from './ethereum/client' +export type { RpcEndpointHealth, RpcEndpointPoolOptions, RpcEndpointStatus } from './ethereum/rpc-resilience' +export { + createPublicClient, + createWalletClient, + custom, + defineChain, + getBalanceAtBlock, + getTransactionCountAtBlock, + http, + mainnet, + publicActions, + readContractAtBlock, + RpcError, +} from './ethereum/client' +export { createRpcEndpointPool, RpcEndpointPoolFailure } from './ethereum/rpc-resilience' diff --git a/bots/shared/src/ethereum/client.ts b/bots/shared/src/ethereum/client.ts index bf359aa10..11151e574 100644 --- a/bots/shared/src/ethereum/client.ts +++ b/bots/shared/src/ethereum/client.ts @@ -1,612 +1,24 @@ -import { bytesToHex as nobleBytesToHex } from '@noble/hashes/utils.js' -import { bigintToSafeNumber, ensure0x, hexQuantity, normalizeBlockTag, transactionCountBlockTag, normalizeHash, normalizeRpcHex, normalizeRpcBigInt, normalizeCodecArguments, getNamedFunctionAbi, getContractMethod, decodeFunctionOutput, encodeEventTopics, encodeFunctionData, decodeEventLog, getAddress } from './codec' -import type { - Hex, - Address, - Hash, - AbiParameter, - Abi, - ContractFunctionResult, - RpcLogForEvent, - ContractReadParameters, - ContractSimulateParameters, - ContractWriteParameters, - EstimateContractGasParameters, - ContractFunctionParameters, - Chain, - TransactionReceipt, - WaitForTransactionReceiptParameters, - BlockTransaction, - Block, - Account, - Transport, - MulticallReturnType, - BlockTag, - LogTopicFilter, -} from './types' -import { REPLACEMENT_SCAN_BLOCK_DEPTH, buildRpcTransactionRequest, findReplacementTransaction, getReplacementReason, isTransactionNotFoundError, normalizeBlock, normalizeLog, normalizeReceipt, normalizeTransaction } from './rpc-normalization' -import { custom, http, requestTransport, RpcError, type TransportOptions } from './rpc-transport.ts' - +import { createPublicClient, type Abi, type Address, type Transport } from '@zoltar/shared/ethereum' +import { custom, http, RpcError, type TransportOptions } from './rpc-transport.ts' + +export { + createPublicClient, + createWalletClient, + defineChain, + mainnet, + publicActions, +} from '@zoltar/shared/ethereum' +export type { PublicActions, PublicClient, WalletClient } from '@zoltar/shared/ethereum' export { custom, http, RpcError, type TransportOptions } -type PublicClientShape = { - chain: TChain - extend: (extension: (client: PublicClientShape) => TExtension) => PublicClientShape & TExtension - estimateContractGas: (parameters: EstimateContractGasParameters) => Promise - getBalance: (parameters: { address: Address; blockTag?: BlockTag | undefined }) => Promise - getBlock: (parameters?: { blockNumber?: bigint | undefined; includeTransactions?: boolean | undefined }) => Promise - getBlockNumber: () => Promise - getChainId: () => Promise - getCode: (parameters: { address: Address; blockNumber?: bigint | undefined; blockTag?: BlockTag | undefined }) => Promise - getLogs: (parameters: { address?: Address | undefined; event?: TEvent; fromBlock?: bigint | undefined; toBlock?: bigint | undefined; topics?: readonly LogTopicFilter[] | undefined }) => Promise[]> - getTransaction: (parameters: { hash: Hash }) => Promise - getTransactionCount: (parameters: { address: Address; blockNumber?: bigint | undefined; blockTag?: BlockTag | undefined }) => Promise - getTransactionReceipt: (parameters: { hash: Hash }) => Promise - multicall: (parameters: { allowFailure: TAllowFailure; blockNumber?: bigint | undefined; contracts: TContracts; multicallAddress: Address }) => Promise> - readContract: (parameters: ContractReadParameters) => Promise> - simulateContract: (parameters: ContractSimulateParameters) => Promise<{ result: ContractFunctionResult }> - transport: TTransport - waitForTransactionReceipt: (parameters: WaitForTransactionReceiptParameters) => Promise -} - -type WalletClientShape = Omit, 'extend'> & { - account: TAccount - call: (parameters: { account?: Account | Address | undefined; data?: Hex | undefined; gas?: bigint | undefined; gasPrice?: bigint | undefined; maxFeePerGas?: bigint | undefined; maxPriorityFeePerGas?: bigint | undefined; to?: Address | undefined; value?: bigint | undefined }) => Promise<{ data: Hex | undefined }> - extend: (extension: (client: WalletClientShape) => TExtension) => WalletClientShape & TExtension - sendRawTransaction: (parameters: { serializedTransaction: Hex }) => Promise - sendTransaction: (parameters: { - account?: Account | Address | undefined - amount?: bigint | undefined - data?: Hex | undefined - gas?: bigint | undefined - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined - nonce?: bigint | number | undefined - to?: Address | null | undefined - value?: bigint | undefined - }) => Promise - writeContract: (parameters: ContractWriteParameters) => Promise -} - -export type PublicClient = PublicClientShape - -export type WalletClient = WalletClientShape - -export type PublicActions = Omit, 'chain' | 'extend' | 'transport'> - -const MAINNET_CHAIN = { - id: 1, - name: 'Ethereum', - nativeCurrency: { - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - rpcUrls: { - default: { - http: ['https://ethereum-rpc.publicnode.com'], - }, - }, -} satisfies Chain - -const MULTICALL3_ABI = [ - { - inputs: [ - { - components: [ - { name: 'target', type: 'address' }, - { name: 'allowFailure', type: 'bool' }, - { name: 'callData', type: 'bytes' }, - ], - name: 'calls', - type: 'tuple[]', - }, - ], - name: 'aggregate3', - outputs: [ - { - components: [ - { name: 'success', type: 'bool' }, - { name: 'returnData', type: 'bytes' }, - ], - name: 'returnData', - type: 'tuple[]', - }, - ], - stateMutability: 'payable', - type: 'function', - }, -] as const - -export const mainnet = MAINNET_CHAIN - -export function defineChain(chain: TChain) { - return chain -} - -async function readContractRaw(transport: Transport, parameters: ContractReadParameters, blockNumber?: bigint | undefined) { - const abiItem = getNamedFunctionAbi(parameters.abi, parameters.functionName, parameters.args) - const method = getContractMethod(abiItem) - const data = ensure0x(nobleBytesToHex(method.encodeInput(normalizeCodecArguments(abiItem.inputs, parameters.args)))) - const rawResult = normalizeRpcHex( - await requestTransport(transport, { - method: 'eth_call', - params: [ - buildRpcTransactionRequest({ - account: parameters.account, - data, - gas: parameters.gas, - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - to: parameters.address, - value: parameters.value, - }), - blockNumber === undefined ? (parameters.blockTag ?? 'latest') : normalizeBlockTag(blockNumber), - ], - }), - ) - if (rawResult === '0x' && (abiItem.outputs?.length ?? 0) > 0) { - throw new RpcError(`The contract function "${parameters.functionName}" returned no data ("0x"). The contract does not have the function "${parameters.functionName}".`, { - shortMessage: `The contract function "${parameters.functionName}" returned no data ("0x"). The contract does not have the function "${parameters.functionName}".`, - }) - } - return { - abiItem, - data: rawResult, - } -} - export async function readContractAtBlock(transport: Transport, parameters: { abi: Abi; address: Address; args?: readonly unknown[] | undefined; functionName: string }, blockNumber: bigint): Promise { - const { abiItem, data } = await readContractRaw(transport, parameters, blockNumber) - return decodeFunctionOutput(abiItem, data) + return await createPublicClient({ transport }).readContract({ ...parameters, blockNumber }) } export async function getBalanceAtBlock(transport: Transport, parameters: { address: Address; blockNumber: bigint }) { - return normalizeRpcBigInt( - await requestTransport(transport, { - method: 'eth_getBalance', - params: [parameters.address, normalizeBlockTag(parameters.blockNumber)], - }), - ) + return await createPublicClient({ transport }).getBalance(parameters) } export async function getTransactionCountAtBlock(transport: Transport, parameters: { address: Address; blockNumber: bigint }) { - return normalizeRpcBigInt( - await requestTransport(transport, { - method: 'eth_getTransactionCount', - params: [parameters.address, normalizeBlockTag(parameters.blockNumber)], - }), - ) -} - -function buildPublicClientActions({ chain, transport }: { chain: TChain; transport: TTransport }): Omit, 'chain' | 'extend' | 'transport'> { - return { - estimateContractGas: async (parameters: EstimateContractGasParameters) => - normalizeRpcBigInt( - await requestTransport(transport, { - method: 'eth_estimateGas', - params: [ - buildRpcTransactionRequest({ - account: parameters.account, - data: encodeFunctionData({ - abi: parameters.abi, - ...(parameters.args === undefined ? {} : { args: parameters.args }), - functionName: parameters.functionName, - }), - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - to: parameters.address, - value: parameters.value, - }), - ], - }), - ), - getBalance: async parameters => - normalizeRpcBigInt( - await requestTransport(transport, { - method: 'eth_getBalance', - params: [parameters.address, parameters.blockTag ?? 'latest'], - }), - ), - getTransactionCount: async parameters => - normalizeRpcBigInt( - await requestTransport(transport, { - method: 'eth_getTransactionCount', - params: [parameters.address, transactionCountBlockTag(parameters)], - }), - ), - getBlock: async parameters => { - const includeTransactions = parameters?.includeTransactions === true - const blockTag = normalizeBlockTag(parameters?.blockNumber) - const block = await requestTransport(transport, { - method: 'eth_getBlockByNumber', - params: [blockTag, includeTransactions], - }) - return normalizeBlock(block, includeTransactions) - }, - getBlockNumber: async () => normalizeRpcBigInt(await requestTransport(transport, { method: 'eth_blockNumber' })), - getChainId: async () => bigintToSafeNumber(normalizeRpcBigInt(await requestTransport(transport, { method: 'eth_chainId' })), 'Chain ID'), - getCode: async parameters => { - if (parameters.blockNumber !== undefined && parameters.blockTag !== undefined) throw new Error('getCode accepts either blockNumber or blockTag, not both') - const result = normalizeRpcHex( - await requestTransport(transport, { - method: 'eth_getCode', - params: [parameters.address, parameters.blockNumber === undefined ? (parameters.blockTag ?? 'latest') : normalizeBlockTag(parameters.blockNumber)], - }), - ) - return result === '0x' ? undefined : result - }, - getLogs: async (parameters: { address?: Address | undefined; event?: TEvent; fromBlock?: bigint | undefined; toBlock?: bigint | undefined; topics?: readonly LogTopicFilter[] | undefined }) => { - const event = parameters.event - if (event !== undefined && parameters.topics !== undefined) throw new Error('getLogs accepts either an event or raw topics, not both') - const topics = - parameters.topics ?? - (event === undefined - ? undefined - : encodeEventTopics({ - abi: [event], - eventName: event.name ?? 'event', - })) - const rawLogs = await requestTransport(transport, { - method: 'eth_getLogs', - params: [ - { - ...(parameters.address === undefined ? {} : { address: parameters.address }), - ...(parameters.fromBlock === undefined ? {} : { fromBlock: hexQuantity(parameters.fromBlock) }), - ...(parameters.toBlock === undefined ? {} : { toBlock: hexQuantity(parameters.toBlock) }), - ...(topics === undefined ? {} : { topics }), - }, - ], - }) - return rawLogs.map(rawLog => { - const normalizedLog = normalizeLog(rawLog) - if (event === undefined) return normalizedLog - const decodedLog = decodeEventLog({ - abi: [event], - data: normalizedLog.data, - topics: normalizedLog.topics, - }) - return { - ...normalizedLog, - args: decodedLog.args, - eventName: decodedLog.eventName, - } - }) as unknown as readonly RpcLogForEvent[] - }, - getTransaction: async parameters => { - const rawTransaction = await requestTransport(transport, { - method: 'eth_getTransactionByHash', - params: [parameters.hash], - }) - if (rawTransaction === null) throw new Error(`Transaction with hash "${parameters.hash}" could not be found.`) - return normalizeTransaction(rawTransaction) - }, - getTransactionReceipt: async parameters => { - const rawReceipt = await requestTransport(transport, { - method: 'eth_getTransactionReceipt', - params: [parameters.hash], - }) - if (rawReceipt === null) throw new Error(`Transaction receipt with hash "${parameters.hash}" could not be found.`) - return normalizeReceipt(rawReceipt) - }, - multicall: async (parameters: { allowFailure: TAllowFailure; blockNumber?: bigint | undefined; contracts: TContracts; multicallAddress: Address }) => { - const calls: { allowFailure: boolean; callData: Hex; target: Address }[] = [] - for (const contract of parameters.contracts) { - calls.push({ - allowFailure: parameters.allowFailure, - callData: encodeFunctionData({ - abi: contract.abi, - ...(contract.args === undefined ? {} : { args: contract.args }), - functionName: contract.functionName, - }), - target: contract.address, - }) - } - const rawResult = (await readContractRaw( - transport, - { - abi: MULTICALL3_ABI, - address: parameters.multicallAddress, - args: [calls] as never, - functionName: 'aggregate3', - }, - parameters.blockNumber, - )) as { - abiItem: AbiParameter - data: Hex - } - const decoded = decodeFunctionOutput(rawResult.abiItem, rawResult.data) - if (!Array.isArray(decoded)) throw new Error('Unexpected multicall response') - - if (parameters.allowFailure) { - return decoded.map((entry, index) => { - if (typeof entry !== 'object' || entry === null || !('success' in entry) || !('returnData' in entry)) { - return { - error: new Error('Unexpected multicall response'), - status: 'failure', - } - } - if (entry.success !== true) { - return { - error: new Error('Multicall contract call failed'), - status: 'failure', - } - } - const contract = parameters.contracts[index] - if (contract === undefined) throw new Error('Missing multicall contract response') - const abiItem = getNamedFunctionAbi(contract.abi, contract.functionName, contract.args) - return { - result: decodeFunctionOutput(abiItem, entry.returnData as Hex), - status: 'success', - } - }) as MulticallReturnType - } - - return decoded.map((entry, index) => { - if (typeof entry !== 'object' || entry === null || !('success' in entry) || !('returnData' in entry) || entry.success !== true) { - throw new Error('Multicall contract call failed') - } - const contract = parameters.contracts[index] - if (contract === undefined) throw new Error('Missing multicall contract response') - const abiItem = getNamedFunctionAbi(contract.abi, contract.functionName, contract.args) - return decodeFunctionOutput(abiItem, entry.returnData as Hex) - }) as MulticallReturnType - }, - readContract: async (parameters: ContractReadParameters) => { - const { abiItem, data } = await readContractRaw(transport, parameters, parameters.blockNumber) - return decodeFunctionOutput(abiItem, data) as ContractFunctionResult - }, - simulateContract: async (parameters: ContractSimulateParameters) => { - const { abiItem, data } = await readContractRaw(transport, parameters, parameters.blockNumber) - return { - result: decodeFunctionOutput(abiItem, data) as ContractFunctionResult, - } - }, - waitForTransactionReceipt: async parameters => { - const timeoutMilliseconds = parameters.timeout ?? 180_000 - const pollingInterval = parameters.pollingInterval ?? 1_000 - const startTime = Date.now() - const actions = buildPublicClientActions({ chain, transport }) - let originalTransaction = parameters.transaction - let lastScannedReplacementBlock: bigint | undefined - if (parameters.onReplaced !== undefined && originalTransaction === undefined) { - try { - originalTransaction = await actions.getTransaction({ - hash: parameters.hash, - }) - } catch (error) { - if (!isTransactionNotFoundError(error)) throw error - } - } - while (true) { - try { - return await actions.getTransactionReceipt({ - hash: parameters.hash, - }) - } catch (error) { - if (!isTransactionNotFoundError(error)) throw error - if (parameters.onReplaced !== undefined && originalTransaction === undefined) { - try { - originalTransaction = await actions.getTransaction({ - hash: parameters.hash, - }) - } catch (transactionError) { - if (!isTransactionNotFoundError(transactionError)) throw transactionError - } - } - if (originalTransaction !== undefined) { - const latestBlockNumber = await actions.getBlockNumber() - let firstScanBlock = lastScannedReplacementBlock === undefined ? 0n : lastScannedReplacementBlock + 1n - if (lastScannedReplacementBlock === undefined && latestBlockNumber > REPLACEMENT_SCAN_BLOCK_DEPTH) { - firstScanBlock = latestBlockNumber - REPLACEMENT_SCAN_BLOCK_DEPTH - } - const replacementTransaction = firstScanBlock > latestBlockNumber ? undefined : await findReplacementTransaction(actions, originalTransaction, { fromBlock: firstScanBlock, toBlock: latestBlockNumber }) - lastScannedReplacementBlock = latestBlockNumber - if (replacementTransaction !== undefined) { - const transactionReceipt = await actions.getTransactionReceipt({ - hash: replacementTransaction.hash, - }) - parameters.onReplaced?.({ - reason: getReplacementReason(originalTransaction, replacementTransaction), - replacedTransaction: originalTransaction, - transaction: replacementTransaction, - transactionReceipt, - }) - return transactionReceipt - } - } - if (Date.now() - startTime >= timeoutMilliseconds) throw error - await new Promise(resolve => { - setTimeout(resolve, pollingInterval) - }) - } - } - }, - } -} - -function getClientDefaultAccountAddress(client: object): Address | undefined { - if (!('account' in client)) return undefined - const account = client.account - if (typeof account === 'string') return getAddress(account) - if (typeof account !== 'object' || account === null) return undefined - if (!('address' in account) || typeof account.address !== 'string') return undefined - return getAddress(account.address) -} - -export function publicActions(client: PublicClientShape) { - const actions = buildPublicClientActions({ - chain: client.chain, - transport: client.transport, - }) - const defaultAccount = getClientDefaultAccountAddress(client) - if (defaultAccount === undefined) return actions - const estimateContractGas: typeof actions.estimateContractGas = async parameters => - await actions.estimateContractGas({ - ...parameters, - account: parameters.account ?? defaultAccount, - }) - const simulateContract: typeof actions.simulateContract = async parameters => - await actions.simulateContract({ - ...parameters, - account: parameters.account ?? defaultAccount, - }) - return { - ...actions, - estimateContractGas, - simulateContract, - } -} - -export function createPublicClient({ chain, transport }: { cacheTime?: number | undefined; chain?: TChain; transport: TTransport }): PublicClient { - const resolvedChain = chain as TChain - const actions = buildPublicClientActions({ - chain: resolvedChain, - transport, - }) - let client: PublicClient - client = { - ...actions, - chain: resolvedChain, - extend: extension => Object.assign({}, client, extension(client)) as PublicClient & ReturnType, - transport, - } - return client -} - -function normalizeWalletAccount(account: Account | Address | undefined) { - if (account === undefined) return undefined - if (typeof account === 'string') { - return { - address: getAddress(account), - type: 'json-rpc', - } satisfies Account - } - return account -} - -export function createWalletClient({ account, chain, transport }: { account: Account | Address; cacheTime?: number | undefined; chain?: TChain; transport: TTransport }): WalletClient -export function createWalletClient({ account, chain, transport }: { account?: undefined; cacheTime?: number | undefined; chain?: TChain; transport: TTransport }): WalletClient -export function createWalletClient({ account, chain, transport }: { account?: Account | Address | undefined; cacheTime?: number | undefined; chain?: TChain; transport: TTransport }) { - const normalizedAccount = normalizeWalletAccount(account) - const publicClient = - chain === undefined - ? createPublicClient({ - transport, - }) - : createPublicClient({ - chain, - transport, - }) - const baseClient = publicClient as PublicClient - let walletClient: WalletClient - walletClient = { - ...baseClient, - account: normalizedAccount, - call: async parameters => { - const account = parameters.account ?? normalizedAccount - const data = normalizeRpcHex( - await requestTransport(transport, { - method: 'eth_call', - params: [ - buildRpcTransactionRequest({ - account, - data: parameters.data, - gas: parameters.gas, - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - to: parameters.to, - value: parameters.value, - }), - 'latest', - ], - }), - ) - return { - data, - } - }, - estimateContractGas: async parameters => - await baseClient.estimateContractGas({ - ...parameters, - account: parameters.account ?? normalizedAccount, - }), - sendRawTransaction: async parameters => - normalizeHash( - await requestTransport(transport, { - method: 'eth_sendRawTransaction', - params: [parameters.serializedTransaction], - }), - ), - simulateContract: async parameters => - await baseClient.simulateContract({ - ...parameters, - account: parameters.account ?? normalizedAccount, - }), - sendTransaction: async parameters => { - const sender = parameters.account ?? normalizedAccount - if (typeof sender === 'object' && sender !== null && sender.type === 'local' && sender.signTransaction !== undefined) { - const serializedTransaction = await sender.signTransaction({ - chainId: chain?.id, - data: parameters.data, - gas: parameters.gas, - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - nonce: parameters.nonce, - to: parameters.to ?? undefined, - value: parameters.value ?? parameters.amount, - }) - return await walletClient.sendRawTransaction({ - serializedTransaction, - }) - } - - const normalizedSender = (() => { - if (sender === undefined) return undefined - if (typeof sender === 'string') return getAddress(sender) - return sender - })() - return normalizeHash( - await requestTransport(transport, { - method: 'eth_sendTransaction', - params: [ - buildRpcTransactionRequest({ - account: normalizedSender, - amount: parameters.amount, - data: parameters.data, - gas: parameters.gas, - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - nonce: parameters.nonce, - to: parameters.to, - value: parameters.value, - }), - ], - }), - ) - }, - extend: extension => Object.assign({}, walletClient, extension(walletClient)) as WalletClient & ReturnType, - writeContract: async parameters => - await walletClient.sendTransaction({ - account: parameters.account, - data: encodeFunctionData({ - abi: parameters.abi, - ...(parameters.args === undefined ? {} : { args: parameters.args }), - functionName: parameters.functionName, - }), - gas: parameters.gas, - gasPrice: parameters.gasPrice, - maxFeePerGas: parameters.maxFeePerGas, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas, - to: parameters.address, - value: parameters.value, - }), - } - return walletClient + return await createPublicClient({ transport }).getTransactionCount(parameters) } diff --git a/bots/shared/src/ethereum/codec.ts b/bots/shared/src/ethereum/codec.ts deleted file mode 100644 index 201f663d9..000000000 --- a/bots/shared/src/ethereum/codec.ts +++ /dev/null @@ -1,799 +0,0 @@ -import { keccak_256 } from '@noble/hashes/sha3.js' -import { bytesToHex as nobleBytesToHex, concatBytes, hexToBytes as nobleHexToBytes, utf8ToBytes } from '@noble/hashes/utils.js' -import { addr, amounts, eip191Signer, Transaction } from 'micro-eth-signer' -import { Decoder, createContract, deployContract, events } from 'micro-eth-signer/advanced/abi.js' -import type { Hex, Address, Hash, AbiParameter, Abi, TupleValue, DecodedFunctionData, DecodedEventLog, Account, ParsedTransaction, BlockTag } from './types' - -export { parseAbiItem, parseAbiParameters } from './human-readable-abi' - -export const zeroAddress = getAddress('0x0000000000000000000000000000000000000000') -export const zeroHash = `0x${'00'.repeat(32)}` satisfies Hash -export const maxUint256 = amounts.maxUint256 - -function stripHexPrefix(value: string) { - return value.startsWith('0x') ? value.slice(2) : value -} - -export function ensure0x(value: string): Hex { - return (value.startsWith('0x') ? value : `0x${value}`) as Hex -} - -function ensureEvenHex(value: string) { - return value.length % 2 === 0 ? value : `0${value}` -} - -function isHexCharacter(value: string) { - return /^[0-9a-fA-F]*$/.test(value) -} - -function hexToBigInt(value: string | bigint | number | undefined) { - if (value === undefined) return undefined - if (typeof value === 'bigint') return value - if (typeof value === 'number') return normalizeQuantityValue(value) - return BigInt(value) -} - -function normalizeQuantityValue(value: bigint | number) { - if (typeof value === 'number') { - if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Number "${value.toString()}" is not in safe integer range`) - return BigInt(value) - } - if (value < 0n) throw new Error(`Number "${value.toString()}n" is not in safe integer range`) - return value -} - -export function bigintToSafeNumber(value: bigint, label = 'Value') { - if (value < -9_007_199_254_740_991n || value > 9_007_199_254_740_991n) throw new Error(`${label} exceeds the JavaScript safe integer range`) - return Number.parseInt(value.toString(), 10) -} - -export function hexQuantity(value: bigint | number) { - const normalized = normalizeQuantityValue(value) - return normalized === 0n ? '0x0' : ensure0x(normalized.toString(16)) -} - -export function normalizeHexData(value: string | undefined) { - if (value === undefined) return undefined - if (!isHex(value, { strict: true })) throw new Error(`Invalid hex value: ${value}`) - return ensure0x(ensureEvenHex(stripHexPrefix(value).toLowerCase())) -} - -export function normalizeBoolean(value: unknown) { - if (typeof value === 'boolean') return value - if (typeof value === 'string') { - if (value === '0x1' || value.toLowerCase() === 'true') return true - if (value === '0x0' || value.toLowerCase() === 'false') return false - } - if (typeof value === 'number') return value !== 0 - if (typeof value === 'bigint') return value !== 0n - return false -} - -export function normalizeTransactionType(value: unknown) { - if (typeof value !== 'string') return undefined - switch (value) { - case '0x0': - return 'legacy' - case '0x1': - return 'eip2930' - case '0x2': - return 'eip1559' - case '0x3': - return 'eip4844' - case '0x4': - return 'eip7702' - default: - return value - } -} - -export function normalizeBlockTag(value: bigint | undefined) { - return value === undefined ? 'latest' : hexQuantity(value) -} - -export function transactionCountBlockTag(parameters: { blockNumber?: bigint | undefined; blockTag?: BlockTag | undefined }) { - if (parameters.blockNumber !== undefined && parameters.blockTag !== undefined) throw new Error('Transaction count cannot specify both blockNumber and blockTag') - return parameters.blockNumber === undefined ? (parameters.blockTag ?? 'latest') : normalizeBlockTag(parameters.blockNumber) -} - -export function normalizeNullableAddress(value: unknown) { - if (value === null || value === undefined) return undefined - if (typeof value !== 'string') throw new Error('RPC returned an invalid address') - if (value === '0x') return undefined - return getAddress(value) -} - -export function normalizeAddress(value: unknown) { - const normalized = normalizeNullableAddress(value) - if (normalized === undefined) throw new Error('RPC returned an invalid address') - return normalized -} - -export function normalizeHash(value: unknown) { - if (typeof value !== 'string' || !isHex(value, { strict: true })) throw new Error('RPC returned an invalid hash') - const normalized = stripHexPrefix(value).toLowerCase() - if (normalized.length !== 64) throw new Error('RPC returned an invalid hash') - return ensure0x(normalized) as Hash -} - -export function normalizeRpcHex(value: unknown) { - if (typeof value !== 'string' || !isHex(value, { strict: true })) throw new Error('RPC returned an invalid hex value') - return ensure0x(ensureEvenHex(stripHexPrefix(value).toLowerCase())) -} - -export function normalizeRpcBigInt(value: unknown, fallback = 0n) { - if (value === undefined || value === null) return fallback - if (typeof value === 'bigint') return value - if (typeof value === 'number') return BigInt(value) - if (typeof value !== 'string') throw new Error('RPC returned an invalid bigint value') - return BigInt(value) -} - -function normalizeInputValues(values: readonly unknown[] | undefined) { - return values === undefined ? [] : [...values] -} - -function isStaticBytesAbiType(type: string) { - return /^bytes\d+$/u.test(type) -} - -function normalizeCodecValue(parameter: AbiParameter, value: unknown): unknown { - const arrayItemType = getArrayItemType(parameter.type) - if (arrayItemType !== undefined) { - if (!Array.isArray(value)) return value - return value.map(item => normalizeCodecValue({ ...parameter, type: arrayItemType }, item)) - } - if (parameter.type.startsWith('tuple')) { - const components = parameter.components ?? [] - const allNamed = components.every(component => component.name !== undefined && component.name !== '') - if (Array.isArray(value)) { - if (!allNamed) { - return value.map((item, index) => { - const component = components[index] - return component === undefined ? item : normalizeCodecValue(component, item) - }) - } - return Object.fromEntries( - components.map((component, index) => { - const name = component.name - if (name === undefined || name === '') throw new Error('ABI tuple component name is missing') - return [name, normalizeCodecValue(component, value[index])] - }), - ) - } - if (typeof value !== 'object' || value === null) return value - if (!allNamed) { - return components.map((component, index) => normalizeCodecValue(component, Reflect.get(value, index.toString()))) - } - return Object.fromEntries( - components.map(component => { - const name = component.name - if (name === undefined || name === '') throw new Error('ABI tuple component name is missing') - return [name, normalizeCodecValue(component, Reflect.get(value, name))] - }), - ) - } - if ((parameter.type === 'bytes' || isStaticBytesAbiType(parameter.type)) && typeof value === 'string' && isHex(value, { strict: true })) { - return abiHexToBytes(value) - } - return value -} - -function abiHexToBytes(value: Hex | string) { - const stripped = stripHexPrefix(value) - return nobleHexToBytes(stripped.length % 2 === 0 ? stripped : `${stripped}0`) -} - -export function normalizeCodecArguments(parameters: readonly AbiParameter[] | undefined, values: readonly unknown[] | undefined) { - const normalizedValues = normalizeInputValues(values) - const resolvedParameters = parameters ?? [] - if (resolvedParameters.length === 0) return normalizedValues - if (resolvedParameters.length === 1) { - const parameter = resolvedParameters[0] - if (parameter === undefined) return normalizedValues[0] - return normalizeCodecValue(parameter, normalizedValues[0]) - } - const allNamed = resolvedParameters.every(parameter => parameter.name !== undefined && parameter.name !== '') - if (!allNamed) { - return resolvedParameters.map((parameter, index) => normalizeCodecValue(parameter, normalizedValues[index])) - } - return Object.fromEntries( - resolvedParameters.map((parameter, index) => { - const name = parameter.name - if (name === undefined || name === '') throw new Error('ABI parameter name is missing') - return [name, normalizeCodecValue(parameter, normalizedValues[index])] - }), - ) -} - -function normalizeAbiParameterValue(value: unknown, context: string): AbiParameter { - if (typeof value !== 'object' || value === null) throw new Error(`Invalid ${context}`) - const parameter = value as Record - const type = parameter['type'] - if (typeof type !== 'string') throw new Error(`Invalid ${context}`) - const normalizeChildParameters = (children: unknown, propertyName: string) => { - if (!Array.isArray(children)) throw new Error(`Invalid ${context}.${propertyName}`) - return children.map((child, index) => normalizeAbiParameterValue(child, `${context}.${propertyName}[${index.toString()}]`)) - } - return { - ...(typeof parameter['anonymous'] === 'boolean' ? { anonymous: parameter['anonymous'] } : {}), - ...(parameter['components'] === undefined ? {} : { components: normalizeChildParameters(parameter['components'], 'components') }), - ...(typeof parameter['indexed'] === 'boolean' ? { indexed: parameter['indexed'] } : {}), - ...(parameter['inputs'] === undefined ? {} : { inputs: normalizeChildParameters(parameter['inputs'], 'inputs') }), - ...(typeof parameter['name'] === 'string' ? { name: parameter['name'] } : {}), - ...(parameter['outputs'] === undefined ? {} : { outputs: normalizeChildParameters(parameter['outputs'], 'outputs') }), - ...(typeof parameter['stateMutability'] === 'string' ? { stateMutability: parameter['stateMutability'] } : {}), - type, - } -} - -function normalizeAbi(abi: readonly unknown[]) { - return abi.map((entry, index) => normalizeAbiParameterValue(entry, `abi[${index.toString()}]`)) -} - -function getArrayItemType(type: string) { - const match = /^(.*)\[(?:\d*)\]$/u.exec(type) - return match?.[1] -} - -function isIntegerAbiType(type: string) { - return /^u?int(?:\d+)?$/u.test(type) -} - -function normalizeDecodedTuple(components: readonly AbiParameter[], value: unknown): unknown { - if (Array.isArray(value)) { - return value.map((item, index) => { - const component = components[index] - return component === undefined ? item : normalizeDecodedValue(component, item) - }) - } - if (typeof value !== 'object' || value === null) return value - const tuple = value as Record - const normalized: Record = {} - for (const [key, currentValue] of Object.entries(tuple)) { - const componentByIndex = /^\d+$/u.test(key) ? components[Number(key)] : undefined - const componentByName = componentByIndex ?? components.find(component => component.name === key) - normalized[key] = componentByName === undefined ? currentValue : normalizeDecodedValue(componentByName, currentValue) - } - return normalized -} - -function normalizeDecodedValue(parameter: AbiParameter, value: unknown): unknown { - const arrayItemType = getArrayItemType(parameter.type) - if (arrayItemType !== undefined) { - if (!Array.isArray(value)) return value - return value.map(item => normalizeDecodedValue({ ...parameter, type: arrayItemType }, item)) - } - if (parameter.type.startsWith('tuple')) { - return normalizeDecodedTuple(parameter.components ?? [], value) - } - if (isIntegerAbiType(parameter.type)) { - if (typeof value === 'number') return BigInt(value) - return value - } - if (parameter.type === 'address' && typeof value === 'string' && isAddress(value)) return getAddress(value) - if (parameter.type.startsWith('bytes') && value instanceof Uint8Array) return bytesToHex(value) - if (parameter.type.startsWith('bytes') && typeof value === 'string' && isHex(value, { strict: true })) return normalizeRpcHex(value) - return value -} - -function normalizeDecodedArguments(parameters: readonly AbiParameter[], value: unknown): unknown[] { - if (parameters.length === 0) return [] - if (parameters.length === 1) { - const parameter = parameters[0] - if (parameter === undefined) return [value] - return [normalizeDecodedValue(parameter, value)] - } - return normalizeDecodeFunctionArgs(value).map((item, index) => { - const parameter = parameters[index] - return parameter === undefined ? item : normalizeDecodedValue(parameter, item) - }) -} - -function normalizeDecodedFunctionOutput(abiItem: AbiParameter, value: unknown): unknown { - const outputs = abiItem.outputs ?? [] - if (outputs.length === 0) return undefined - if (outputs.length === 1) { - const output = outputs[0] - if (output === undefined) return value - return normalizeDecodedValue(output, value) - } - return normalizeDecodedTuple(outputs, value) -} - -function cloneAbiParameter(parameter: AbiParameter, options: { stripName: boolean }): AbiParameter { - const nameProperties = (() => { - if (options.stripName) return {} - if (parameter.name === undefined) return {} - return { name: parameter.name } - })() - return { - ...nameProperties, - ...(parameter.anonymous === undefined ? {} : { anonymous: parameter.anonymous }), - ...(parameter.indexed === undefined ? {} : { indexed: parameter.indexed }), - ...(parameter.inputs === undefined ? {} : { inputs: parameter.inputs.map((input: AbiParameter) => cloneAbiParameter(input, { stripName: false })) }), - ...(parameter.outputs === undefined ? {} : { outputs: parameter.outputs.map((output: AbiParameter) => cloneAbiParameter(output, { stripName: false })) }), - ...(parameter.components === undefined ? {} : { components: parameter.components.map((component: AbiParameter) => cloneAbiParameter(component, { stripName: false })) }), - ...(parameter.stateMutability === undefined ? {} : { stateMutability: parameter.stateMutability }), - type: parameter.type, - } -} - -function normalizeFunctionAbiForCodec(abiItem: AbiParameter): AbiParameter { - return { - ...(abiItem.name === undefined ? {} : { name: abiItem.name }), - ...(abiItem.inputs === undefined - ? {} - : { - inputs: abiItem.inputs.map((input: AbiParameter) => cloneAbiParameter(input, { stripName: true })), - }), - ...(abiItem.outputs === undefined - ? {} - : { - outputs: abiItem.outputs.map((output: AbiParameter, _index: number, outputs: readonly AbiParameter[]) => cloneAbiParameter(output, { stripName: outputs.length !== 1 || !output.type.startsWith('tuple') })), - }), - ...(abiItem.stateMutability === undefined ? {} : { stateMutability: abiItem.stateMutability }), - type: abiItem.type, - } -} - -function normalizeFunctionAbiForEncoder(abiItem: AbiParameter): AbiParameter { - return { - ...(abiItem.name === undefined ? {} : { name: abiItem.name }), - ...(abiItem.inputs === undefined - ? {} - : { - inputs: abiItem.inputs.map((input: AbiParameter) => cloneAbiParameter(input, { stripName: false })), - }), - ...(abiItem.outputs === undefined - ? {} - : { - outputs: abiItem.outputs.map((output: AbiParameter, _index: number, outputs: readonly AbiParameter[]) => cloneAbiParameter(output, { stripName: outputs.length !== 1 || !output.type.startsWith('tuple') })), - }), - ...(abiItem.stateMutability === undefined ? {} : { stateMutability: abiItem.stateMutability }), - type: abiItem.type, - } -} - -export function getNamedFunctionAbi(abi: readonly unknown[], functionName: string, args?: readonly unknown[]) { - const normalizedAbi = normalizeAbi(abi) - const signatureMatch = normalizedAbi.find((entry: AbiParameter) => entry.type === 'function' && getAbiSignature(entry) === functionName) - if (signatureMatch !== undefined) return signatureMatch - - const matchingEntries = normalizedAbi.filter((entry: AbiParameter) => entry.type === 'function' && entry.name === functionName) - if (matchingEntries.length === 0) { - throw new Error(`Function "${functionName}" was not found in the ABI`) - } - if (matchingEntries.length === 1) { - const onlyEntry = matchingEntries[0] - if (onlyEntry === undefined) throw new Error(`Function "${functionName}" was not found in the ABI`) - return onlyEntry - } - - const argumentCount = args?.length ?? 0 - const arityMatches = matchingEntries.filter((entry: AbiParameter) => (entry.inputs?.length ?? 0) === argumentCount) - if (arityMatches.length === 1) { - const arityMatch = arityMatches[0] - if (arityMatch === undefined) throw new Error(`Function "${functionName}" was not found in the ABI`) - return arityMatch - } - if (arityMatches.length > 1) { - const compatibleMatches = arityMatches.filter((entry: AbiParameter) => canEncodeFunctionArguments(entry, args)) - if (compatibleMatches.length === 1) { - const compatibleMatch = compatibleMatches[0] - if (compatibleMatch === undefined) throw new Error(`Function "${functionName}" was not found in the ABI`) - return compatibleMatch - } - if (compatibleMatches.length > 1) { - throw new Error(`Function "${functionName}" is overloaded and remained ambiguous for the provided argument shape`) - } - } - - throw new Error(`Function "${functionName}" is overloaded and could not be resolved from ${argumentCount.toString()} arguments`) -} - -function canEncodeFunctionArguments(abiItem: AbiParameter, args: readonly unknown[] | undefined) { - try { - const method = getContractMethod(abiItem) - method.encodeInput(normalizeCodecArguments(abiItem.inputs, args)) - return true - } catch (error) { - if (error instanceof Error) return false - return false - } -} - -export function getNamedEventAbi(abi: readonly unknown[], eventName: string) { - for (const entry of normalizeAbi(abi)) { - if (entry.type !== 'event') continue - if (entry.name === eventName) return entry - } - throw new Error(`Event "${eventName}" was not found in the ABI`) -} - -export function getContractMethod(abiItem: AbiParameter) { - if (abiItem.name === undefined) throw new Error('ABI function is missing a name') - const contract = createContract([normalizeFunctionAbiForEncoder(abiItem)] as never) as Record< - string, - { - decodeOutput: (value: Uint8Array) => unknown - encodeInput: (value: unknown) => Uint8Array - } - > - const method = contract[abiItem.name] - if (method === undefined) throw new Error(`Function "${abiItem.name}" could not be created`) - return method -} - -function normalizeDecodeFunctionArgs(value: unknown) { - if (value === undefined) return [] - return Array.isArray(value) ? value : [value] -} - -export function decodeFunctionOutput(abiItem: AbiParameter, data: Hex) { - const method = getContractMethod(abiItem) - return normalizeDecodedFunctionOutput(abiItem, method.decodeOutput(nobleHexToBytes(stripHexPrefix(data)))) -} - -function rlpEncodeBytes(value: Uint8Array): Uint8Array { - if (value.length === 1 && value[0] !== undefined && value[0] < 0x80) return value - if (value.length <= 55) return concatBytes(Uint8Array.of(0x80 + value.length), value) - const lengthBytes = bigintToBytes(BigInt(value.length)) - return concatBytes(Uint8Array.of(0xb7 + lengthBytes.length), lengthBytes, value) -} - -function rlpEncodeList(items: readonly Uint8Array[]) { - const payload = concatBytes(...items) - if (payload.length <= 55) return concatBytes(Uint8Array.of(0xc0 + payload.length), payload) - const lengthBytes = bigintToBytes(BigInt(payload.length)) - return concatBytes(Uint8Array.of(0xf7 + lengthBytes.length), lengthBytes, payload) -} - -function bigintToBytes(value: bigint) { - if (value === 0n) return new Uint8Array([]) - let hex = value.toString(16) - hex = ensureEvenHex(hex) - return nobleHexToBytes(hex) -} - -function checksumAddressFromBytes(value: Uint8Array) { - return getAddress(ensure0x(nobleBytesToHex(value).slice(-40))) -} - -export function normalizeEventTopicArgs(eventAbi: AbiParameter, args: readonly unknown[] | Record | undefined) { - const inputs = eventAbi.inputs ?? [] - const hasNames = inputs.every((input: AbiParameter) => input.name !== undefined) - const normalizeTopicValue = (input: AbiParameter, value: unknown) => { - if (value === null || value === undefined) return null - if (input.type === 'bytes' && typeof value === 'string' && isHex(value, { strict: true })) return hexToBytes(value) - return normalizeCodecValue(input, value) - } - if (args === undefined) { - if (hasNames) { - return Object.fromEntries(inputs.map((input: AbiParameter) => [input.name as string, null])) - } - return inputs.map(() => null) - } - if (!hasNames || Array.isArray(args)) { - let indexedInputIndex = 0 - const usesFullInputArray = Array.isArray(args) && args.length === inputs.length - return inputs.map((input, inputIndex) => { - if (input.indexed !== true) return null - const value = Array.isArray(args) ? args[usesFullInputArray ? inputIndex : indexedInputIndex] : undefined - indexedInputIndex += 1 - return normalizeTopicValue(input, value) - }) - } - return Object.fromEntries( - inputs.map(input => { - const name = input.name - if (name === undefined) throw new Error('ABI event input name is missing') - return [name, input.indexed === true ? normalizeTopicValue(input, Reflect.get(args, name)) : null] - }), - ) -} - -function createDecodeError(name: string, message: string) { - const error = new Error(message) - error.name = name - return error -} - -export function getEventDecoder(eventAbi: AbiParameter) { - if (eventAbi.name === undefined) throw new Error('ABI event is missing a name') - const contractEvents = events([eventAbi as never]) as Record< - string, - { - decode: (topics: string[], data: string) => unknown - topics: (values: readonly unknown[] | Record) => (string | null)[] - } - > - const eventDecoder = contractEvents[eventAbi.name] - if (eventDecoder === undefined) throw new Error(`Event "${eventAbi.name}" could not be created`) - return eventDecoder -} - -function getAbiSignature(parameter: AbiParameter): string { - if (parameter.type === 'function' || parameter.type === 'event') { - return `${parameter.name ?? 'function'}(${(parameter.inputs ?? []).map((input: AbiParameter) => getAbiSignature(input)).join(',')})` - } - if (parameter.type.startsWith('tuple')) { - return `(${(parameter.components ?? []).map((component: AbiParameter) => getAbiSignature(component)).join(',')})${parameter.type.slice(5)}` - } - return parameter.type -} - -export function getEventSignatureHash(eventAbi: AbiParameter) { - return stripHexPrefix(keccak256(getAbiSignature(eventAbi))).toLowerCase() -} - -function ensureConstructorAbi(abi: readonly unknown[]) { - const normalizedAbi = normalizeAbi(abi) - return normalizedAbi.some(entry => entry.type === 'constructor') - ? normalizedAbi - : [ - ...normalizedAbi, - { - inputs: [], - type: 'constructor', - } satisfies AbiParameter, - ] -} - -export function getAddress(value: string): Address { - if (value.startsWith('0X')) throw new Error(`Invalid address: ${value}`) - const parsed = addr.parse(value) - if (!addr.isValid(value)) throw new Error(`Invalid address: ${value}`) - return ensure0x(addr.addChecksum(parsed.hasPrefix ? value : parsed.data)) as Address -} - -export function isAddress(value: string) { - if (value.startsWith('0X')) return false - return addr.isValid(value) -} - -export function isHex(value: string, options: { strict?: boolean | undefined } = {}) { - if (options.strict === true && !value.startsWith('0x')) return false - if (!value.startsWith('0x')) return false - if (value === '0x') return true - const normalized = stripHexPrefix(value) - return isHexCharacter(normalized) -} - -export function bytesToHex(value: Uint8Array) { - return ensure0x(nobleBytesToHex(value)) -} - -export function hexToBytes(value: Hex | string) { - return nobleHexToBytes(ensureEvenHex(stripHexPrefix(value))) -} - -export function concatHex(values: readonly Hex[]) { - return ensure0x(values.map(value => stripHexPrefix(value)).join('')) -} - -export function toHex(value: bigint | number | string | Uint8Array, options: { size?: number | undefined } = {}) { - if (typeof value === 'string') { - return ensure0x(nobleBytesToHex(utf8ToBytes(value))) - } - if (typeof value === 'bigint' || typeof value === 'number') { - const bigintValue = normalizeQuantityValue(value) - if (options.size === undefined) return hexQuantity(bigintValue) - const bytes = bigintToBytes(bigintValue) - if (bytes.length > options.size) throw new Error(`Value exceeds requested size of ${options.size.toString()} bytes`) - return ensure0x(nobleBytesToHex(Uint8Array.from([...new Uint8Array(options.size - bytes.length), ...bytes]))) - } - const bytes = value - if (options.size === undefined) return ensure0x(nobleBytesToHex(bytes)) - if (bytes.length > options.size) throw new Error(`Value exceeds requested size of ${options.size.toString()} bytes`) - return ensure0x(nobleBytesToHex(Uint8Array.from([...new Uint8Array(options.size - bytes.length), ...bytes]))) -} - -export function numberToBytes(value: bigint | number, options: { size?: number | undefined } = {}) { - const bytes = bigintToBytes(normalizeQuantityValue(value)) - if (options.size === undefined) return bytes - if (bytes.length > options.size) throw new Error(`Value exceeds requested size of ${options.size.toString()} bytes`) - return Uint8Array.from([...new Uint8Array(options.size - bytes.length), ...bytes]) -} - -export function keccak256(value: Hex | Uint8Array | string) { - if (typeof value === 'string' && value.startsWith('0x')) { - return ensure0x(nobleBytesToHex(keccak_256(hexToBytes(value)))) - } - const bytes = typeof value === 'string' ? utf8ToBytes(value) : value - return ensure0x(nobleBytesToHex(keccak_256(bytes))) -} - -export function encodeAbiParameters(parameters: readonly AbiParameter[], values: readonly unknown[]) { - return deployContract( - [ - { - inputs: parameters.map(parameter => cloneAbiParameter(parameter, { stripName: false })), - type: 'constructor', - }, - ], - '0x', - normalizeCodecArguments(parameters, values), - ) as Hex -} - -export function encodeFunctionData(parameters: { abi: readonly unknown[]; args?: readonly unknown[]; functionName: string }): Hex -export function encodeFunctionData(parameters: { abi: readonly unknown[]; args?: readonly unknown[]; functionName: string }) { - const abiItem = getNamedFunctionAbi(parameters.abi, parameters.functionName, parameters.args) - const method = getContractMethod(abiItem) - return ensure0x(nobleBytesToHex(method.encodeInput(normalizeCodecArguments(abiItem.inputs, parameters.args)))) -} - -export function decodeFunctionData(parameters: { abi: TAbi; data: Hex }): DecodedFunctionData -export function decodeFunctionData(parameters: { abi: Abi; data: Hex }): { - args: readonly unknown[] - functionName: string -} -export function decodeFunctionData(parameters: { abi: Abi; data: Hex }) { - const strippedAbi = normalizeAbi(parameters.abi) - .filter((entry: AbiParameter) => entry.type === 'function') - .map((entry: AbiParameter) => ({ - ...normalizeFunctionAbiForCodec(entry), - outputs: entry.outputs, - })) - const decoder = new Decoder() - decoder.add(zeroAddress, strippedAbi as never) - const decoded = decoder.decode(zeroAddress, nobleHexToBytes(stripHexPrefix(parameters.data)), {}) - if (decoded === undefined || Array.isArray(decoded)) throw new Error('Function selector was not found in the ABI') - const functionAbi = getNamedFunctionAbi(parameters.abi, decoded.signature ?? decoded.name, normalizeDecodeFunctionArgs(decoded.value)) - return { - args: normalizeDecodedArguments(functionAbi.inputs ?? [], decoded.value), - functionName: decoded.name, - } -} - -function encodeDeploymentWithMicroEthSigner(abi: Abi, bytecode: Hex, constructorArguments: readonly unknown[]) { - const deploymentEncoder = deployContract as (...args: readonly unknown[]) => unknown - const encoded = deploymentEncoder(...[abi, bytecode, ...constructorArguments]) - if (typeof encoded !== 'string' || !isHex(encoded, { strict: true })) { - throw new Error('Contract deployment encoding returned an invalid hex value') - } - return normalizeRpcHex(encoded) -} - -export function encodeDeployData(parameters: { abi: Abi; args?: readonly unknown[]; bytecode: Hex }) { - const constructorAbi = ensureConstructorAbi(parameters.abi) - const constructorParameters = constructorAbi.find(entry => entry.type === 'constructor')?.inputs ?? [] - const constructorArguments = constructorParameters.length === 0 ? [] : [normalizeCodecArguments(constructorParameters, parameters.args)] - return encodeDeploymentWithMicroEthSigner(constructorAbi, parameters.bytecode, constructorArguments) -} - -export function decodeEventLog(parameters: { abi: TAbi; data: Hex; topics: readonly Hex[] }): DecodedEventLog -export function decodeEventLog(parameters: { abi: Abi; data: Hex; topics: readonly Hex[] }): { - args: TupleValue - eventName: string -} -export function decodeEventLog(parameters: { abi: Abi; data: Hex; topics: readonly Hex[] }) { - const selector = parameters.topics[0] - if (selector === undefined) throw createDecodeError('DecodeLogTopicsMismatch', 'Event topics were missing') - const matchingEvent = normalizeAbi(parameters.abi).find((entry: AbiParameter) => entry.type === 'event' && getEventSignatureHash(entry) === stripHexPrefix(selector).toLowerCase()) - if (matchingEvent === undefined || matchingEvent.name === undefined) { - throw createDecodeError('AbiEventSignatureNotFoundError', 'Event signature was not found in the ABI') - } - try { - const decodedArgs = getEventDecoder(matchingEvent).decode(parameters.topics as string[], parameters.data) - return { - args: normalizeDecodedTuple(matchingEvent.inputs ?? [], decodedArgs), - eventName: matchingEvent.name, - } - } catch (error) { - if (error instanceof Error && error.message.toLowerCase().includes('topic')) { - throw createDecodeError('DecodeLogTopicsMismatch', error.message) - } - if (error instanceof Error) throw createDecodeError('DecodeLogDataMismatch', error.message) - throw createDecodeError('DecodeLogDataMismatch', 'Failed to decode event log') - } -} - -export function encodeEventTopics(parameters: { abi: Abi; args?: readonly unknown[] | Record | undefined; eventName: string }) { - const eventAbi = getNamedEventAbi(parameters.abi, parameters.eventName) - return getEventDecoder(eventAbi) - .topics(normalizeEventTopicArgs(eventAbi, parameters.args)) - .map((topic: string | null) => (topic === null ? null : ensure0x(topic))) -} - -export function parseTransaction(serializedTransaction: Hex) { - const transaction = Transaction.fromHex(serializedTransaction) - return { - chainId: 'chainId' in transaction.raw && typeof transaction.raw.chainId === 'bigint' ? transaction.raw.chainId : undefined, - data: normalizeHexData(transaction.raw.data), - gas: 'gasLimit' in transaction.raw ? transaction.raw.gasLimit : undefined, - gasPrice: 'gasPrice' in transaction.raw && typeof transaction.raw.gasPrice === 'bigint' ? transaction.raw.gasPrice : undefined, - maxFeePerGas: 'maxFeePerGas' in transaction.raw && typeof transaction.raw.maxFeePerGas === 'bigint' ? transaction.raw.maxFeePerGas : undefined, - maxPriorityFeePerGas: 'maxPriorityFeePerGas' in transaction.raw && typeof transaction.raw.maxPriorityFeePerGas === 'bigint' ? transaction.raw.maxPriorityFeePerGas : undefined, - nonce: 'nonce' in transaction.raw ? transaction.raw.nonce : undefined, - to: transaction.raw.to === '0x' ? undefined : getAddress(transaction.raw.to), - type: transaction.type, - value: 'value' in transaction.raw ? transaction.raw.value : undefined, - } satisfies ParsedTransaction -} - -export async function recoverTransactionAddress(parameters: { serializedTransaction: Hex }) { - return getAddress(Transaction.fromHex(parameters.serializedTransaction).sender) -} - -export function privateKeyToAccount(privateKey: Hex) { - return { - address: getAddress(addr.fromPrivateKey(privateKey)), - signMessage: async message => ensure0x(eip191Signer.sign(message, privateKey)), - signTransaction: async parameters => { - const type = parameters.gasPrice !== undefined ? 'legacy' : 'eip1559' - const transaction = Transaction.prepare({ - chainId: hexToBigInt(parameters.chainId) ?? 1n, - data: parameters.data ?? '0x', - gasLimit: hexToBigInt(parameters.gas) ?? 21_000n, - ...(type === 'legacy' - ? { - gasPrice: parameters.gasPrice ?? 0n, - type, - } - : { - maxFeePerGas: parameters.maxFeePerGas ?? parameters.maxPriorityFeePerGas ?? 0n, - maxPriorityFeePerGas: parameters.maxPriorityFeePerGas ?? 0n, - type, - }), - nonce: hexToBigInt(parameters.nonce) ?? 0n, - to: parameters.to ?? '0x', - value: parameters.value ?? 0n, - }) - return transaction.signBy(privateKey).toHex() as Hex - }, - type: 'local', - } satisfies Account -} - -export function getCreateAddress(parameters: { from: Address; nonce: bigint }) { - const fromBytes = nobleHexToBytes(stripHexPrefix(parameters.from)) - const nonceBytes = parameters.nonce === 0n ? new Uint8Array([]) : bigintToBytes(parameters.nonce) - const encoded = rlpEncodeList([rlpEncodeBytes(fromBytes), rlpEncodeBytes(nonceBytes)]) - return checksumAddressFromBytes(keccak_256(encoded).slice(-20)) -} - -export function getCreate2Address(parameters: { bytecode?: Hex | undefined; bytecodeHash?: Hex | undefined; from: Address; salt: Hex | Uint8Array }) { - const fromBytes = nobleHexToBytes(stripHexPrefix(parameters.from)) - const saltBytes = parameters.salt instanceof Uint8Array ? parameters.salt : hexToBytes(parameters.salt) - if (saltBytes.length !== 32) throw new Error('CREATE2 salt must be 32 bytes') - const bytecodeHashBytes = (() => { - if (parameters.bytecodeHash !== undefined) return hexToBytes(parameters.bytecodeHash) - if (parameters.bytecode === undefined) return undefined - return keccak_256(hexToBytes(parameters.bytecode)) - })() - if (bytecodeHashBytes === undefined) throw new Error('CREATE2 address derivation requires bytecode or bytecodeHash') - const encoded = concatBytes(Uint8Array.of(0xff), fromBytes, saltBytes, bytecodeHashBytes) - return checksumAddressFromBytes(keccak_256(encoded).slice(-20)) -} - -export function parseUnits(value: string, decimals: number) { - const trimmed = value.trim() - if (!/^-?(?:\d+\.?\d*|\.\d+)$/.test(trimmed)) throw new Error(`Invalid decimal value: ${value}`) - const negative = trimmed.startsWith('-') - const normalized = negative ? trimmed.slice(1) : trimmed - const [wholePartRaw, fractionPartRaw = ''] = normalized.split('.') - const wholePart = wholePartRaw === '' ? '0' : wholePartRaw - const trimmedFraction = fractionPartRaw.replace(/0+$/, '') - if (trimmedFraction.length > decimals) throw new Error(`Too many decimal places: expected at most ${decimals.toString()}`) - const paddedFraction = trimmedFraction.padEnd(decimals, '0') - const combined = `${wholePart}${paddedFraction}`.replace(/^0+/, '') || '0' - const result = BigInt(combined) - return negative ? -result : result -} - -export function formatUnits(value: bigint, decimals: number) { - const negative = value < 0n - const normalized = negative ? -value : value - const base = 10n ** BigInt(decimals) - const whole = normalized / base - const fraction = normalized % base - if (fraction === 0n) return `${negative ? '-' : ''}${whole.toString()}` - const fractionString = fraction.toString().padStart(decimals, '0').replace(/0+$/, '') - return `${negative ? '-' : ''}${whole.toString()}.${fractionString}` -} - -export function formatEther(value: bigint) { - return formatUnits(value, 18) -} diff --git a/bots/shared/src/ethereum/human-readable-abi.ts b/bots/shared/src/ethereum/human-readable-abi.ts deleted file mode 100644 index d83095c63..000000000 --- a/bots/shared/src/ethereum/human-readable-abi.ts +++ /dev/null @@ -1,164 +0,0 @@ -import type { AbiParameter } from './types' - -function findMatchingParenthesis(value: string, openingIndex: number) { - let depth = 0 - for (let index = openingIndex; index < value.length; ++index) { - const character = value[index] - if (character === '(') { - depth += 1 - continue - } - if (character !== ')') continue - depth -= 1 - if (depth === 0) return index - } - throw new Error(`Unable to parse ABI item: ${value}`) -} - -function splitTopLevelCommaSeparated(value: string) { - const entries: string[] = [] - let current = '' - let depth = 0 - for (const character of value) { - if (character === '(') { - depth += 1 - current += character - continue - } - if (character === ')') { - depth -= 1 - if (depth < 0) throw new Error(`Unable to parse ABI item: ${value}`) - current += character - continue - } - if (character === ',' && depth === 0) { - const trimmedEntry = current.trim() - if (trimmedEntry !== '') entries.push(trimmedEntry) - current = '' - continue - } - current += character - } - if (depth !== 0) throw new Error(`Unable to parse ABI item: ${value}`) - const finalEntry = current.trim() - if (finalEntry !== '') entries.push(finalEntry) - return entries -} - -function canonicalizeHumanReadableAbiType(type: string) { - const typeMatch = /^(?[^\[]+)(?(?:\[[0-9]*\])*)$/u.exec(type) - if (typeMatch === null) return type - const baseType = typeMatch.groups?.['baseType'] - const arraySuffix = typeMatch.groups?.['arraySuffix'] ?? '' - if (baseType === undefined) return type - const canonicalBaseType = (() => { - if (baseType === 'uint') return 'uint256' - if (baseType === 'int') return 'int256' - if (baseType === 'byte') return 'bytes1' - if (baseType === 'fixed') return 'fixed128x18' - if (baseType === 'ufixed') return 'ufixed128x18' - return baseType - })() - return `${canonicalBaseType}${arraySuffix}` -} - -function parseAbiParameterEntry(entry: string): AbiParameter { - const trimmedEntry = entry.trim() - if (trimmedEntry === '') throw new Error(`Unable to parse ABI parameter: ${entry}`) - const indexed = /(?:^|\s)indexed(?:\s|$)/u.test(trimmedEntry) - const sanitizedEntry = trimmedEntry - .replace(/\b(?:indexed|memory|calldata|storage)\b/gu, ' ') - .replace(/\s+/gu, ' ') - .trim() - - if (/^(?:tuple\s*)?\(/u.test(sanitizedEntry)) { - const openingIndex = sanitizedEntry.indexOf('(') - const closingIndex = findMatchingParenthesis(sanitizedEntry, openingIndex) - const componentsSource = sanitizedEntry.slice(openingIndex + 1, closingIndex) - const trailingSource = sanitizedEntry.slice(closingIndex + 1).trim() - const tupleMatch = /^(?(?:\[[0-9]*\])*)(?:\s*(?[A-Za-z_][A-Za-z0-9_]*))?$/u.exec(trailingSource) - if (tupleMatch === null) throw new Error(`Unable to parse ABI parameter: ${entry}`) - const arraySuffix = tupleMatch.groups?.['arraySuffix'] ?? '' - const name = tupleMatch.groups?.['name'] - return { - ...(indexed ? { indexed } : {}), - ...(name === undefined ? {} : { name }), - components: parseParameterList(componentsSource), - type: `tuple${arraySuffix}`, - } - } - - const parameterMatch = /^(?\S+)(?:\s+(?[A-Za-z_][A-Za-z0-9_]*))?$/u.exec(sanitizedEntry) - if (parameterMatch === null) throw new Error(`Unable to parse ABI parameter: ${entry}`) - const type = parameterMatch.groups?.['type'] - const name = parameterMatch.groups?.['name'] - if (type === undefined) throw new Error(`Unable to parse ABI parameter: ${entry}`) - return { - ...(indexed ? { indexed } : {}), - ...(name === undefined ? {} : { name }), - type: canonicalizeHumanReadableAbiType(type), - } -} - -function parseParameterList(value: string) { - if (value.trim() === '') return [] - return splitTopLevelCommaSeparated(value).map(parseAbiParameterEntry) -} - -export function parseAbiParameters(value: string) { - return parseParameterList(value) -} - -export function parseAbiItem(value: string) { - const trimmed = value.trim() - const functionHeaderMatch = /^function\s+(?[A-Za-z_][A-Za-z0-9_]*)\s*\(/u.exec(trimmed) - if (functionHeaderMatch !== null) { - const name = functionHeaderMatch.groups?.['name'] - if (name === undefined) throw new Error(`Unsupported ABI item string: ${value}`) - const inputsOpeningIndex = trimmed.indexOf('(', functionHeaderMatch[0].length - 1) - const inputsClosingIndex = findMatchingParenthesis(trimmed, inputsOpeningIndex) - const inputSource = trimmed.slice(inputsOpeningIndex + 1, inputsClosingIndex) - const trailingSource = trimmed.slice(inputsClosingIndex + 1).trim() - const returnsMatch = /\breturns\s*\(/u.exec(trailingSource) - const modifiersSource = returnsMatch === null ? trailingSource : trailingSource.slice(0, returnsMatch.index).trim() - const stateMutability = ['pure', 'view', 'payable', 'nonpayable'].find(candidate => new RegExp(`(?:^|\\s)${candidate}(?:\\s|$)`, 'u').test(modifiersSource)) - const unsupportedModifiers = modifiersSource - .replace(/\b(?:external|public|internal|private|pure|view|payable|nonpayable)\b/gu, ' ') - .replace(/\s+/gu, ' ') - .trim() - if (unsupportedModifiers !== '') throw new Error(`Unsupported ABI item string: ${value}`) - - const outputs = (() => { - if (returnsMatch === null) return [] - const returnsOpeningIndex = trailingSource.indexOf('(', returnsMatch.index) - const returnsClosingIndex = findMatchingParenthesis(trailingSource, returnsOpeningIndex) - const trailingAfterReturns = trailingSource.slice(returnsClosingIndex + 1).trim() - if (trailingAfterReturns !== '') throw new Error(`Unsupported ABI item string: ${value}`) - return parseParameterList(trailingSource.slice(returnsOpeningIndex + 1, returnsClosingIndex)) - })() - return { - inputs: parseParameterList(inputSource), - name, - outputs, - ...(stateMutability === undefined ? {} : { stateMutability }), - type: 'function', - } satisfies AbiParameter - } - const eventHeaderMatch = /^event\s+(?[A-Za-z_][A-Za-z0-9_]*)\s*\(/u.exec(trimmed) - if (eventHeaderMatch !== null) { - const name = eventHeaderMatch.groups?.['name'] - if (name === undefined) throw new Error(`Unsupported ABI item string: ${value}`) - const inputsOpeningIndex = trimmed.indexOf('(', eventHeaderMatch[0].length - 1) - const inputsClosingIndex = findMatchingParenthesis(trimmed, inputsOpeningIndex) - const inputSource = trimmed.slice(inputsOpeningIndex + 1, inputsClosingIndex) - const trailingSource = trimmed.slice(inputsClosingIndex + 1).trim() - if (trailingSource !== '' && trailingSource !== 'anonymous') throw new Error(`Unsupported ABI item string: ${value}`) - return { - ...(trailingSource === 'anonymous' ? { anonymous: true } : {}), - inputs: parseParameterList(inputSource), - name, - type: 'event', - } satisfies AbiParameter - } - throw new Error(`Unsupported ABI item string: ${value}`) -} diff --git a/bots/shared/src/ethereum/rpc-normalization.ts b/bots/shared/src/ethereum/rpc-normalization.ts deleted file mode 100644 index efce0191f..000000000 --- a/bots/shared/src/ethereum/rpc-normalization.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { getAddress, hexQuantity, normalizeAddress, normalizeBoolean, normalizeHash, normalizeNullableAddress, normalizeRpcBigInt, normalizeRpcHex, normalizeTransactionType } from './codec' -import type { Account, Address, Block, BlockTransaction, Hex, ReplacementReason, TransactionLog, TransactionReceipt } from './types' - -type ReplacementActions = { - getBlock: (parameters: { blockNumber: bigint; includeTransactions: true }) => Promise -} - -export function normalizeLog(value: unknown): TransactionLog { - if (typeof value !== 'object' || value === null) throw new Error('RPC returned an invalid log') - const log = value as Record - return { - address: normalizeAddress(log['address']), - blockHash: log['blockHash'] === undefined || log['blockHash'] === null ? undefined : normalizeHash(log['blockHash']), - blockNumber: log['blockNumber'] === undefined || log['blockNumber'] === null ? undefined : normalizeRpcBigInt(log['blockNumber']), - data: normalizeRpcHex(log['data']), - logIndex: log['logIndex'] === undefined || log['logIndex'] === null ? undefined : normalizeRpcBigInt(log['logIndex']), - removed: normalizeBoolean(log['removed']), - topics: Array.isArray(log['topics']) ? log['topics'].map(topic => normalizeRpcHex(topic)) : [], - transactionHash: log['transactionHash'] === undefined || log['transactionHash'] === null ? undefined : normalizeHash(log['transactionHash']), - transactionIndex: log['transactionIndex'] === undefined || log['transactionIndex'] === null ? undefined : normalizeRpcBigInt(log['transactionIndex']), - } -} - -export function normalizeReceipt(value: unknown): TransactionReceipt { - if (typeof value !== 'object' || value === null) throw new Error('RPC returned an invalid transaction receipt') - const receipt = value as Record - return { - blockHash: normalizeHash(receipt['blockHash']), - blockNumber: normalizeRpcBigInt(receipt['blockNumber']), - contractAddress: normalizeNullableAddress(receipt['contractAddress']) ?? null, - cumulativeGasUsed: normalizeRpcBigInt(receipt['cumulativeGasUsed']), - effectiveGasPrice: receipt['effectiveGasPrice'] === undefined ? undefined : normalizeRpcBigInt(receipt['effectiveGasPrice']), - from: normalizeAddress(receipt['from']), - gasUsed: normalizeRpcBigInt(receipt['gasUsed']), - logs: Array.isArray(receipt['logs']) ? receipt['logs'].map(item => normalizeLog(item)) : [], - logsBloom: receipt['logsBloom'] === undefined ? undefined : normalizeRpcHex(receipt['logsBloom']), - status: normalizeBoolean(receipt['status']) ? 'success' : 'reverted', - to: normalizeNullableAddress(receipt['to']) ?? null, - transactionHash: normalizeHash(receipt['transactionHash']), - transactionIndex: normalizeRpcBigInt(receipt['transactionIndex']), - type: normalizeTransactionType(receipt['type']), - } -} - -export function normalizeTransaction(value: unknown): BlockTransaction { - if (typeof value !== 'object' || value === null) throw new Error('RPC returned an invalid transaction') - const transaction = value as Record - return { - blockNumber: transaction['blockNumber'] === undefined || transaction['blockNumber'] === null ? undefined : normalizeRpcBigInt(transaction['blockNumber']), - from: normalizeAddress(transaction['from']), - gas: normalizeRpcBigInt(transaction['gas']), - gasPrice: transaction['gasPrice'] === undefined || transaction['gasPrice'] === null ? undefined : normalizeRpcBigInt(transaction['gasPrice']), - hash: normalizeHash(transaction['hash']), - input: normalizeRpcHex(transaction['input'] ?? transaction['data'] ?? '0x'), - maxFeePerGas: transaction['maxFeePerGas'] === undefined || transaction['maxFeePerGas'] === null ? undefined : normalizeRpcBigInt(transaction['maxFeePerGas']), - maxPriorityFeePerGas: transaction['maxPriorityFeePerGas'] === undefined || transaction['maxPriorityFeePerGas'] === null ? undefined : normalizeRpcBigInt(transaction['maxPriorityFeePerGas']), - nonce: normalizeRpcBigInt(transaction['nonce']), - to: normalizeNullableAddress(transaction['to']) ?? null, - transactionIndex: transaction['transactionIndex'] === undefined || transaction['transactionIndex'] === null ? undefined : normalizeRpcBigInt(transaction['transactionIndex']), - type: normalizeTransactionType(transaction['type']), - value: normalizeRpcBigInt(transaction['value']), - } -} - -export function normalizeBlock(value: unknown, includeTransactions: boolean) { - if (typeof value !== 'object' || value === null) throw new Error('RPC returned an invalid block') - const block = value as Record - return { - baseFeePerGas: block['baseFeePerGas'] === undefined || block['baseFeePerGas'] === null ? undefined : normalizeRpcBigInt(block['baseFeePerGas']), - hash: block['hash'] === undefined || block['hash'] === null ? undefined : normalizeHash(block['hash']), - number: block['number'] === undefined || block['number'] === null ? undefined : normalizeRpcBigInt(block['number']), - parentHash: block['parentHash'] === undefined || block['parentHash'] === null ? undefined : normalizeHash(block['parentHash']), - timestamp: normalizeRpcBigInt(block['timestamp']), - transactions: Array.isArray(block['transactions']) ? block['transactions'].map(transaction => (includeTransactions ? normalizeTransaction(transaction) : normalizeHash(transaction))) : [], - } satisfies Block -} - -export function isBlockTransaction(value: unknown): value is BlockTransaction { - return typeof value === 'object' && value !== null && 'hash' in value && 'from' in value && 'nonce' in value -} - -export function isTransactionNotFoundError(error: unknown) { - return error instanceof Error && error.message.includes('could not be found') -} - -export function getReplacementReason(originalTransaction: BlockTransaction, replacementTransaction: BlockTransaction): ReplacementReason { - if (replacementTransaction.to?.toLowerCase() === originalTransaction.from.toLowerCase() && replacementTransaction.value === 0n && replacementTransaction.input === '0x') return 'cancelled' - if (replacementTransaction.to?.toLowerCase() === originalTransaction.to?.toLowerCase() && replacementTransaction.value === originalTransaction.value && replacementTransaction.input === originalTransaction.input) return 'repriced' - return 'replaced' -} - -export const REPLACEMENT_SCAN_BLOCK_DEPTH = 12n - -export async function findReplacementTransaction(actions: ReplacementActions, originalTransaction: BlockTransaction, parameters: { fromBlock: bigint; toBlock: bigint }) { - for (let blockNumber = parameters.fromBlock; blockNumber <= parameters.toBlock; blockNumber += 1n) { - const block = await actions.getBlock({ - blockNumber, - includeTransactions: true, - }) - const replacementTransaction = block.transactions.find((transaction): transaction is BlockTransaction => isBlockTransaction(transaction) && transaction.hash !== originalTransaction.hash && transaction.nonce === originalTransaction.nonce && transaction.from.toLowerCase() === originalTransaction.from.toLowerCase()) - if (replacementTransaction !== undefined) return replacementTransaction - } - return undefined -} - -export function buildRpcTransactionRequest(parameters: { - account?: Account | Address | undefined - amount?: bigint | undefined - data?: Hex | undefined - gas?: bigint | undefined - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined - nonce?: bigint | number | undefined - to?: Address | null | undefined - value?: bigint | undefined -}) { - const from = normalizeAccountAddress(parameters.account) - const value = parameters.value ?? parameters.amount - return { - ...(from === undefined ? {} : { from }), - ...(parameters.to === undefined || parameters.to === null ? {} : { to: parameters.to }), - ...(parameters.data === undefined ? {} : { data: parameters.data }), - ...(parameters.gas === undefined ? {} : { gas: hexQuantity(parameters.gas) }), - ...(parameters.gasPrice === undefined ? {} : { gasPrice: hexQuantity(parameters.gasPrice) }), - ...(parameters.maxFeePerGas === undefined ? {} : { maxFeePerGas: hexQuantity(parameters.maxFeePerGas) }), - ...(parameters.maxPriorityFeePerGas === undefined ? {} : { maxPriorityFeePerGas: hexQuantity(parameters.maxPriorityFeePerGas) }), - ...(parameters.nonce === undefined ? {} : { nonce: hexQuantity(parameters.nonce) }), - ...(value === undefined ? {} : { value: hexQuantity(value) }), - } -} - -export function normalizeAccountAddress(account: Account | Address | undefined) { - if (account === undefined) return undefined - return typeof account === 'string' ? getAddress(account) : account.address -} diff --git a/bots/shared/src/ethereum/rpc-resilience.ts b/bots/shared/src/ethereum/rpc-resilience.ts index 9aae75d5a..10085c571 100644 --- a/bots/shared/src/ethereum/rpc-resilience.ts +++ b/bots/shared/src/ethereum/rpc-resilience.ts @@ -52,6 +52,7 @@ function safeErrorMessage(error: unknown, url: string, target: string) { function retryableRpcFailure(error: unknown) { if (error instanceof RpcEndpointPoolFailure) return true if (error instanceof RpcError) { + if (typeof error.code === 'number') return error.code === 408 || error.code === 425 || error.code === 429 || error.code >= 500 if (error.code !== undefined) return false const message = error.message.toLowerCase() return message.includes('timed out') || /^http (408|425|429|5\d\d)\b/.test(message) @@ -133,9 +134,7 @@ export function createRpcEndpointPool(urls: readonly string[], options: RpcEndpo const failures: { error: string; target: string }[] = [] const candidates = orderedEndpoints(now()) if (candidates.length === 0) { - throw new RpcEndpointPoolFailure( - endpoints.map(endpoint => ({ error: `cooling down until ${endpoint.nextRetryAt ?? 'the next retry window'}`, target: endpoint.target })), - ) + throw new RpcEndpointPoolFailure(endpoints.map(endpoint => ({ error: `cooling down until ${endpoint.nextRetryAt ?? 'the next retry window'}`, target: endpoint.target }))) } for (const endpoint of candidates) { try { diff --git a/bots/shared/src/ethereum/rpc-transport.ts b/bots/shared/src/ethereum/rpc-transport.ts index 7e8ccfd62..56f51bf31 100644 --- a/bots/shared/src/ethereum/rpc-transport.ts +++ b/bots/shared/src/ethereum/rpc-transport.ts @@ -1,104 +1,15 @@ +import { custom as sharedCustom, http as sharedHttp, requestRpc, RpcError, type EIP1193Provider, type Transport } from '@zoltar/shared/ethereum' import { boundedJsonResponse, DEFAULT_RPC_RESPONSE_BYTES, LOG_RPC_RESPONSE_BYTES } from '../infrastructure/bounded-json.ts' -import type { EIP1193Provider, Transport } from './types.ts' -export class RpcError extends Error { - code?: number | string | undefined - override cause?: unknown - shortMessage?: string | undefined - - constructor(message: string, options: { cause?: unknown; code?: number | string | undefined; shortMessage?: string | undefined } = {}) { - super(message) - this.name = 'RpcError' - this.code = options.code - this.cause = options.cause - this.shortMessage = options.shortMessage - } -} +export { RpcError } type ClientRequestParameters = { method: string params?: unknown } -type JsonRpcEnvelope = { - error?: { - code: number | string - data?: unknown - message: string - } - id: number - jsonrpc: '2.0' - result?: unknown -} - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - -function jsonRpcEnvelope(value: unknown, method: string): JsonRpcEnvelope { - if (!isRecord(value)) throw new RpcError(`Invalid JSON-RPC envelope while calling ${method}`) - const record = value - if (record['jsonrpc'] !== '2.0' || record['id'] !== 1) throw new RpcError(`Invalid JSON-RPC envelope while calling ${method}`) - const hasResult = Object.prototype.hasOwnProperty.call(record, 'result') - const hasError = Object.prototype.hasOwnProperty.call(record, 'error') - if (hasResult === hasError) throw new RpcError(`Invalid JSON-RPC envelope while calling ${method}`) - if (hasError) { - const error = record['error'] - if (!isRecord(error)) throw new RpcError(`Invalid JSON-RPC error while calling ${method}`) - const errorRecord = error - if ((typeof errorRecord['code'] !== 'number' && typeof errorRecord['code'] !== 'string') || typeof errorRecord['message'] !== 'string') { - throw new RpcError(`Invalid JSON-RPC error while calling ${method}`) - } - return { - error: { code: errorRecord['code'], data: errorRecord['data'], message: errorRecord['message'] }, - id: 1, - jsonrpc: '2.0', - } - } - return { id: 1, jsonrpc: '2.0', result: record['result'] } -} - -function toRpcError(error: unknown, fallbackMessage: string) { - if (error instanceof RpcError) return error - if (typeof error === 'object' && error !== null) { - const code = 'code' in error && (typeof error.code === 'number' || typeof error.code === 'string') ? error.code : undefined - const message = 'message' in error && typeof error.message === 'string' ? error.message : fallbackMessage - return new RpcError(message, { cause: error, code, shortMessage: message }) - } - return new RpcError(error instanceof Error ? error.message : fallbackMessage, { - cause: error, - shortMessage: error instanceof Error ? error.message : fallbackMessage, - }) -} - export async function requestTransport(transport: Transport, parameters: ClientRequestParameters): Promise { - if (transport.kind === 'custom') { - let timeout: ReturnType | undefined - try { - return (await Promise.race([ - transport.provider.request({ method: parameters.method, params: parameters.params }), - new Promise((_resolve, reject) => { - timeout = setTimeout(() => reject(new Error(`${parameters.method} timed out after ${transport.timeoutMilliseconds.toString()}ms`)), transport.timeoutMilliseconds) - }), - ])) as TValue - } catch (error) { - throw toRpcError(error, `${parameters.method} failed`) - } finally { - if (timeout !== undefined) clearTimeout(timeout) - } - } - - const response = await fetch(transport.url, { - body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: parameters.method, params: parameters.params ?? [] }), - headers: { 'content-type': 'application/json' }, - method: 'POST', - redirect: 'error', - signal: AbortSignal.timeout(transport.timeoutMilliseconds), - }) - if (!response.ok) throw new RpcError(`HTTP ${response.status} while calling ${parameters.method}`, { shortMessage: `HTTP ${response.status} while calling ${parameters.method}` }) - const envelope = jsonRpcEnvelope(await boundedJsonResponse(response, parameters.method === 'eth_getLogs' ? LOG_RPC_RESPONSE_BYTES : DEFAULT_RPC_RESPONSE_BYTES, `RPC ${parameters.method}`), parameters.method) - if (envelope.error !== undefined) throw new RpcError(envelope.error.message, { cause: envelope.error.data, code: envelope.error.code, shortMessage: envelope.error.message }) - return envelope.result as TValue + return await requestRpc(transport, parameters) } export type TransportOptions = { @@ -114,9 +25,32 @@ function transportTimeout(options: TransportOptions | undefined) { } export function http(url: string, options?: TransportOptions) { - return { kind: 'http', timeoutMilliseconds: transportTimeout(options), url } satisfies Transport + const requestTimeout = transportTimeout(options) + return sharedHttp(url, { + requestTimeout, + responseParser: (response, method) => boundedJsonResponse(response, method === 'eth_getLogs' ? LOG_RPC_RESPONSE_BYTES : DEFAULT_RPC_RESPONSE_BYTES, `RPC ${method}`), + retryCount: 0, + }) } export function custom(provider: EIP1193Provider, options?: TransportOptions) { - return { kind: 'custom', provider, timeoutMilliseconds: transportTimeout(options) } satisfies Transport + const timeoutMilliseconds = transportTimeout(options) + return sharedCustom( + { + request: async parameters => { + let timeout: ReturnType | undefined + try { + return await Promise.race([ + provider.request(parameters), + new Promise((_resolve, reject) => { + timeout = setTimeout(() => reject(new Error(`${parameters.method} timed out after ${timeoutMilliseconds.toString()}ms`)), timeoutMilliseconds) + }), + ]) + } finally { + if (timeout !== undefined) clearTimeout(timeout) + } + }, + }, + { retryCount: 0 }, + ) } diff --git a/bots/shared/src/ethereum/types.ts b/bots/shared/src/ethereum/types.ts deleted file mode 100644 index ea23ea43e..000000000 --- a/bots/shared/src/ethereum/types.ts +++ /dev/null @@ -1,364 +0,0 @@ -export type Hex = `0x${string}` -export type Address = Hex -export type Hash = Hex -export type AbiParameter = { - readonly anonymous?: boolean - readonly components?: readonly AbiParameter[] - readonly internalType?: string - readonly indexed?: boolean - readonly inputs?: readonly AbiParameter[] - readonly name?: string - readonly outputs?: readonly AbiParameter[] - readonly stateMutability?: string - readonly type: string -} -export type Abi = readonly AbiParameter[] - -export type FixedArrayValue = TAccumulator['length'] extends TLength ? TAccumulator : FixedArrayValue - -export type TupleComponentName = TComponent['name'] -export type AbiValueKind = 'input' | 'output' - -export type TupleComponentsAllNamed = TComponents extends readonly [] ? false : Extract, undefined | ''> extends never ? true : false - -export type TupleComponentsObject = { - readonly [TComponent in TComponents[number] as TComponent['name'] extends string ? TComponent['name'] : never]: AbiParameterValue -} - -export type TupleComponentsArray = Readonly<{ - [TIndex in keyof TComponents]: TComponents[TIndex] extends AbiParameter ? AbiParameterValue : never -}> - -export type TupleValue = TKind extends 'input' - ? TupleComponentsAllNamed extends true - ? TupleComponentsArray | TupleComponentsObject - : TupleComponentsArray - : TupleComponentsArray & (TupleComponentsAllNamed extends true ? TupleComponentsObject : {}) - -export type RebasedAbiParameter = { - readonly anonymous?: boolean - readonly components?: Exclude - readonly internalType?: Exclude - readonly indexed?: boolean - readonly inputs?: Exclude - readonly name?: Exclude - readonly outputs?: Exclude - readonly stateMutability?: Exclude - readonly type: TType -} - -export type ArrayElementValue = TElementType extends 'tuple' - ? TParameter['components'] extends readonly AbiParameter[] - ? TKind extends 'input' - ? TupleValue - : TupleComponentsAllNamed extends true - ? TupleComponentsObject - : TupleComponentsArray - : unknown - : AbiParameterValue, TKind> - -export type AbiParameterValue = string extends TParameter['type'] - ? unknown - : TParameter['type'] extends `${infer TElementType}[${infer TSize}]` - ? TSize extends `${infer TLength extends number}` - ? FixedArrayValue, TLength> - : readonly ArrayElementValue[] - : TParameter['type'] extends 'tuple' - ? TupleValue - : TParameter['type'] extends 'address' - ? Address - : TParameter['type'] extends 'bool' - ? boolean - : TParameter['type'] extends 'bytes' | `bytes${number}` - ? Hex - : TParameter['type'] extends 'function' - ? Hex - : TParameter['type'] extends 'int' | 'uint' | `${'int' | 'uint'}${number}` - ? TKind extends 'input' - ? bigint | number - : bigint - : TParameter['type'] extends 'string' - ? string - : unknown - -export type AbiParametersToValues = TParameters extends readonly AbiParameter[] ? TupleComponentsArray : readonly unknown[] - -export type KnownAbiFunctions = Extract - -export type ContractFunctionName = [KnownAbiFunctions] extends [never] ? string : Extract['name'], string> - -export type ContractFunctionDefinition = [KnownAbiFunctions] extends [never] - ? { - inputs?: readonly AbiParameter[] - outputs?: readonly AbiParameter[] - } - : Extract, { name: TFunctionName }> extends infer TFunction - ? [TFunction] extends [never] - ? { - inputs?: readonly AbiParameter[] - outputs?: readonly AbiParameter[] - } - : TFunction - : never - -export type ContractFunctionInputs = ContractFunctionDefinition extends { - inputs?: infer TInputs extends readonly AbiParameter[] -} - ? TInputs - : readonly AbiParameter[] | undefined - -export type ContractFunctionOutputs = ContractFunctionDefinition extends { - outputs?: infer TOutputs extends readonly AbiParameter[] -} - ? TOutputs - : readonly AbiParameter[] | undefined - -export type ContractFunctionResult = ContractFunctionOutputs extends infer TOutputs extends readonly AbiParameter[] | undefined - ? TOutputs extends readonly [] - ? undefined - : TOutputs extends readonly [infer TOutput extends AbiParameter] - ? AbiParameterValue - : TOutputs extends readonly AbiParameter[] - ? TupleValue - : unknown - : unknown - -export type KnownAbiEvents = Extract - -export type ContractEventName = [KnownAbiEvents] extends [never] ? string : Extract['name'], string> - -export type ContractEventDefinition = [KnownAbiEvents] extends [never] - ? { - inputs?: readonly AbiParameter[] - } - : Extract, { name: TEventName }> - -export type ContractEventArgs = TupleValue['inputs'] extends readonly AbiParameter[] ? ContractEventDefinition['inputs'] : readonly [], 'output'> - -export type DecodedFunctionData = [KnownAbiFunctions] extends [never] - ? { - args: readonly unknown[] - functionName: string - } - : { - [TFunctionName in ContractFunctionName]: { - args: AbiParametersToValues, 'output'> - functionName: TFunctionName - } - }[ContractFunctionName] - -export type DecodedEventLog = [KnownAbiEvents] extends [never] - ? { - args: TupleValue - eventName: string - } - : { - [TEventName in ContractEventName]: { - args: ContractEventArgs - eventName: TEventName - } - }[ContractEventName] - -export type RpcLogForEvent = TEvent extends AbiParameter ? RpcLog : TupleValue, TEvent['name'] extends string ? TEvent['name'] : string> : RpcLog - -export type ContractReadParameters = ContractFunctionParameters & { - account?: Account | Address | undefined - blockNumber?: bigint | undefined - blockTag?: BlockTag | undefined - gas?: bigint | undefined - value?: bigint | undefined -} - -export type ContractSimulateParameters = ContractReadParameters & { - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined -} - -export type ContractWriteParameters = ContractFunctionParameters & { - account?: Account | Address | undefined - gas?: bigint | undefined - value?: bigint | undefined -} - -export type EstimateContractGasParameters = ContractFunctionParameters & { - account?: Account | Address | undefined - value?: bigint | undefined -} - -export type MulticallContractResult = TContract extends ContractFunctionParameters ? ContractFunctionResult : unknown - -export type ContractFunctionParameters = { - abi: TAbi - address: Address - args?: AbiParametersToValues, 'input'> | undefined - functionName: TFunctionName - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined -} - -export type Chain = { - id: number - name: string - nativeCurrency: { - decimals: number - name: string - symbol: string - } - rpcUrls: { - default: { - http: readonly string[] - } - } - readonly [key: string]: unknown -} - -export type EIP1193Provider = { - request: (parameters: { method: string; params?: unknown }) => Promise -} - -export type TransactionLog = { - address: Address - blockHash?: Hash | undefined - blockNumber?: bigint | undefined - data: Hex - logIndex?: bigint | undefined - removed?: boolean | undefined - topics: readonly Hex[] - transactionHash?: Hash | undefined - transactionIndex?: bigint | undefined -} - -export type TransactionReceipt = { - blockHash: Hash - blockNumber: bigint - contractAddress?: Address | null | undefined - cumulativeGasUsed: bigint - effectiveGasPrice?: bigint | undefined - from: Address - gasUsed: bigint - logs: TransactionLog[] - logsBloom?: Hex | undefined - status: 'reverted' | 'success' - to?: Address | null | undefined - transactionHash: Hash - transactionIndex: bigint - type?: string | undefined -} - -export type ReplacementReason = 'cancelled' | 'replaced' | 'repriced' - -export type TransactionReplacement = { - reason: ReplacementReason - replacedTransaction: Pick - transaction: Pick - transactionReceipt: TransactionReceipt -} - -export type WaitForTransactionReceiptParameters = { - hash: Hash - onReplaced?: ((replacement: TransactionReplacement) => void) | undefined - pollingInterval?: number | undefined - transaction?: BlockTransaction | undefined - timeout?: number | undefined -} - -export type BlockTransaction = { - blockNumber?: bigint | undefined - from: Address - gas: bigint - gasPrice?: bigint | undefined - hash: Hash - input: Hex - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined - nonce: bigint - to?: Address | null | undefined - transactionIndex?: bigint | undefined - type?: string | undefined - value: bigint -} - -export type Block = { - baseFeePerGas?: bigint | undefined - hash?: Hash | undefined - number?: bigint | undefined - parentHash?: Hash | undefined - readonly transactions: readonly unknown[] - timestamp: bigint -} - -export type RpcLog = TransactionLog & { - args?: TArgs - eventName?: TEventName | undefined -} - -export type Account = { - address: Address - signMessage?: (message: string | Uint8Array) => Promise - signTransaction?: (parameters: SignTransactionParameters) => Promise - type: 'json-rpc' | 'local' | string -} - -export type SignTransactionParameters = { - chainId?: bigint | number | undefined - data?: Hex | undefined - gas?: bigint | number | undefined - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined - nonce?: bigint | number | undefined - to?: Address | undefined - value?: bigint | undefined -} - -export type ParsedTransaction = { - chainId?: bigint | undefined - data?: Hex | undefined - gas?: bigint | undefined - gasPrice?: bigint | undefined - maxFeePerGas?: bigint | undefined - maxPriorityFeePerGas?: bigint | undefined - nonce?: bigint | undefined - to?: Address | undefined - type?: string | undefined - value?: bigint | undefined -} - -export type TypedTransport = - | { - kind: 'custom' - provider: EIP1193Provider - timeoutMilliseconds: number - } - | { - kind: 'http' - timeoutMilliseconds: number - url: string - } - -export type Transport = TypedTransport - -export type MulticallSuccessResult = { - result: TValue - status: 'success' -} - -export type MulticallFailureResult = { - error: Error - status: 'failure' -} - -export type MulticallReturnType = Readonly<{ - [TIndex in keyof TContracts]: TContracts[TIndex] extends ContractFunctionParameters - ? TAllowFailure extends true - ? MulticallSuccessResult> | MulticallFailureResult - : MulticallContractResult - : TAllowFailure extends true - ? MulticallSuccessResult | MulticallFailureResult - : unknown -}> - -export type BlockTag = 'earliest' | 'latest' | 'pending' -export type LogTopicFilter = Hex | readonly Hex[] | null diff --git a/bots/shared/src/monitoring/connectivity.ts b/bots/shared/src/monitoring/connectivity.ts index 470ccc2eb..44591cf18 100644 --- a/bots/shared/src/monitoring/connectivity.ts +++ b/bots/shared/src/monitoring/connectivity.ts @@ -1,5 +1,5 @@ import type { Address, Hex } from '../ethereum.ts' -import { bigintToSafeNumber, keccak256 } from '../ethereum/codec.ts' +import { bigintToSafeNumber, keccak256 } from '../ethereum.ts' import type { SubmissionSettings } from '../execution/transaction-submission.ts' import { boundedJsonResponse, DEFAULT_RPC_RESPONSE_BYTES } from '../infrastructure/bounded-json.ts' diff --git a/bots/shared/tests/shared-primitives.test.ts b/bots/shared/tests/shared-primitives.test.ts index fe4841554..29abe064e 100644 --- a/bots/shared/tests/shared-primitives.test.ts +++ b/bots/shared/tests/shared-primitives.test.ts @@ -11,7 +11,7 @@ import { paddedTransactionGas, prepareSignedTransaction, submitSignedTransaction import { createPublicClient, custom, encodeAbiParameters, http, parseTransaction, privateKeyToAccount, RpcError } from '../src/ethereum.ts' import { createRpcEndpointPool, RpcEndpointPoolFailure } from '../src/ethereum/rpc-resilience.ts' import { ConnectivityDegradedError, operationalFailureDisposition } from '../src/monitoring/resilience.ts' -import { bigintToSafeNumber } from '../src/ethereum/codec.ts' +import { bigintToSafeNumber } from '../src/ethereum.ts' import { confirmCanonicalReceiptFinality } from '../src/execution/canonical-finality.ts' import { EndpointCheckFailure } from '../src/monitoring/connectivity.ts' @@ -22,6 +22,56 @@ afterEach(async () => { }) describe('shared bot primitives', () => { + test('keeps the Ethereum facade limited to the compatibility surface', async () => { + const facade = await import('../src/ethereum.ts') + expect(Object.keys(facade).sort()).toEqual( + [ + 'RpcEndpointPoolFailure', + 'RpcError', + 'bigintToSafeNumber', + 'bytesToHex', + 'concatHex', + 'createPublicClient', + 'createRpcEndpointPool', + 'createWalletClient', + 'custom', + 'decodeEventLog', + 'decodeFunctionData', + 'defineChain', + 'encodeAbiParameters', + 'encodeDeployData', + 'encodeEventTopics', + 'encodeFunctionData', + 'formatEther', + 'formatUnits', + 'getAddress', + 'getBalanceAtBlock', + 'getCreate2Address', + 'getCreateAddress', + 'getTransactionCountAtBlock', + 'hexToBytes', + 'http', + 'isAddress', + 'isHex', + 'keccak256', + 'mainnet', + 'maxUint256', + 'numberToBytes', + 'parseAbiItem', + 'parseAbiParameters', + 'parseTransaction', + 'parseUnits', + 'privateKeyToAccount', + 'publicActions', + 'readContractAtBlock', + 'recoverTransactionAddress', + 'toHex', + 'zeroAddress', + 'zeroHash', + ].sort(), + ) + }) + test('converts bigint values only inside the safe integer range', () => { expect(bigintToSafeNumber(9_007_199_254_740_991n)).toBe(Number.MAX_SAFE_INTEGER) expect(() => bigintToSafeNumber(9_007_199_254_740_992n)).toThrow('safe integer range') @@ -308,7 +358,7 @@ describe('shared bot primitives', () => { if (malformed.port === undefined || healthy.port === undefined) throw new Error('RPC pool test servers did not expose ports') const pool = createRpcEndpointPool([`http://127.0.0.1:${malformed.port.toString()}`, `http://127.0.0.1:${healthy.port.toString()}`]) const client = createPublicClient({ transport: pool.transport }) - await expect(client.getChainId()).rejects.toThrow('Invalid JSON-RPC envelope') + await expect(client.getChainId()).rejects.toThrow('Malformed JSON-RPC response') expect(healthyRequests).toBe(0) } finally { malformed.stop(true) @@ -398,7 +448,7 @@ describe('shared bot primitives', () => { try { if (server.port === undefined) throw new Error('Malformed RPC did not expose a port') const client = createPublicClient({ transport: http(`http://127.0.0.1:${server.port.toString()}`) }) - await expect(client.getChainId()).rejects.toThrow('Invalid JSON-RPC envelope') + await expect(client.getChainId()).rejects.toThrow('Malformed JSON-RPC response') } finally { server.stop(true) } diff --git a/scripts/check-docs-reference-values.mts b/scripts/check-docs-reference-values.mts index 5476a5f92..bf8d8390b 100644 --- a/scripts/check-docs-reference-values.mts +++ b/scripts/check-docs-reference-values.mts @@ -21,7 +21,7 @@ const startHere = normalizeHtmlSource(await readFile('docs/documentation.html', const operatorReference = htmlToDocumentationText(await readFile('docs/reference/operator-guardrails.html', 'utf8')) const securityModel = await readFile('docs/reference/security-model.html', 'utf8') const contractInteractionReference = htmlToDocumentationText(await readFile('docs/reference/contracts.html', 'utf8')) -const contractReferenceGenerator = await readFile('scripts/generate-contract-interaction-reference.mts', 'utf8') +const contractReferenceGenerator = `${await readFile('scripts/generate-contract-interaction-reference.mts', 'utf8')}\n${await readFile('scripts/contract-reference-metadata.mts', 'utf8')}` const deploymentStatus = normalizeHtmlSource(await readFile('docs/reference/deployment-status.html', 'utf8')) const escalationGame = await readFile('solidity/contracts/peripherals/EscalationGame.sol', 'utf8') const escalationGameClaimDelegate = await readFile('solidity/contracts/peripherals/EscalationGameClaimDelegate.sol', 'utf8') diff --git a/scripts/contract-reference-metadata.mts b/scripts/contract-reference-metadata.mts new file mode 100644 index 000000000..820bc22e7 --- /dev/null +++ b/scripts/contract-reference-metadata.mts @@ -0,0 +1,1801 @@ +export type Interaction = { + call: string + caller: string + declarations: ContractDeclaration[] + effect: string + preconditions: string + signals: string +} + +export type ContractDeclaration = { + kind?: 'receive' + name: string + sourcePath?: string +} + +export type ContractReference = { + compiledAbiFingerprint: string + interactions: Interaction[] + name: string + purpose: string + readAbiFingerprint: string + readDeclarations: ContractDeclaration[] + readStorageDeclarations?: ContractDeclaration[] + readSurface: string + securityBoundary?: string + securityBoundaryHeading?: string + sourcePath: string +} + +export type AssemblyDelegateCall = { + abiSignature: string + argumentOffsets: Array<{ argument: string; offset: string }> + calldataLength: string + selector: string + sourcePath: string + targetEntrypointSignature: string + targetFunctionName: string + targetSourcePath: string +} + +export const outputPath = 'docs/reference/contracts.html' +export const expectedProductionSoliditySourceFingerprint = 'd7f143000d729b03a6a7a0d37923ae035c2e13f80f86c0fb6807419d63fbd1dc' + +export const eventSourceByName: Record = { + VaultBadDebtMigrated: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + Approval: 'solidity/contracts/IERC20.sol', + ApprovalForAll: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + AuctionStarted: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + AwaitingForkContinuationSet: 'solidity/contracts/peripherals/SecurityPool.sol', + AuctionFinalized: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + AuthorizationUpdated: 'solidity/contracts/peripherals/interfaces/IShareToken.sol', + BidSettled: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + BidSubmitted: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + Burn: 'solidity/contracts/ReputationToken.sol', + CarryDepositConsumed: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + ChildDisputeStakedRepMaterialized: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + ChildPoolLinked: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + PoolHeldRepSweptToChild: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + ChildRepSplit: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + ClaimAuctionProceeds: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + ClaimDeposit: 'solidity/contracts/peripherals/EscalationGameState.sol', + ClaimForkedEscalationDepositsToWallet: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + CompleteSetCreated: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', + CompleteSetRedeemed: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', + CoordinatorStateCheckpoint: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + DeployChild: 'solidity/contracts/Zoltar.sol', + DeploySecurityPool: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', + DepositOnOutcome: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + RepDepositedToVault: 'solidity/contracts/peripherals/SecurityPool.sol', + DepositToEscalationGame: 'solidity/contracts/peripherals/SecurityPool.sol', + EscalationGameSet: 'solidity/contracts/peripherals/SecurityPool.sol', + EscalationMigrationEntitlementInitialized: 'solidity/contracts/peripherals/EscalationGameForker.sol', + EscalationMigrationEntitlementMaterialized: 'solidity/contracts/peripherals/EscalationGameForker.sol', + DisputeStakedRepDrainedAtFork: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + TruthAuctionHaircutApplied: 'solidity/contracts/peripherals/EscalationGameState.sol', + EthRefundDeferred: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + ExecutedStagedOperation: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + ForkContinuationResumed: 'solidity/contracts/peripherals/EscalationGameState.sol', + ForkCarryCheckpoint: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + ForkedEscrowExported: 'solidity/contracts/peripherals/EscalationGameState.sol', + ForkedEscrowRecorded: 'solidity/contracts/peripherals/EscalationGameState.sol', + GameContinuedFromFork: 'solidity/contracts/peripherals/EscalationGameState.sol', + GameStarted: 'solidity/contracts/peripherals/EscalationGameState.sol', + InheritedThresholdTie: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + LocalDepositAppended: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + LiquidationApprovalConsumed: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationApprovalNonceInvalidated: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationApprovalReleased: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationApprovalReserved: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationApprovalRevoked: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationApprovalSet: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + LiquidationRouteStaged: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + Migrate: 'solidity/contracts/peripherals/tokens/ShareToken.sol', + VaultMigrationCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + MigrationRepAdded: 'solidity/contracts/Zoltar.sol', + MigrationRepSplit: 'solidity/contracts/Zoltar.sol', + Mint: 'solidity/contracts/ReputationToken.sol', + NonDecisionReached: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', + TotalRepBackingUnitsSet: 'solidity/contracts/peripherals/SecurityPool.sol', + VaultTargetHealthFactorSet: 'solidity/contracts/peripherals/SecurityPool.sol', + PendingEthRefundWithdrawn: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', + PendingReportRecovered: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + ParentRepLocked: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + RepWithdrawnFromVault: 'solidity/contracts/peripherals/SecurityPool.sol', + PoolForkModeActivated: 'solidity/contracts/peripherals/SecurityPool.sol', + PriceReportRejected: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + PriceReported: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + PriceRequested: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + QuestionCreated: 'solidity/contracts/ZoltarQuestionData.sol', + RepRedeemedFromVault: 'solidity/contracts/peripherals/SecurityPool.sol', + RepBurned: 'solidity/contracts/Zoltar.sol', + RepEthPriceSet: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + ResidualRepSweptToSecurityPool: 'solidity/contracts/peripherals/EscalationGameState.sol', + ForkContinuationResidualRepBurned: 'solidity/contracts/peripherals/EscalationGameState.sol', + SecurityPoolSet: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + SecurityPoolForkSnapshot: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + SecurityPoolRegistered: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', + ShareTokenSupplySet: 'solidity/contracts/peripherals/SecurityPool.sol', + SharesRedeemed: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', + StagedOperationQueued: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + SystemStateSet: 'solidity/contracts/peripherals/SecurityPool.sol', + TruthAuctionFinalized: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + TruthAuctionStarted: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + TheoreticalSupplySet: 'solidity/contracts/ReputationToken.sol', + Transfer: 'solidity/contracts/IERC20.sol', + TransferBatch: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + TransferSingle: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + UniverseForked: 'solidity/contracts/Zoltar.sol', + PoolAccountingCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', + VaultAccountingCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', + VaultBadDebtRecorded: 'solidity/contracts/peripherals/SecurityPool.sol', + VaultLiquidated: 'solidity/contracts/peripherals/SecurityPool.sol', + VaultEscrowUpdated: 'solidity/contracts/peripherals/EscalationGameState.sol', + VaultUnresolvedTotalsExported: 'solidity/contracts/peripherals/EscalationGameState.sol', +} + +export const documentedEventSchemas: Array<{ name: string; parameters: string; sourcePath: string }> = [ + { + name: 'Transfer', + parameters: 'address indexed from,address indexed to,uint256 value', + sourcePath: 'solidity/contracts/IERC20.sol', + }, + { + name: 'Approval', + parameters: 'address indexed owner,address indexed spender,uint256 value', + sourcePath: 'solidity/contracts/IERC20.sol', + }, + { + name: 'TransferSingle', + parameters: 'address indexed operator,address indexed from,address indexed to,uint256 id,uint256 value', + sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + }, + { + name: 'TransferBatch', + parameters: 'address indexed operator,address indexed from,address indexed to,uint256[] ids,uint256[] values', + sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + }, + { + name: 'ApprovalForAll', + parameters: 'address indexed owner,address indexed operator,bool approved', + sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', + }, + { + name: 'QuestionCreated', + parameters: 'uint256 indexed questionId,uint256 createdTimestamp,QuestionData questionData,string[] outcomeOptions', + sourcePath: 'solidity/contracts/ZoltarQuestionData.sol', + }, + { + name: 'UniverseInitialized', + parameters: 'uint248 indexed universeId,uint256 forkTime,uint256 forkQuestionId,uint256 forkingOutcomeIndex,ReputationToken reputationToken,uint248 indexed parentUniverseId,uint256 universeTheoreticalSupplyAttoRep', + sourcePath: 'solidity/contracts/Zoltar.sol', + }, + { + name: 'DeployChild', + parameters: 'address deployer,uint248 indexed universeId,uint256 indexed outcomeIndex,uint248 indexed childUniverseId,ReputationToken childReputationToken,uint256 childUniverseTheoreticalSupplyAttoRep', + sourcePath: 'solidity/contracts/Zoltar.sol', + }, + { + name: 'SecurityPoolRegistered', + parameters: 'bytes32 indexed originId,bytes32 indexed poolId,uint248 indexed universeId,ISecurityPool securityPool', + sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', + }, + { + name: 'DeploySecurityPool', + parameters: + 'ISecurityPool indexed securityPool,UniformPriceDualCapBatchAuction truthAuction,OpenOraclePriceCoordinator priceOracleManagerAndOperatorQueuer,IShareToken shareToken,ISecurityPool indexed parent,uint248 indexed universeId,uint256 questionId,uint256 statoblastSecurityMultiplierBps,uint256 initialReportPriorityFeeAttoEthPerGas,uint256 currentRetentionRate,uint256 settlementCollateralAttoEth', + sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', + }, + { + name: 'ChildPoolLinked', + parameters: 'ISecurityPool indexed parent,uint256 indexed outcomeIndex,ISecurityPool indexed child,UniformPriceDualCapBatchAuction truthAuction', + sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + }, + { + name: 'ChildRepSplit', + parameters: 'ISecurityPool indexed parent,uint256 indexed outcomeIndex,uint256 childPoolRepSplitAttoRep,uint256 pendingChildAttoRep', + sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + }, + { + name: 'ChildDisputeStakedRepMaterialized', + parameters: 'ISecurityPool indexed parentPool,ISecurityPool indexed childPool,address indexed childGame,uint256 outcomeIndex,uint256 attoRepAmount,uint256 resultingDisputeStakedRepBalanceAttoRep', + sourcePath: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + }, + { + name: 'PoolHeldRepSweptToChild', + parameters: 'ISecurityPool indexed parentPool,ISecurityPool indexed childPool,uint256 indexed outcomeIndex,uint256 attoRepAmount,uint256 resultingChildPoolHeldRepBalanceAttoRep', + sourcePath: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', + }, + { + name: 'EscalationMigrationEntitlementInitialized', + parameters: 'ISecurityPool indexed parent,address indexed vault,uint256[3] sourcePrincipalByOutcomeAttoRep,uint256[3] currentRepByOutcomeAttoRep,uint256 totalCurrentAttoRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameForker.sol', + }, + { + name: 'EscalationMigrationEntitlementMaterialized', + parameters: 'ISecurityPool indexed parent,address indexed vault,uint256 indexed childOutcomeIndex,ISecurityPool child,uint256 childAttoRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameForker.sol', + }, + { name: 'TheoreticalSupplySet', parameters: 'uint256 totalTheoreticalSupplyAttoRep', sourcePath: 'solidity/contracts/ReputationToken.sol' }, + { name: 'Mint', parameters: 'address indexed account,uint256 valueAttoRep', sourcePath: 'solidity/contracts/ReputationToken.sol' }, + { + name: 'Burn', + parameters: 'address indexed account,uint256 valueAttoRep,uint256 totalTheoreticalSupplyAttoRep', + sourcePath: 'solidity/contracts/ReputationToken.sol', + }, + { + name: 'AwaitingForkContinuationSet', + parameters: 'bool awaitingForkContinuation', + sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', + }, + { + name: 'TotalRepBackingUnitsSet', + parameters: 'uint256 totalRepBackingUnits', + sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', + }, + { + name: 'ShareTokenSupplySet', + parameters: 'uint256 shareTokenSupplyAttoShares', + sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', + }, + { name: 'SystemStateSet', parameters: 'SystemState systemState', sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol' }, + { + name: 'VaultEscrowUpdated', + parameters: 'address indexed vault,uint256 disputeStakedRepByVaultAttoRep,uint256 totalDisputeStakedAttoRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', + }, + { + name: 'ForkedEscrowRecorded', + parameters: 'address indexed depositor,BinaryOutcomes.BinaryOutcome indexed outcome,uint256 sourcePrincipalTotalAttoRep,uint256 childRepTotalAttoRep,uint256 disputeStakedRepByVaultAttoRep,uint256 totalDisputeStakedAttoRep,uint256 outcomeBalanceAttoRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', + }, + { + name: 'VaultUnresolvedTotalsExported', + parameters: 'address indexed vault,address repReceiver,uint256[3] principalByOutcomeAttoRep,uint256 principalToTransferAttoRep,bool transferredRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', + }, + { + name: 'ForkedEscrowExported', + parameters: 'address indexed vault,address repReceiver,uint256[3] sourcePrincipalByOutcomeAttoRep,uint256[3] childRepByOutcomeAttoRep,uint256 totalChildRepToTransferAttoRep,bool transferredRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', + }, + { + name: 'ForkedEscrowClaimed', + parameters: 'address indexed depositor,BinaryOutcomes.BinaryOutcome indexed outcome,uint256 sourcePrincipalClaimedAttoRep,uint256 childRepClaimedAttoRep', + sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', + }, + { + name: 'InternalApproval', + parameters: 'address indexed owner,address indexed spender,address indexed token,uint256 amount', + sourcePath: 'solidity/contracts/peripherals/openOracle/OpenOracle.sol', + }, + { + name: 'DeploymentAddressesSet', + parameters: 'address[] deploymentAddresses', + sourcePath: 'solidity/contracts/DeploymentStatusOracle.sol', + }, +] + +export const delegateEventDeclarationMirrors: Array<{ name: string; sourcePath: string }> = [ + { name: 'PoolAccountingCheckpoint', sourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol' }, + { name: 'VaultAccountingCheckpoint', sourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol' }, + { name: 'ChildPoolLinked', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, + { name: 'ChildRepSplit', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, + { name: 'ClaimForkedEscalationDepositsToWallet', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, +] + +export const assemblyEventEmissions: Array<{ + dataArguments: string + indexedArguments: string + name: string + signature: string + signatureConstant: string + sourcePath: string +}> = [ + { + dataArguments: 'carryRoots, nullifierRoots, leafCounts, unresolvedTotalsAttoRep, resolutionBalancesAttoRep', + indexedArguments: 'sourceGame, snapshotId', + name: 'ForkCarryCheckpoint', + signature: 'ForkCarryCheckpoint(address,bytes32,bytes32[3],bytes32[3],uint256[3],uint256[3],uint256[3])', + signatureConstant: 'FORK_CARRY_CHECKPOINT_SIGNATURE', + sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol', + }, + { + dataArguments: 'BinaryOutcomes.BinaryOutcome(outcomeIndex), amountAttoRep, reason, carryTotalAttoRep, _getCurrentNullifierRoot(outcomeIndex), carryRoot', + indexedArguments: 'parentDepositIndex, sourceNodeId, depositor', + name: 'CarryDepositConsumed', + signature: 'CarryDepositConsumed(uint256,uint256,address,uint8,uint256,uint8,uint256,bytes32,bytes32)', + signatureConstant: 'CARRY_DEPOSIT_CONSUMED_SIGNATURE', + sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol', + }, +] + +export const assemblyDelegateCalls: AssemblyDelegateCall[] = [ + { + abiSignature: 'emitForkSnapshotEvents(address,address,address,uint256,uint256,uint256)', + argumentOffsets: [ + { argument: 'parent', offset: '0x04' }, + { argument: 'migrationProxy', offset: '0x24' }, + { argument: 'sourceGame', offset: '0x44' }, + { argument: 'totalPoolHeldRepAtForkAttoRep', offset: '0x64' }, + { argument: 'disputeStakedRepAtForkAttoRep', offset: '0x84' }, + { argument: 'resultingLockedAttoRep', offset: '0xa4' }, + ], + calldataLength: '0xc4', + selector: '0x408d33da', + sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + targetEntrypointSignature: 'external(ISecurityPool,address,address,uint256,uint256,uint256)', + targetFunctionName: 'emitForkSnapshotEvents', + targetSourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol', + }, +] + +export const referencedEventAbiFingerprint = 'c209c24aef4071e13c7598ed7b8a6dc733f3946e8646307befa3c9a60387ff72' + +export const entrypointSignaturesBySource: Record> = { + 'solidity/contracts/ERC20.sol': { + approve: ['public(address,uint256)'], + transfer: ['public(address,uint256)'], + transferFrom: ['public(address,address,uint256)'], + }, + 'solidity/contracts/ZoltarQuestionData.sol': { + createQuestion: ['external(QuestionData,string[])'], + }, + 'solidity/contracts/Zoltar.sol': { + addRepToMigrationBalance: ['public(uint248,uint256)'], + burnRep: ['external(uint248,uint256)'], + deployChild: ['public(uint248,uint256)'], + forkUniverse: ['public(uint248,uint256)'], + splitMigrationRep: ['public(uint248,uint256,uint256[])'], + }, + 'solidity/contracts/ReputationToken.sol': { + burn: ['external(address,uint256)'], + mint: ['external(address,uint256)'], + setMaxTheoreticalSupplyAttoRep: ['external(uint256)'], + }, + 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol': { + deployChildSecurityPool: ['external(ISecurityPool,IShareToken,uint248,uint256,uint256,uint256,uint256)'], + deployOriginSecurityPool: ['external(uint248,uint256,uint256,uint256)'], + }, + 'solidity/contracts/peripherals/EscalationGame.sol': { + applyTruthAuctionHaircut: ['external(uint256)'], + recordDepositFromSecurityPool: ['external(address,BinaryOutcomes.BinaryOutcome,uint256,uint256)'], + resumeFromFork: ['external()'], + start: ['external(uint256,uint256)'], + startFromFork: ['external(uint256,uint256,uint256,BinaryOutcomes.BinaryOutcome,bool,uint256)'], + }, + 'solidity/contracts/peripherals/EscalationGameCarry.sol': { + initializeForkCarrySnapshotWithResolutionBalances: ['external(address,bytes32,bytes32[MERKLE_MOUNTAIN_RANGE_MAX_PEAKS][3],uint256[3],uint256[3],uint256[3],bytes32[3])'], + }, + 'solidity/contracts/peripherals/EscalationGameEscrow.sol': { + exportForkedEscrowByOutcome: ['external(address,address)'], + exportForkedEscrowByOutcomeWithoutTransfer: ['external(address)'], + exportVaultUnresolvedTotals: ['external(address,address)'], + exportVaultUnresolvedTotalsWithoutTransfer: ['external(address)'], + recordForkedEscrowForOutcome: ['external(address,BinaryOutcomes.BinaryOutcome,uint256,uint256)'], + }, + 'solidity/contracts/peripherals/EscalationGameSettlement.sol': { + claimDepositForWinning: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], + claimDepositForWinningWithoutTransfer: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], + drainAllRep: ['external(address)'], + exportUnresolvedDeposit: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], + sweepResidualRepToSecurityPool: ['external()'], + withdrawDeposit: ['public(CarriedDepositProof,BinaryOutcomes.BinaryOutcome)', 'public(uint256,BinaryOutcomes.BinaryOutcome)'], + }, + 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': { + executeStagedOperation: ['public(uint256)'], + expireStagedOperation: ['external(uint256)'], + openOracleCallback: ['external(uint256,uint256,uint256,uint256,address,address)'], + recoverSettledPendingReport: ['public()'], + requestPrice: ['public(uint256,uint256)'], + requestPriceIfNeededAndStageLiquidation: ['external(address,address,uint256,bytes32,uint256,uint256,uint256)'], + requestPriceIfNeededAndStageOperation: ['public(OperationType,address,uint256,uint256,uint256,uint256)'], + setLiquidationApprovalRegistry: ['external(LiquidationApprovalRegistry)'], + setRepEthPrice: ['public(uint256)'], + setSecurityPool: ['public(ISecurityPool)'], + }, + 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': { + consume: ['external(uint256,uint256)'], + initialize: ['external(address)'], + invalidateLiquidationApprovalNonce: ['external(uint256)'], + permitLiquidationApproval: ['external(LiquidationApprovalParams,bytes)'], + release: ['external(uint256)'], + reserve: ['external(uint256,bytes32,address,address,address,uint256,uint256,uint256)'], + revokeLiquidationApproval: ['external(bytes32)'], + setLiquidationApproval: ['external(LiquidationApprovalParams)'], + }, + 'solidity/contracts/peripherals/SecurityPool.sol': { + activateForkMode: ['external()'], + addFeeEligibleCapacityOwnershipAttoRep: ['external(address,uint256)'], + authorizeChildPool: ['external(ISecurityPool)'], + burnEscalationWinnerHaircut: ['external(uint256)'], + configureVault: ['external(address,uint256,uint256,uint256,uint256,uint256,uint256)'], + createCompleteSet: ['external()'], + depositRepToVault: ['external(uint256,uint256)'], + depositToEscalationGame: ['external(BinaryOutcomes.BinaryOutcome,uint256)'], + initializeForkCarrySnapshotWithResolutionBalances: ['external(address,bytes32,bytes32[64][3],uint256[3],uint256[3],uint256[3],bytes32[3])'], + initializeForkedEscalationGame: ['external(uint256,uint256,uint256,BinaryOutcomes.BinaryOutcome)'], + performLiquidation: ['external(LiquidationRequest)'], + withdrawRepFromVault: ['external(address,uint256)'], + receive: ['external payable()'], + redeemCompleteSet: ['external(uint256)'], + redeemFees: ['external(address)'], + redeemRepFromVault: ['external(address)'], + redeemShares: ['external()'], + resumeForkedEscalationGame: ['external()'], + setAwaitingForkContinuation: ['external(bool)'], + setTotalRepBackingUnits: ['external(uint256)'], + setPoolFinancials: ['external(uint256,uint256,uint256,uint256)'], + setStartingParams: ['external(uint256,uint256)'], + setSystemState: ['external(SystemState)'], + setTotalSharesAttoShares: ['external(uint256)'], + transferEth: ['external(address payable,uint256)'], + updateSettlementCollateral: ['public()'], + updateRetentionRate: ['public()'], + updateVaultFees: ['public(address)'], + withdrawForkedEscalationDeposits: ['external(QuestionOutcome,CarriedDepositProof[])'], + withdrawFromEscalationGame: ['external(BinaryOutcomes.BinaryOutcome,uint256[])'], + }, + 'solidity/contracts/peripherals/SecurityPoolForker.sol': { + claimAuctionProceeds: ['external(ISecurityPool,address,IUniformPriceDualCapBatchAuction.TickIndex[])'], + claimForkedEscalationDeposits: ['external(ISecurityPool,address,BinaryOutcomes.BinaryOutcome,uint256[])'], + createChildUniverse: ['external(ISecurityPool,uint256)'], + finalizeTruthAuction: ['external(ISecurityPool)'], + forkZoltarWithOwnEscalationGame: ['external(ISecurityPool)'], + initiateSecurityPoolFork: ['external(ISecurityPool)'], + initializeChildForkedEscalationGameIfNeeded: ['external(ISecurityPool,ISecurityPool,EscalationGame)'], + migrateRepToZoltar: ['external(ISecurityPool,uint256[])'], + migrateVault: ['public(ISecurityPool,uint256)'], + migrateVaultWithUnresolvedEscalation: ['external(ISecurityPool,address,uint256)'], + receive: ['external payable()'], + settleAuctionBids: ['external(ISecurityPool,address,IUniformPriceDualCapBatchAuction.TickIndex[],IUniformPriceDualCapBatchAuction.TickIndex[])'], + startTruthAuction: ['external(ISecurityPool)'], + }, + 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol': { + finalize: ['external()'], + refundLosingBids: ['external(IUniformPriceDualCapBatchAuction.TickIndex[])'], + refundLosingBidsFor: ['external(address,IUniformPriceDualCapBatchAuction.TickIndex[])'], + startAuction: ['public(uint256,uint256)'], + submitBid: ['external(int256)'], + withdrawBids: ['external(address,IUniformPriceDualCapBatchAuction.TickIndex[],uint256)'], + withdrawPendingEthRefund: ['external()'], + }, + 'solidity/contracts/peripherals/tokens/ShareToken.sol': { + authorize: ['external(ISecurityPool)'], + burnCompleteSets: ['external(uint248,address,uint256)'], + burnTokenIdAndGetRemainingSupply: ['external(uint256,address)'], + migrate: ['external(uint256,uint256[])'], + mintCompleteSets: ['external(uint248,address,uint256)'], + }, + 'solidity/contracts/peripherals/tokens/ERC1155.sol': { + safeBatchTransferFrom: ['external(address,address,uint256[],uint256[])', 'external(address,address,uint256[],uint256[],bytes)'], + safeTransferFrom: ['external(address,address,uint256,uint256)', 'external(address,address,uint256,uint256,bytes)'], + setApprovalForAll: ['external(address,bool)'], + }, +} + +export const stateChangingAbiFingerprintBySource: Record = { + 'solidity/contracts/Context.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/ERC20.sol': '6c4161bf27a2ed1bc2de94b58253a8ec4201e28d125571cb2124238753387a22', + 'solidity/contracts/ReputationToken.sol': 'b3e68791ded4f7fd9cc70785bdd3c55d5ec7fde5ad64b7fbe8aee03d5d273e3b', + 'solidity/contracts/Zoltar.sol': '6479e6b24905f8f3299e486703df934aa7811152a9d20517596da64cbcd4b471', + 'solidity/contracts/ZoltarQuestionData.sol': '904b4369195f070fa3b04bbcbc1acba529810ffa2da4667569cd9168ac568d65', + 'solidity/contracts/peripherals/EscalationGame.sol': '41394612ae4488f08c9f4c18ff912cec089fc40a3f5d501a27cb8b2fabc4db57', + 'solidity/contracts/peripherals/EscalationGameCalculations.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/peripherals/EscalationGameCarry.sol': 'bdd7cfe47523c5e0c8985eec993214de44caf88fc4f2e6f1586d2d03c0a02ef0', + 'solidity/contracts/peripherals/EscalationGameEscrow.sol': 'c75cd0c9ea134a3bfa03227d0500485049818553447b4b258ff220cb0d201dde', + 'solidity/contracts/peripherals/EscalationGameSettlement.sol': '73f9aad63165cacbff5bd02fd57a6b5a3f73737545018ecdf152c46f905c8c32', + 'solidity/contracts/peripherals/EscalationGameState.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/peripherals/EscalationGameStorage.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': '2a27b7ed5407ac8067de39d67bbe84902f4c1c36ab070eeaeb99375db6f8b8e1', + 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': '986a20fc0e4cfe0898be8fc91c6b911b93ef0ae1086d4cb1142a93c66f315684', + 'solidity/contracts/peripherals/SecurityPool.sol': '7de24a5d15ed2b8ffc052c498eefb96f92997d59ee8b8d075b42efad4a17012d', + 'solidity/contracts/peripherals/SecurityPoolForker.sol': '282c464a68623405a6241816a1c5fcef4b80e9db39e42e89d77177d8a4f10eae', + 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/peripherals/SecurityPoolForkerStorage.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol': 'af052a0723644556a488b365d578205eb331e53a1e47fff8b869ff77fab9c7ef', + 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol': '618aed7f3f8bdfd50267b9d7533db3f489f45715f1cd448f5107f67631814d34', + 'solidity/contracts/peripherals/tokens/ERC1155.sol': '7bb87695bc3df8fa177c545209ed58d2e4571c19c869b5598bb0a829e764b218', + 'solidity/contracts/peripherals/tokens/ShareToken.sol': '2a3339ca5db0ccabc2bc10318ff3baf52273b90837f01683d3e5147a13fd2d0d', +} + +export const readDeclarationExclusionsBySource: Record = { + 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol': ['securityPool'], + 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': ['storedGame', 'disputeHistory'], + 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': ['securityPool'], + 'solidity/contracts/peripherals/SecurityPool.sol': ['eventEmitter', 'factory'], + 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol': [], +} + +export const contractReferences: ContractReference[] = [ + { + compiledAbiFingerprint: '580109cfcebb3ce505def01895f7b6567e75bbd8e8ccac857bdd00d54f15c37f', + name: 'ZoltarQuestionData', + purpose: 'Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata.', + readAbiFingerprint: '964d0ce318d2890011ff485c8d78e933cabc8d10e489a0c22f0e266fa2563ded', + readSurface: + 'Use `getQuestionId` before submission; `questionCreatedTimestamp` and `questions` for direct lookup; `getQuestionCount` and `getQuestions` for indexed or paged discovery; and `getQuestionEndDate`, `getOutcomeLabels`, `splitUint256IntoTwoWithInvalid`, `hasNonZeroScalarReservedBits`, `isMalformedAnswerOption`, and `getAnswerOptionName` when validating or displaying answers. In the `QuestionData` tuple, `startTime` and `endTime` are `uint48`, while `numTicks` is `uint120`; clients must use these exact widths because they determine the `getQuestionId` and `createQuestion` selectors.', + readDeclarations: [ + { name: 'getQuestionId' }, + { name: 'getQuestionCount' }, + { name: 'getQuestions' }, + { name: 'getQuestionEndDate' }, + { name: 'getOutcomeLabels' }, + { name: 'splitUint256IntoTwoWithInvalid' }, + { name: 'hasNonZeroScalarReservedBits' }, + { name: 'isMalformedAnswerOption' }, + { name: 'getAnswerOptionName' }, + ], + readStorageDeclarations: [{ name: 'questionCreatedTimestamp' }, { name: 'questions' }], + sourcePath: 'solidity/contracts/ZoltarQuestionData.sol', + interactions: [ + { + call: '`createQuestion(questionData, outcomeOptions)`', + caller: 'Anyone', + effect: 'Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied.', + declarations: [{ name: 'createQuestion' }], + preconditions: 'Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose `keccak256(abi.encode(label))` values are strictly descending.', + signals: '`QuestionCreated`', + }, + ], + }, + { + compiledAbiFingerprint: '023e5a38bcf613044e07d23e84095e1125be871017388a0c5a6cf7a41958b350', + name: 'Zoltar', + purpose: 'Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP.', + readAbiFingerprint: '1916e3480c70c4ccd5962f4b8069988d7dc36f0ea89337ebe04b8bca089d1492', + readSurface: + 'Use `universes`, `forkThresholdDivisor`, `forkBurnDivisor`, `zoltarQuestionData`, `genesisReputationToken`, `getForkTime`, `forkQuestionMatches`, `getRepToken`, `getForkThresholdAttoRep`, `getNonDecisionThresholdAttoRep`, `getUniverseTheoreticalSupplyAttoRep`, `getChildUniverseId`, `getDeployedChildUniverses`, and `getMigrationRepBalanceAttoRep` to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and `forkBurnDivisor >= 5`, which caps the uncredited fork haircut at 20% of the threshold.', + securityBoundary: 'Security boundaries for these calls are [A15 intended question selection](./security-model.html#assumption-a15) and [A25 safe immutable parameters](./security-model.html#assumption-a25).', + readDeclarations: [ + { name: 'getForkTime' }, + { name: 'forkQuestionMatches' }, + { name: 'getRepToken' }, + { name: 'getForkThresholdAttoRep' }, + { name: 'getNonDecisionThresholdAttoRep' }, + { name: 'getUniverseTheoreticalSupplyAttoRep' }, + { name: 'getChildUniverseId' }, + { name: 'getDeployedChildUniverses' }, + { name: 'getMigrationRepBalanceAttoRep' }, + ], + readStorageDeclarations: [{ name: 'universes' }, { name: 'forkThresholdDivisor' }, { name: 'forkBurnDivisor' }, { name: 'zoltarQuestionData' }, { name: 'genesisReputationToken' }], + sourcePath: 'solidity/contracts/Zoltar.sol', + interactions: [ + { + call: '`forkUniverse(universeId, questionId)`', + caller: 'Any address able to fund the current fork threshold', + effect: 'Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut.', + declarations: [{ name: 'forkUniverse' }], + preconditions: 'Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance.', + signals: '`UniverseForked`', + }, + { + call: '`burnRep(universeId, amountAttoRep)`', + caller: 'Any REP holder; the caller can burn only its own balance', + effect: 'Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork.', + declarations: [{ name: 'burnRep' }], + preconditions: 'Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance.', + signals: '`RepBurned` and the token burn or transfer event', + }, + { + call: '`deployChild(universeId, outcomeIndex)`', + caller: 'Anyone', + effect: 'Deploys the deterministic child REP token and initializes the child universe.', + declarations: [{ name: 'deployChild' }], + preconditions: 'Parent forked; outcome is well formed; child is not already deployed.', + signals: '`DeployChild`', + }, + { + call: '`addRepToMigrationBalance(universeId, amountAttoRep)`', + caller: 'Parent REP holder', + effect: "Burns or sinks additional parent REP and increases the caller's reusable migration balance.", + declarations: [{ name: 'addRepToMigrationBalance' }], + preconditions: 'Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance.', + signals: '`MigrationRepAdded`', + }, + { + call: '`splitMigrationRep(universeId, amountAttoRep, outcomeIndexes)`', + caller: 'Migration-balance holder', + effect: + 'Mints `amount` of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch.', + declarations: [{ name: 'splitMigrationRep' }], + preconditions: "Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance.", + signals: '`TheoreticalSupplySet` and `DeployChild` when needed; child REP `Transfer` and `Mint`, then `MigrationRepSplit`, per selected branch, including at zero amount; no event for an empty list', + }, + ], + }, + { + compiledAbiFingerprint: '14cee3c68c22f454d0d83f16aad27d40b686fa8abc7fb0220c03ba19ba609f64', + name: 'ReputationToken', + purpose: 'Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar.', + readAbiFingerprint: '1385406a6e5989eb754a8adeb36f946309659127e088734528cdffa7f8bbe7c8', + readSurface: 'Use `getTotalTheoreticalSupplyAttoRep`, `zoltar`, and the standard ERC-20 `name`, `symbol`, `decimals`, `totalSupply`, `balanceOf`, and `allowance` reads.', + readDeclarations: [ + { name: 'getTotalTheoreticalSupplyAttoRep' }, + { name: 'name', sourcePath: 'solidity/contracts/ERC20.sol' }, + { name: 'symbol', sourcePath: 'solidity/contracts/ERC20.sol' }, + { name: 'decimals', sourcePath: 'solidity/contracts/ERC20.sol' }, + { name: 'totalSupply', sourcePath: 'solidity/contracts/ERC20.sol' }, + { name: 'balanceOf', sourcePath: 'solidity/contracts/ERC20.sol' }, + { name: 'allowance', sourcePath: 'solidity/contracts/ERC20.sol' }, + ], + readStorageDeclarations: [{ name: 'zoltar' }], + sourcePath: 'solidity/contracts/ReputationToken.sol', + interactions: [ + { + call: '`setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep)`', + caller: '`Zoltar` only', + effect: 'Sets the child token theoretical-supply ceiling used to bound subsequent migration mints.', + declarations: [{ name: 'setMaxTheoreticalSupplyAttoRep' }], + preconditions: 'Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP.', + signals: '`TheoreticalSupplySet`', + }, + { + call: '`mint(account, valueAttoRep)`', + caller: '`Zoltar` only', + effect: 'Mints branch REP to an account.', + declarations: [{ name: 'mint' }], + preconditions: '`account` is nonzero; resulting ERC-20 supply does not exceed theoretical supply.', + signals: '`Mint` and ERC-20 `Transfer`', + }, + { + call: '`burn(account, valueAttoRep)`', + caller: '`Zoltar` only', + effect: 'Burns account REP and reduces both actual and theoretical supply by the same amount.', + declarations: [{ name: 'burn' }], + preconditions: '`account` is nonzero and has sufficient REP; theoretical supply covers the burn.', + signals: '`Burn` and ERC-20 `Transfer`', + }, + { + call: '`transfer(to, value)`', + caller: 'REP holder', + effect: 'Moves REP from the caller without changing actual or theoretical supply.', + declarations: [{ name: 'transfer', sourcePath: 'solidity/contracts/ERC20.sol' }], + preconditions: 'Destination is nonzero; caller has sufficient balance.', + signals: '`Transfer`', + }, + { + call: '`approve(spender, value)`', + caller: 'Any REP account setting its own allowance', + effect: 'Replaces the named spender allowance without moving REP.', + declarations: [{ name: 'approve', sourcePath: 'solidity/contracts/ERC20.sol' }], + preconditions: 'Spender is nonzero.', + signals: '`Approval`', + }, + { + call: '`transferFrom(from, to, value)`', + caller: 'A spender with sufficient allowance from `from`', + effect: 'Moves REP from `from`; a finite allowance decreases by `value`, while an infinite allowance remains unchanged. Neither allowance path emits `Approval`.', + declarations: [{ name: 'transferFrom', sourcePath: 'solidity/contracts/ERC20.sol' }], + preconditions: 'Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source.', + signals: '`Transfer` only', + }, + ], + }, + { + compiledAbiFingerprint: 'c502f414482a9872fb01eaa78dccd3c19d5e1af5227c10047ba645da00fb3406', + name: 'SecurityPoolFactory', + purpose: 'Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction.', + readAbiFingerprint: '855853487a8ab201b9e990820bac4f51ec6ae6520ae2dcf49efcc93e81c9a474', + readSurface: + 'Use `initialEscalationGameDepositAttoRep`, `minimumSecurityBondDebtAttoEth`, and `minimumVaultRepDepositAttoRep` for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly `max(1 REP, theoretical REP supply / 10,000,000)`. A zero configured vault REP floor selects the default `theoretical REP supply / 100,000`; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use `securityPoolDeploymentCount` with the strict `securityPoolDeploymentsRange(startIndex, count)` pager, which reverts rather than truncating when the requested range exceeds the array. Use `getOriginId`, `getPoolId`, `getSecurityPool`, `getSecurityPoolOriginId`, and `getSecurityPoolHasInheritedForkOutcome` for canonical lookup.', + readDeclarations: [{ name: 'securityPoolDeploymentCount' }, { name: 'securityPoolDeploymentsRange' }, { name: 'getOriginId' }, { name: 'getPoolId' }, { name: 'getSecurityPool' }, { name: 'getSecurityPoolOriginId' }, { name: 'getSecurityPoolHasInheritedForkOutcome' }], + readStorageDeclarations: [{ name: 'initialEscalationGameDepositAttoRep' }, { name: 'minimumSecurityBondDebtAttoEth' }, { name: 'minimumVaultRepDepositAttoRep' }], + sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', + interactions: [ + { + call: '`deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas)`', + caller: 'Anyone', + effect: 'Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically.', + declarations: [{ name: 'deployOriginSecurityPool' }], + preconditions: + '`statoblastSecurityMultiplierBps > 10_001`, which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the [liquidation design](../explanation/liquidations.html#rule). `initialReportPriorityFeeAttoEthPerGas > 0` and remains within the coordinator-computed OpenOracle `uint128` report/escalation-halt capacity bound; question exists and has exactly the categorical labels `Yes`, then `No`; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit `max(1 REP, theoretical REP supply / 10,000,000)`; the origin/universe/priority-fee slot has not already been claimed.', + signals: '`SecurityPoolRegistered`, then `DeploySecurityPool`', + }, + { + call: '`deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth)`', + caller: '`SecurityPoolForker` only', + effect: 'Creates and registers a canonical child pool with a coordinator that inherits `initialReportPriorityFeeAttoEthPerGas` from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token.', + declarations: [{ name: 'deployChildSecurityPool' }], + preconditions: 'Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring.', + signals: '`SecurityPoolRegistered`, then `DeploySecurityPool`', + }, + ], + }, + { + compiledAbiFingerprint: '4e95ce668a620668593258b22e33ff0fdaa591d23455a7ac0dffafd4d003354f', + name: 'SecurityPool', + purpose: 'Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation.', + readAbiFingerprint: '25c286eef854f7f8c3a60fb339e29063f864976fb0c650594e6b5478a5af13f8', + readSurface: + 'Immutable relationship and configuration getters are `questionId`, `universeId`, `initialEscalationGameDepositAttoRep`, `zoltar`, `parent`, `shareToken`, `repToken`, `priceOracleManagerAndOperatorQueuer`, `openOracle`, `escalationGameFactory`, `questionData`, `securityPoolForker`, `truthAuction`, `securityPoolFactory`, and `statoblastSecurityMultiplierBps`; the current game is `escalationGame`. Accounting getters include `totalCapacityOwnershipAttoRep`, `settlementCollateralAttoEth`, `totalRepBackingUnits`, `shareTokenSupplyAttoShares`, `securityVaults`, `minimumSecurityBondDebtAttoEth`, `minimumVaultRepDepositAttoRep`, `vaultTargetHealthFactorBps`, `totalBadDebtAttoEth`, and `vaultBadDebtAttoEth`. Use `getCurrentMintingCapacityAttoEth` for price-converted aggregate capacity and `getVaultOpenInterestAttoEth` for a vault’s live proportional obligation. Other derived and paged reads are `getVaultCount`, `getVaults`, `attoSharesToAttoEth`, `attoEthToAttoShares`, `attoRepToBackingUnits`, `backingUnitsToAttoRep`, `getTotalPoolHeldAttoRep`, `totalAccruedFeesAttoEth`, `getPoolAccountingSnapshot`, `getVaultFeeRemainder`, and `isEscalationResolved`. The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from `securityVaults`, escalation stake, and bad debt. `isEscalationResolved()` is true only when a local escalation game is configured and the forker routes a non-`None` outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are `totalClaimableVaultFeesAttoEth`, `lastUpdatedFeeAccumulator`, `feeIndex`, `currentRetentionRate`, `awaitingForkContinuation`, and `systemState`.', + securityBoundary: + 'Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on [A16 timely inclusion](./security-model.html#assumption-a16), [A21 genesis REP and WETH behavior](./security-model.html#assumption-a21), [A19 observable correctable price](./security-model.html#assumption-a19), and [A06 lifecycle executors](./security-model.html#assumption-a06). User-initiated pool calls additionally depend on [A28 account authority](./security-model.html#assumption-a28).', + readDeclarations: [ + { name: 'getVaultCount' }, + { name: 'getVaults' }, + { name: 'attoSharesToAttoEth' }, + { name: 'attoEthToAttoShares' }, + { name: 'attoRepToBackingUnits' }, + { name: 'backingUnitsToAttoRep' }, + { name: 'getTotalPoolHeldAttoRep' }, + { name: 'totalAccruedFeesAttoEth' }, + { name: 'getPoolAccountingSnapshot' }, + { name: 'getVaultFeeRemainder' }, + { name: 'getCurrentMintingCapacityAttoEth' }, + { name: 'getVaultOpenInterestAttoEth' }, + { name: 'isEscalationResolved' }, + ], + readStorageDeclarations: [ + { name: 'questionId' }, + { name: 'universeId' }, + { name: 'initialEscalationGameDepositAttoRep' }, + { name: 'zoltar' }, + { name: 'parent' }, + { name: 'shareToken' }, + { name: 'repToken' }, + { name: 'priceOracleManagerAndOperatorQueuer' }, + { name: 'openOracle' }, + { name: 'escalationGameFactory' }, + { name: 'escalationGame', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'questionData' }, + { name: 'securityPoolForker' }, + { name: 'truthAuction' }, + { name: 'securityPoolFactory' }, + { name: 'totalCapacityOwnershipAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'settlementCollateralAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'totalRepBackingUnits', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'statoblastSecurityMultiplierBps', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'shareTokenSupplyAttoShares', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'totalClaimableVaultFeesAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'lastUpdatedFeeAccumulator', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'feeIndex', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'currentRetentionRate', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'awaitingForkContinuation', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'securityVaults', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'totalBadDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'vaultBadDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'systemState', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'minimumSecurityBondDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'minimumVaultRepDepositAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + { name: 'vaultTargetHealthFactorBps', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, + ], + sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', + interactions: [ + { + call: '`burnEscalationWinnerHaircut(amountAttoRep)`', + caller: "This pool's `EscalationGame` only", + effect: 'Burns the winning-deposit haircut from REP already escrowed in the game.', + declarations: [{ name: 'burnEscalationWinnerHaircut' }], + preconditions: 'Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool.', + signals: '`RepBurned` and ERC-20 `Transfer`; child REP also emits `Burn`', + }, + { + call: '`depositRepToVault(attoRepAmount, targetHealthFactorBps)`', + caller: 'Vault owner', + effect: 'Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from the deposit and selected target health factor.', + declarations: [{ name: 'depositRepToVault' }], + preconditions: 'Operational and unforked; `isEscalationResolved()` is false; target health factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum.', + signals: '`RepDepositedToVault`, the vault target-health-factor event, and accounting checkpoints', + }, + { + call: '`redeemFees(vault)`', + caller: 'Anyone; any nonzero ETH payment is always sent to `vault`', + effect: "First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount.", + declarations: [{ name: 'redeemFees' }], + preconditions: 'A nonzero payment path requires `vault` to accept ETH.', + signals: 'Accrual checkpoints only when accrual state changes; both `VaultAccountingCheckpoint` and `PoolAccountingCheckpoint` for a nonzero redemption; no event when fees and accrual state are unchanged', + }, + { + call: '`createCompleteSet()` with ETH', + caller: 'Trader', + effect: 'Adds collateral and mints one `Invalid`, `Yes`, and `No` share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint.', + declarations: [{ name: 'createCompleteSet' }], + preconditions: + 'Operational and unforked; `isEscalationResolved()` is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract trader accepts `onERC1155BatchReceived`.', + signals: '`CompleteSetCreated`, `PoolAccountingCheckpoint`, then ERC-1155 `TransferBatch` on a successful callback', + }, + { + call: '`redeemCompleteSet(amountAttoShares)`', + caller: 'Anyone; positive redemption requires the caller to hold the complete set', + effect: + "Burns equal balances of all three outcomes and pays `amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares` using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction.", + declarations: [{ name: 'redeemCompleteSet' }], + preconditions: 'Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance.', + signals: '`CompleteSetRedeemed` and `PoolAccountingCheckpoint`', + }, + { + call: '`redeemShares()`', + caller: 'Anyone; a positive payout requires the caller to hold winning shares', + effect: "Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction.", + declarations: [{ name: 'redeemShares' }], + preconditions: 'Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value.', + signals: '`SharesRedeemed` and `PoolAccountingCheckpoint`', + }, + { + call: '`redeemRepFromVault(vault)`', + caller: 'Anyone; REP is always sent to `vault`', + effect: "Burns the vault's REP backing units and returns its proportional vault REP backing.", + declarations: [{ name: 'redeemRepFromVault' }], + preconditions: 'Operational pool with a final outcome; the specified `vault` has no escalation escrow and has redeemable REP.', + signals: '`RepRedeemedFromVault`', + }, + { + call: '`depositToEscalationGame(outcome, maxAmount)`', + caller: 'Vault owner', + effect: + "Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses `nonDecisionThresholdAttoRep - 1` instead. Repeat deposits use the existing game's stored `startBondAttoRep` and `nonDecisionThresholdAttoRep`. Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome.", + declarations: [{ name: 'depositToEscalationGame' }], + preconditions: + 'Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero.', + signals: '`EscalationGameSet` on first deposit; `DepositToEscalationGame`', + }, + { + call: '`withdrawFromEscalationGame(outcome, depositIndexes)`', + caller: 'Anyone; a nonempty list must select deposits belonging to one original depositor', + effect: 'A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event.', + declarations: [{ name: 'withdrawFromEscalationGame' }], + preconditions: + 'Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor.', + signals: 'Per processed deposit, escalation-game `CarryDepositConsumed`; additionally `ClaimDeposit` for a winning payout. No event for an empty list', + }, + { + call: '`withdrawForkedEscalationDeposits(outcome, proofs)`', + caller: 'Anyone; a nonempty list must name one original depositor across all proofs', + effect: + 'A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event.', + declarations: [{ name: 'withdrawForkedEscalationDeposits' }], + preconditions: 'Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor.', + signals: 'Per processed proof, escalation-game `CarryDepositConsumed` and `ClaimDeposit`. No event for an empty list', + }, + { + call: '`updateSettlementCollateral()`', + caller: 'Anyone', + effect: + "Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees.", + declarations: [{ name: 'updateSettlementCollateral' }], + preconditions: 'No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp.', + signals: '`PoolAccountingCheckpoint` whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp', + }, + { + call: '`updateRetentionRate()`', + caller: 'Anyone', + effect: 'Recalculates the retention rate from current collateral and live oracle-priced minting capacity.', + declarations: [{ name: 'updateRetentionRate' }], + preconditions: 'No caller restriction. It returns unchanged when the pool is not `Operational` or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate.', + signals: '`PoolAccountingCheckpoint` only when the stored retention rate changes; no event for a no-op', + }, + { + call: '`updateVaultFees(vault)`', + caller: 'Anyone for any address', + effect: + 'First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership.', + declarations: [{ name: 'updateVaultFees' }], + preconditions: 'No caller, nonzero-vault, or lifecycle restriction.', + signals: 'Accrual `PoolAccountingCheckpoint` when due; `VaultAccountingCheckpoint` when the vault index, remainder, or claimable fee balance changes; an additional `PoolAccountingCheckpoint` when pool accounting changes; no event when neither accrual nor vault or pool accounting changes', + }, + { + call: '`withdrawRepFromVault(vault, attoRepAmount)`', + caller: "This pool's `OpenOraclePriceCoordinator` only", + effect: 'Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to `vault`.', + declarations: [{ name: 'withdrawRepFromVault' }], + preconditions: 'Fresh coordinator price; operational pool in an unforked universe; `isEscalationResolved()` is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy.', + signals: '`VaultTargetHealthFactorSet`; REP `Transfer`; `RepWithdrawnFromVault`; `VaultAccountingCheckpoint`; and applicable fee-accrual or retention `PoolAccountingCheckpoint` events, including a zero-value transfer/event path if the trusted coordinator supplies zero', + }, + { + call: '`performLiquidation(request)`', + caller: "This pool's `OpenOraclePriceCoordinator` only", + effect: + "Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt.", + declarations: [{ name: 'performLiquidation' }], + preconditions: + 'In ABI order, `request` contains `operationId`, `operator`, `receiverVault`, `targetVault`, `requestedDebtAttoEth`, `snapshot`, `minimumReceiverHealthFactorBps`, and `minLiquidationPriceDistanceBps`. The nested snapshot contains `targetBackingUnits`, `targetCapacityOwnershipAttoRep`, `totalPoolHeldAttoRep`, and `totalRepBackingUnits`. Fresh settled coordinator price; operational pool in an unforked universe; `isEscalationResolved()` is false; receiver differs from target. The target backing and capacity-ownership snapshot fields must match; the two pool-total snapshot fields are reconstruction evidence, while execution uses live pool totals. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least `minLiquidationPriceDistanceBps` beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor.', + signals: + 'Fee-accrual and target or receiver `VaultAccountingCheckpoint` events as needed; `VaultLiquidated` identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; `VaultBadDebtRecorded` records residual target debt on a full-target request; final pool accounting checkpoint', + }, + { + call: '`setStartingParams(...)`', + caller: '`SecurityPoolFactory` only', + effect: "Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization.", + declarations: [{ name: 'setStartingParams' }], + preconditions: 'Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring.', + signals: 'Coordinator `RepEthPriceSet` and `CoordinatorStateCheckpoint`, then pool `PoolAccountingCheckpoint`, even for zero or repeated values if the factory were to call again', + }, + { + call: '`activateForkMode()`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'activateForkMode' }], + effect: + "Sets `PoolForked`, accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints.", + preconditions: "The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data.", + signals: 'Pool-held REP `Transfer` always, including at zero; configured-game REP `Transfer` only for a positive game balance; accrual checkpoint when due; always `PoolForkModeActivated` and fork-activation `PoolAccountingCheckpoint`', + }, + { + call: '`initializeForkedEscalationGame(...)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'initializeForkedEscalationGame' }], + effect: "Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome.", + preconditions: 'No game is configured; downstream `startFromFork` parameters are valid.', + signals: 'Escalation `GameContinuedFromFork`, then pool `EscalationGameSet`', + }, + { + call: '`initializeForkCarrySnapshotWithResolutionBalances(...)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'initializeForkCarrySnapshotWithResolutionBalances' }], + effect: "Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots.", + preconditions: 'A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data.', + signals: '`ForkCarryCheckpoint`', + }, + { + call: '`resumeForkedEscalationGame()`', + caller: 'Anyone', + declarations: [{ name: 'resumeForkedEscalationGame' }], + effect: "Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call.", + preconditions: 'Pool is operational, awaiting a configured fork continuation, and the game has not resumed.', + signals: '`ForkContinuationResumed` and `AwaitingForkContinuationSet(false)`', + }, + { + call: '`setAwaitingForkContinuation(shouldAwait)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'setAwaitingForkContinuation' }], + effect: 'Stores whether complete-set minting must wait for continuation initialization.', + preconditions: 'No lifecycle or value-change guard.', + signals: '`AwaitingForkContinuationSet`, including for a repeated value', + }, + { + call: '`setSystemState(newState)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'setSystemState' }], + effect: 'Replaces the pool lifecycle state directly.', + preconditions: 'No transition or value-change guard.', + signals: '`SystemStateSet`, including for a repeated state', + }, + { + call: '`configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, targetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'configureVault' }], + effect: 'Replaces the vault REP backing units, price-independent capacity ownership, fee index, target health factor, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state.', + preconditions: '`vault` is nonzero; no lifecycle or value-change guard.', + signals: 'Always `VaultAccountingCheckpoint` and `PoolAccountingCheckpoint`, including when all supplied values repeat current state', + }, + { + call: '`setTotalRepBackingUnits(newDenominator)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'setTotalRepBackingUnits' }], + effect: 'Replaces the REP backing units denominator.', + preconditions: 'No lifecycle or value-change guard.', + signals: '`TotalRepBackingUnitsSet`, including for zero or a repeated value', + }, + { + call: '`setTotalSharesAttoShares(newTotalSharesAttoShares)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'setTotalSharesAttoShares' }], + effect: 'Replaces stored `shareTokenSupplyAttoShares`, the denominator used by `attoSharesToAttoEth` and complete-set redemption.', + preconditions: 'No lifecycle or value-change guard.', + signals: '`ShareTokenSupplySet`, including for zero or a repeated value', + }, + { + call: '`setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'setPoolFinancials' }], + effect: 'Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry.', + preconditions: 'Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard.', + signals: '`PoolAccountingCheckpoint`, including for repeated financial values', + }, + { + call: '`authorizeChildPool(pool)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'authorizeChildPool' }], + effect: 'Asks the lineage share token to establish `pool` as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op.', + preconditions: 'This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard.', + signals: '`AuthorizationUpdated` only on first authorization; no event when already authorized', + }, + { + call: '`transferEth(receiver, amountAttoEth)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'transferEth' }], + effect: 'Reduces tracked settlement collateral by `amount`, checkpoints the reconciliation, and calls `receiver` with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint.', + preconditions: 'Fee liabilities are covered; `amount` fits both unreserved pool ETH and tracked settlement collateral; `receiver` accepts the ETH call, including zero value.', + signals: '`PoolAccountingCheckpoint`, including at zero amount; no dedicated ETH-transfer event', + }, + { + call: '`addFeeEligibleCapacityOwnershipAttoRep(vault, amountAttoRep)`', + caller: '`SecurityPoolForker` only', + declarations: [{ name: 'addFeeEligibleCapacityOwnershipAttoRep' }], + effect: 'Adds newly auction-claimed capacity ownership to the live fee denominator, clears the pooled fee-index rounding remainder, then checkpoints elapsed fees and recalculates retention from collateral and unchanged total capacity ownership. The assignment itself does not change live minting capacity.', + preconditions: 'The resulting fee-eligible capacity ownership cannot exceed total capacity ownership; no lifecycle, vault, positive-amount, or value-change guard.', + signals: 'Retention-rate `PoolAccountingCheckpoint` first when the rate changes, then `VaultAccountingCheckpoint` and auction-claim `PoolAccountingCheckpoint`, including the latter two at zero amount; the calling forker emits `ClaimAuctionProceeds` only after the broader credit workflow completes', + }, + { + call: 'Direct ETH transfer to `receive()`', + caller: "Forker, this pool's truth auction, or parent pool only", + effect: 'Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees.', + declarations: [{ kind: 'receive', name: 'receive' }], + preconditions: 'Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard.', + signals: 'No dedicated receive event; the calling protocol step emits its own event', + }, + ], + }, + { + compiledAbiFingerprint: '53bc009e3dfbd79b99b31c22b2128cf93b16b73059ce344190ce65b39400183c', + name: 'SecurityPoolForker', + purpose: 'Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions.', + readAbiFingerprint: '2d321031db910e3feec1b22c481203de331c894217e60456fa429472808aa4a4', + readSurface: + 'Use `zoltar`, `forkData`, `getMigratedAttoRep`, `getForkActivationTime`, `isEscalationDepositClaimedDirectly`, `getEscalationDepositId`, `getDirectlyClaimedEscalationPrincipal`, `isEscalationWinnerHaircutPaidByFork`, `getEscalationMigrationEntitlementStatus`, `getOwnForkRepBuckets`, `getOwnForkMigrationStatus`, `getMigrationProxyAddress`, `getQuestionOutcome`, `attoRepToBackingUnits`, and `backingUnitsToAttoRep` to reconstruct fork progress and preview migration conversions.', + readDeclarations: [ + { name: 'forkData' }, + { name: 'getMigratedAttoRep' }, + { name: 'getForkActivationTime' }, + { name: 'isEscalationDepositClaimedDirectly' }, + { name: 'getEscalationDepositId' }, + { name: 'getDirectlyClaimedEscalationPrincipal' }, + { name: 'isEscalationWinnerHaircutPaidByFork' }, + { name: 'getEscalationMigrationEntitlementStatus' }, + { name: 'getOwnForkRepBuckets' }, + { name: 'getOwnForkMigrationStatus' }, + { name: 'getMigrationProxyAddress' }, + { name: 'getQuestionOutcome' }, + { name: 'attoRepToBackingUnits', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }, + { name: 'backingUnitsToAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }, + ], + readStorageDeclarations: [{ name: 'zoltar', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }], + securityBoundaryHeading: 'Child-game trust boundary', + securityBoundary: + 'Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured `SecurityPoolFactory` registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from `securityPool()` when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed `EscalationGame` instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do.', + sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', + interactions: [ + { + call: '`initiateSecurityPoolFork(securityPool)`', + caller: 'Anyone', + effect: 'Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured `SecurityPoolFactory`.', + declarations: [{ name: 'initiateSecurityPoolFork' }], + preconditions: + 'Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from `securityPool()` when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the [child-game trust boundary](#child-game-trust-boundary).', + signals: '`SecurityPoolForkSnapshot` and `ParentRepLocked`; additionally `DisputeStakedRepDrainedAtFork` when unresolved escalation exists', + }, + { + call: '`forkZoltarWithOwnEscalationGame(securityPool)`', + caller: 'Anyone', + effect: "Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured `SecurityPoolFactory`.", + declarations: [{ name: 'forkZoltarWithOwnEscalationGame' }], + preconditions: + 'Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from `securityPool()` when validated and `canTriggerOwnFork()` is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the [child-game trust boundary](#child-game-trust-boundary).', + signals: '`SecurityPoolForkSnapshot`, `ParentRepLocked`, and Zoltar fork events; additionally `DisputeStakedRepDrainedAtFork` when unresolved escalation exists', + }, + { + call: '`migrateRepToZoltar(securityPool, outcomeIndices)`', + caller: 'Anyone', + effect: "For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events.", + declarations: [{ name: 'migrateRepToZoltar' }], + preconditions: + 'Migration proxy exists and the pool is `PoolForked`. Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child `ForkMigration` state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied.', + signals: '`MigrationRepSplit` and `ChildRepSplit` when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch', + }, + { + call: '`createChildUniverse(securityPool, outcomeIndex)`', + caller: 'Anyone', + effect: + "Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game.", + declarations: [{ name: 'createChildUniverse' }], + preconditions: + "Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary). These relationship checks do not independently prove configured-factory registration.", + signals: + '`DeployChild` only when child REP was absent; always `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, `ChildPoolLinked`, and `TotalRepBackingUnitsSet`; `AwaitingForkContinuationSet`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, `MigrationRepSplit`, `ChildDisputeStakedRepMaterialized`, and `PoolHeldRepSweptToChild` as continuation and backing state requires', + }, + { + call: '`migrateVault(securityPool, outcomeIndex)`', + caller: 'Vault owner for their non-escrowed position', + declarations: [{ name: 'migrateVault' }], + effect: + "Converts the caller's parent REP backing-unit claim to REP at the fork snapshot and credits that REP amount as child-local backing units; transfers REP-denominated capacity ownership, target health factor, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move.", + preconditions: "Migration window open; the selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary). The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state.", + signals: '`VaultBadDebtMigrated` and `VaultMigrationCheckpoint`', + }, + { + call: '`migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex)`', + caller: 'The named vault owner', + effect: + "First runs ordinary migration for the same vault, which may convert its parent REP backing-unit claim to REP and credit that REP as child-local backing units; transfer capacity ownership, target health factor, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs.", + declarations: [{ name: 'migrateVaultWithUnresolvedEscalation' }], + preconditions: "Migration window open; caller equals `vault`; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary).", + signals: 'Vault migration events, including `VaultBadDebtMigrated`, plus `EscalationMigrationEntitlementInitialized` on first export and `EscalationMigrationEntitlementMaterialized` for the selected child', + }, + { + call: '`claimForkedEscalationDeposits(...)`', + caller: 'The named vault owner', + effect: + "First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary.", + declarations: [{ name: 'claimForkedEscalationDeposits' }], + preconditions: + 'Caller equals `vault`; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies `canTriggerOwnFork()` by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in `ForkMigration`, has a continuation game that passes the [child-game trust boundary](#child-game-trust-boundary), and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit `vault` as its immutable depositor.', + signals: + '`DeployChild`, `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, `ChildPoolLinked`, `TotalRepBackingUnitsSet`, `AwaitingForkContinuationSet`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, `MigrationRepSplit`, `ChildDisputeStakedRepMaterialized`, and `PoolHeldRepSweptToChild` as setup requires; per claimed deposit, `CarryDepositConsumed` and `ClaimDeposit`; escrow record/export events when REP is paid; always `ClaimForkedEscalationDepositsToWallet`, including for an empty list', + }, + { + call: '`startTruthAuction(securityPool)`', + caller: 'Anyone', + effect: "Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction.", + declarations: [{ name: 'startTruthAuction' }], + preconditions: 'Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the [child-game trust boundary](#child-game-trust-boundary).', + signals: '`ShareTokenSupplySet` and `TruthAuctionStarted`; immediate no-auction completion also emits `TruthAuctionFinalized`, pool accounting checkpoints, and `ForkContinuationResumed` for an unresolved continuation', + }, + { + call: '`finalizeTruthAuction(securityPool)`', + caller: 'Anyone', + effect: 'Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, activates the child at that settlement-collateral level, and fixes bidder REP-backing-unit and capacity-ownership rates. A nonzero repair contribution is rejected.', + declarations: [{ name: 'finalizeTruthAuction' }], + preconditions: + 'Truth auction started, its one-week window has passed, `msg.value` is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the [child-game trust boundary](#child-game-trust-boundary).', + signals: '`TruthAuctionFinalized`, auction `AuctionFinalized`, and pool accounting checkpoints; `TruthAuctionHaircutApplied` when purchased REP removes a positive escalation allocation; `ForkContinuationResumed` for an unresolved continuation', + }, + { + call: '`settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices)`', + caller: 'Anyone on behalf of the named bidder vault', + declarations: [{ name: 'settleAuctionBids' }], + effect: + 'Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and credits each fixed-position REP backing and capacity-ownership result. It also assigns the bidder vault its cumulative share of auctioned bad debt: intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion.', + preconditions: 'At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled.', + signals: 'Underlying auction `BidSettled`; `EthRefundDeferred` when the named bidder rejects a positive refund; `ClaimAuctionProceeds` with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited', + }, + { + call: '`claimAuctionProceeds(securityPool, vault, tickIndices)`', + caller: 'Anyone on behalf of the named bidder vault', + declarations: [{ name: 'claimAuctionProceeds' }], + effect: + 'For a nonempty list, withdraws finalized bid settlements, converts purchased REP into child REP backing units, independently credits the bid positional capacity-ownership allocation, and assigns the bidder vault its cumulative share of auctioned bad debt. Intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns three zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events.', + preconditions: 'Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled.', + signals: 'For processed bids, underlying auction `BidSettled`; `EthRefundDeferred` when the named bidder rejects a positive refund; `ClaimAuctionProceeds` with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list', + }, + { + call: '`initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame)`', + caller: 'This `SecurityPoolForker` contract only, through its migration delegate callback', + effect: + 'Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use.', + declarations: [{ name: 'initializeChildForkedEscalationGameIfNeeded' }], + preconditions: 'External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the [child-game trust boundary](#child-game-trust-boundary).', + signals: '`ChildDisputeStakedRepMaterialized` and escalation-continuation events when initialization is required', + }, + { + call: 'Direct ETH transfer to `receive()`', + caller: 'A child-pool truth auction trusted by this forker during `ChildPoolLinked`', + effect: 'Accepts auction ETH during forker-controlled auction finalization.', + declarations: [{ kind: 'receive', name: 'receive' }], + preconditions: '`trustedAuctionAddresses[msg.sender]` was set when the forker linked the child and emitted `ChildPoolLinked`; configured-factory registration determines whether that lineage is canonical.', + signals: 'No dedicated receive event; auction `AuctionFinalized` is followed by forker `TruthAuctionFinalized` and pool accounting checkpoints', + }, + ], + }, + { + compiledAbiFingerprint: 'aa111e15b811c762945753415ef818ed6f85ec81553ab7ede082aca87869ad64', + name: 'EscalationGame', + purpose: 'Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits.', + readAbiFingerprint: 'ed587e847ca84dfb0faa31896f294197b8e84a13c229b3bab68447f262dae58d', + readSurface: + 'Base getters are `securityPool`, `repToken`, `activationTime`, `nonDecisionThresholdAttoRep`, `startBondAttoRep`, `nonDecisionTimestamp`, `nonDecisionState`, `forkContinuation`, `forkElapsedAtStart`, `forkResumedAt`, `fixedQuestionOutcome`, `nodes`, `disputeStakedRepByVaultAttoRep`, `totalDisputeStakedAttoRep`, `truthAuctionRepBeforeAttoRep`, `truthAuctionRepRemainingAttoRep`, `cumulativeClaimRetention`, and `cumulativeClaimRetentionExponent`. The claim delegate fallback exposes `rootClaimSourceGame`, `applyInheritedClaimRetention`, and `applyInheritedSourceStorageBasis`. The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. `disputeStakedRepByVaultAttoRep` is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use `previewDepositOnOutcome`, `computeIterativeAttritionCostAttoRep`, `computeTimeSinceStartFromAttritionCostAttoRep`, `totalCostAttoRep`, `getEscalationGameEndDate`, `getQuestionResolution`, `getFinalQuestionResolution`, `hasReachedNonDecision`, `canTriggerOwnFork`, `getBindingCapitalAttoRep`, `getOutcomeBalancesAttoRep`, `getDepositsByOutcome`, `getDepositsByOutcomeLength`, `forkCarrySnapshotInitialized`, `getOutcomeState`, `getForkCarrySnapshot`, `getForkCarryRoots`, `isForkCarryFundingComplete`, `getCarryLeafPageByOutcome`, `getProofConsumedCarriedDepositIndexesByOutcome`, `getLocalUnresolvedPrincipalByVaultAndOutcome`, and `getForkedEscrowByVaultAndOutcome` for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through `SecurityPool`.', + readDeclarations: [ + { name: 'previewDepositOnOutcome' }, + { name: 'disputeStakedRepByVaultAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, + { name: 'rootClaimSourceGame', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, + { name: 'applyInheritedClaimRetention', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, + { name: 'applyInheritedSourceStorageBasis', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, + { name: 'computeIterativeAttritionCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'computeTimeSinceStartFromAttritionCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'totalCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getEscalationGameEndDate', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getQuestionResolution', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getFinalQuestionResolution', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'hasReachedNonDecision', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'canTriggerOwnFork', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getBindingCapitalAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getOutcomeBalancesAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, + { name: 'getDepositsByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }, + { name: 'getDepositsByOutcomeLength', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }, + { name: 'forkCarrySnapshotInitialized', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getOutcomeState', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getForkCarrySnapshot', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getForkCarryRoots', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'isForkCarryFundingComplete', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getCarryLeafPageByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getProofConsumedCarriedDepositIndexesByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, + { name: 'getLocalUnresolvedPrincipalByVaultAndOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }, + { name: 'getForkedEscrowByVaultAndOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }, + ], + readStorageDeclarations: [ + { name: 'securityPool', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, + { name: 'repToken', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, + { name: 'activationTime', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'nonDecisionThresholdAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'startBondAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'nonDecisionTimestamp', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'nonDecisionState', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'forkContinuation', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'forkElapsedAtStart', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'forkResumedAt', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'nodes', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'totalDisputeStakedAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'truthAuctionRepBeforeAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'truthAuctionRepRemainingAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'cumulativeClaimRetention', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'cumulativeClaimRetentionExponent', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + { name: 'fixedQuestionOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, + ], + sourcePath: 'solidity/contracts/peripherals/EscalationGame.sol', + interactions: [ + { + call: '`start(startBondAttoRep, nonDecisionThresholdAttoRep)`', + caller: '`EscalationGameFactory` contract during atomic deployment', + effect: 'Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to `nonDecisionThresholdAttoRep - 1` before this call.', + declarations: [{ name: 'start' }], + preconditions: 'Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid.', + signals: '`GameStarted`', + }, + { + call: '`startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep)`', + caller: 'Immutable owner (`EscalationGameFactory`) during atomic continuation deployment', + effect: 'Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until `resumeFromFork`.', + declarations: [{ name: 'startFromFork' }], + preconditions: 'Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid.', + signals: '`GameContinuedFromFork`', + }, + { + call: '`resumeFromFork()`', + caller: 'Owning `SecurityPool` only', + effect: + 'Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is `max(rebasedCurveEnd, forkResumedAt + 3 days)`, so even an exhausted inherited clock receives a fresh response period. After that deadline, `getFinalQuestionResolution` returns the fixed outcome when the continuation has one.', + declarations: [{ name: 'resumeFromFork' }], + preconditions: + 'Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least `sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋`, where `sourcePrincipalAtForkAttoRep` is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims.', + signals: '`ForkContinuationResumed`', + }, + { + call: '`applyTruthAuctionHaircut(repToRemove)`', + caller: "The child pool's `SecurityPoolForker` only", + declarations: [{ name: 'applyTruthAuctionHaircut' }], + effect: 'Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it.', + preconditions: "Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance.", + signals: '`TruthAuctionHaircutApplied` and REP `Transfer`', + }, + { + call: '`recordDepositFromSecurityPool(...)`', + caller: 'Owning `SecurityPool` only', + effect: 'Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf.', + declarations: [{ name: 'recordDepositFromSecurityPool' }], + preconditions: 'Explicit non-decision state is `None`; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold.', + signals: '`LocalDepositAppended`, `DepositOnOutcome`, optionally `NonDecisionReached`', + }, + { + call: '`withdrawDeposit(uint256 depositIndex, outcome)`', + caller: 'Owning `SecurityPool` only', + declarations: [{ name: 'withdrawDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: "Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting.", + preconditions: 'Explicit non-decision state is `None`; non-`None` supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index.', + signals: '`CarryDepositConsumed` and `VaultEscrowUpdated`; for a winner, `ClaimDeposit`, positive REP payout `Transfer`, and haircut burn signals when nonzero', + }, + { + call: '`initializeForkCarrySnapshotWithResolutionBalances(...)`', + caller: 'Owning `SecurityPool` only', + declarations: [{ name: 'initializeForkCarrySnapshotWithResolutionBalances', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }], + effect: 'Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set `nonDecisionState` to `InheritedThresholdTie` without creating a local timestamp.', + preconditions: 'Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data.', + signals: '`ForkCarryCheckpoint`; additionally `InheritedThresholdTie` when the installed balances meet the non-decision threshold', + }, + { + call: '`claimDepositForWinning(depositIndex, outcome)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'claimDepositForWinning', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: "Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor.", + preconditions: 'Non-`None` supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path.', + signals: '`CarryDepositConsumed`, `VaultEscrowUpdated`, `ClaimDeposit` with `transferredRep = true`; REP payout `Transfer` and haircut burn signals only when their amounts are positive', + }, + { + call: '`claimDepositForWinningWithoutTransfer(depositIndex, outcome)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'claimDepositForWinningWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: + "Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or `⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉` after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut.", + preconditions: 'Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non-`None` guard; neither form checks final resolution or that the outcome won.', + signals: '`CarryDepositConsumed`, `VaultEscrowUpdated`, and `ClaimDeposit` with `transferredRep = false`; no REP transfer or haircut burn', + }, + { + call: '`exportUnresolvedDeposit(depositIndex, outcome)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'exportUnresolvedDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: 'Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP.', + preconditions: 'Non-`None` outcome and a valid unsettled local deposit. Final resolution is not required.', + signals: '`CarryDepositConsumed` and `VaultEscrowUpdated`; no `ClaimDeposit` or REP transfer', + }, + { + call: '`withdrawDeposit(CarriedDepositProof proof, outcome)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'withdrawDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: 'Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it.', + preconditions: 'Non-`None` supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof.', + signals: '`CarryDepositConsumed` and `ClaimDeposit` with `transferredRep = true`; REP payout `Transfer` and haircut burn signals only when positive', + }, + { + call: '`exportVaultUnresolvedTotals(vault, repReceiver)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'exportVaultUnresolvedTotals', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], + effect: "Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to `repReceiver`.", + preconditions: '`vault` is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted.', + signals: 'Always `VaultUnresolvedTotalsExported`, including when every amount is zero; `VaultEscrowUpdated` and REP `Transfer` only for a positive total', + }, + { + call: '`exportVaultUnresolvedTotalsWithoutTransfer(vault)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'exportVaultUnresolvedTotalsWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], + effect: "Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller.", + preconditions: '`vault` is nonzero and has not exported before.', + signals: 'Always `VaultUnresolvedTotalsExported` with `transferredRep = false`, including when every amount is zero; `VaultEscrowUpdated` only for a positive total; no REP transfer', + }, + { + call: '`drainAllRep(receiver)`', + caller: 'Owning `SecurityPool` only', + declarations: [{ name: 'drainAllRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + effect: "Transfers the game's full REP balance to `receiver`. A zero balance returns zero without a transfer or event.", + preconditions: '`receiver` is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after `activateForkMode` enters `PoolForked`.', + signals: 'REP `Transfer` for a positive balance; no event at zero balance', + }, + { + call: '`recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'recordForkedEscrowForOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], + effect: + 'Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event.', + preconditions: 'Outcome is not `None`; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op.', + signals: '`ForkedEscrowRecorded` for a nonzero record; no event when both amounts are zero', + }, + { + call: '`exportForkedEscrowByOutcome(vault, repReceiver)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'exportForkedEscrowByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], + effect: 'Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event.', + preconditions: '`vault` and `repReceiver` are nonzero.', + signals: '`ForkedEscrowExported` when any source principal or child REP remains; REP `Transfer` when positive child REP is transferred; no event for an already-empty export', + }, + { + call: '`exportForkedEscrowByOutcomeWithoutTransfer(vault)`', + caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', + declarations: [{ name: 'exportForkedEscrowByOutcomeWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], + effect: 'Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event.', + preconditions: '`vault` is nonzero.', + signals: '`ForkedEscrowExported` with `transferredRep = false` when any source principal or child REP remains; no REP transfer; no event for an already-empty export', + }, + { + call: '`sweepResidualRepToSecurityPool()`', + caller: 'Anyone', + effect: 'Returns ordinary-game residual REP to the owning pool. Burns fork-continuation residual so pre-child capital cannot accrue to late or nonexistent child owners.', + declarations: [{ name: 'sweepResidualRepToSecurityPool', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], + preconditions: 'Final outcome; no unresolved principal; no vault escrow; positive residual balance.', + signals: '`ResidualRepSweptToSecurityPool` for an ordinary game; `ForkContinuationResidualRepBurned` for a fork continuation', + }, + ], + }, + { + compiledAbiFingerprint: '24fef7375af443bf5477c0c4afa6d6ce6ef852f82a8b17d46bd1cea15bc3c264', + name: 'LiquidationApprovalRegistry', + purpose: 'Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator.', + readAbiFingerprint: '04465d90cef2bd37454bf8496fffcaccf07f0ec5808f31ffd6481d4cdc46f810', + readSurface: + 'Use `coordinator` to identify the validating coordinator and implied security pool. `LIQUIDATION_APPROVAL_TYPEHASH`, `DOMAIN_SEPARATOR`, and `liquidationApprovalDigest` define the chain- and registry-bound EIP-712 message. `getLiquidationApproval` reports parameters plus available, reserved, consumed, and revoked state; `minimumLiquidationApprovalNonce` reports receiver invalidation state; `liquidationReservations` and `minimumHealthFactorBps` expose operation reservation state and its execution-time health floor.', + readDeclarations: [{ name: 'DOMAIN_SEPARATOR' }, { name: 'liquidationApprovalDigest' }, { name: 'getLiquidationApproval' }, { name: 'minimumHealthFactorBps' }], + readStorageDeclarations: [{ name: 'coordinator' }, { name: 'LIQUIDATION_APPROVAL_TYPEHASH' }, { name: 'minimumLiquidationApprovalNonce' }, { name: 'liquidationReservations' }], + sourcePath: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', + interactions: [ + { + call: '`initialize(coordinator)`', + caller: 'Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically', + effect: 'Binds this registry clone to one coordinator and therefore one security pool.', + declarations: [{ name: 'initialize' }], + preconditions: 'Coordinator is nonzero and the registry has not been initialized.', + signals: 'No event; the public `coordinator` getter records the binding.', + }, + { + call: '`setLiquidationApproval(params)`', + caller: 'The receiver vault named by `params`', + effect: 'Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce.', + declarations: [{ name: 'setLiquidationApproval' }], + preconditions: 'Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce.', + signals: '`LiquidationApprovalSet`', + }, + { + call: '`permitLiquidationApproval(params, signature)`', + caller: 'Anyone relaying the receiver vault signature', + effect: 'Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce.', + declarations: [{ name: 'permitLiquidationApproval' }], + preconditions: 'Signature is valid for `params.receiverVault`; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass.', + signals: '`LiquidationApprovalSet`', + }, + { + call: '`revokeLiquidationApproval(approvalId)`', + caller: 'Approval receiver vault only', + effect: 'Prevents new reservations while leaving reservations already attached to staged operations intact.', + declarations: [{ name: 'revokeLiquidationApproval' }], + preconditions: 'Approval exists and is not already revoked.', + signals: '`LiquidationApprovalRevoked` with available, reserved, and consumed totals', + }, + { + call: '`invalidateLiquidationApprovalNonce(newNonce)`', + caller: 'Receiver vault invalidating its own older nonce range', + effect: 'Raises the minimum nonce accepted for new approval installation or reservation.', + declarations: [{ name: 'invalidateLiquidationApprovalNonce' }], + preconditions: 'New nonce is greater than the receiver current minimum.', + signals: '`LiquidationApprovalNonceInvalidated`', + }, + { + call: '`reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp)`', + caller: 'Bound coordinator only', + effect: 'Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota.', + declarations: [{ name: 'reserve' }], + preconditions: 'Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota.', + signals: '`LiquidationApprovalReserved`', + }, + { + call: '`release(operationId)`', + caller: 'Bound coordinator only', + effect: 'Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op.', + declarations: [{ name: 'release' }], + preconditions: 'Coordinator terminal cleanup path.', + signals: '`LiquidationApprovalReleased` when quota is returned', + }, + { + call: '`consume(operationId, debtMovedAttoEth)`', + caller: 'Bound coordinator only', + effect: 'Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once.', + declarations: [{ name: 'consume' }], + preconditions: 'For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op.', + signals: '`LiquidationApprovalConsumed`', + }, + ], + }, + { + compiledAbiFingerprint: '60cd10890a685efe179e17e93a783c660542bd6368f86fed87f46de03695b243', + name: 'OpenOraclePriceCoordinator', + purpose: 'Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup.', + readAbiFingerprint: '288a73d13de5a0f593226105eb11eb177bf085ac2ee708a31645c3d7c4eb7237', + readSurface: + 'Configuration getters are `MAX_PENDING_SETTLEMENT_OPERATIONS`, `OPEN_INTEREST_DIVIDER`, `reputationToken`, `securityPool`, `openOracle`, `weth`, `liquidationApprovalRegistry`, `gasConsumedOpenOracleReportPrice`, `gasConsumedSettlement`, `gasUnitsForOneDispute`, `initialReportPriorityFeeAttoEthPerGas`, `targetPriceErrorForDispute`, `openOracleSecurityMultiplierBps`, `settlementTime`, `disputeDelay`, `protocolFee`, `feePercentage`, `multiplier`, `timeType`, `trackDisputes`, `protocolFeeRecipient`, `escalationHaltMultiplierBps`, `maxSettlementBaseFeeMultiplierBps`, and `minLiquidationPriceDistanceBps`. Current report and operation getters are `pendingReportId`, `pendingReportSponsor`, `pendingOperationSlotId`, `lastSettlementTimestamp`, `lastPrice`, `pendingReportMaxSettlementBaseFeeAttoEthPerGas`, `stagedOperationCounter`, and `stagedOperations`. Use `isPriceValid`, `minimumToken1ReportAttoEth`, `getRequestPriceCostAttoEth`, `getQueuedOperationCostAttoEth`, `getSettlementCallbackGasLimit`, `getPendingOperationSlot`, `getActiveStagedOperationCount`, `getActiveStagedOperations`, `getPendingSettlementOperationCount`, and `getPendingSettlementOperationIds` for derived or paged state.', + securityBoundary: + 'Report and staged-operation liveness depends on [A16 timely inclusion](./security-model.html#assumption-a16), [A17 corrector capability](./security-model.html#assumption-a17), [A18 independent correction incentive](./security-model.html#assumption-a18), [A19 observable correctable price](./security-model.html#assumption-a19), and [A06 lifecycle executors](./security-model.html#assumption-a06). When `lastPrice` is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path.', + readDeclarations: [ + { name: 'isPriceValid' }, + { name: 'minimumToken1ReportAttoEth' }, + { name: 'getRequestPriceCostAttoEth' }, + { name: 'getQueuedOperationCostAttoEth' }, + { name: 'getSettlementCallbackGasLimit' }, + { name: 'getPendingOperationSlot' }, + { name: 'getActiveStagedOperationCount' }, + { name: 'getPendingSettlementOperationCount' }, + { name: 'getPendingSettlementOperationIds' }, + { name: 'getActiveStagedOperations' }, + ], + readStorageDeclarations: [ + { name: 'MAX_PENDING_SETTLEMENT_OPERATIONS' }, + { name: 'OPEN_INTEREST_DIVIDER' }, + { name: 'pendingReportId' }, + { name: 'pendingReportSponsor' }, + { name: 'pendingOperationSlotId' }, + { name: 'lastSettlementTimestamp' }, + { name: 'lastPrice' }, + { name: 'reputationToken' }, + { name: 'securityPool' }, + { name: 'openOracle' }, + { name: 'weth' }, + { name: 'gasConsumedOpenOracleReportPrice' }, + { name: 'gasConsumedSettlement' }, + { name: 'gasUnitsForOneDispute' }, + { name: 'initialReportPriorityFeeAttoEthPerGas' }, + { name: 'targetPriceErrorForDispute' }, + { name: 'openOracleSecurityMultiplierBps' }, + { name: 'settlementTime' }, + { name: 'disputeDelay' }, + { name: 'protocolFee' }, + { name: 'feePercentage' }, + { name: 'multiplier' }, + { name: 'timeType' }, + { name: 'trackDisputes' }, + { name: 'protocolFeeRecipient' }, + { name: 'escalationHaltMultiplierBps' }, + { name: 'maxSettlementBaseFeeMultiplierBps' }, + { name: 'minLiquidationPriceDistanceBps' }, + { name: 'pendingReportMaxSettlementBaseFeeAttoEthPerGas' }, + { name: 'stagedOperationCounter' }, + { name: 'stagedOperations' }, + { name: 'liquidationApprovalRegistry' }, + ], + sourcePath: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', + interactions: [ + { + call: '`requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...)`', + caller: 'Liquidation operator; a delegated receiver must have approved this exact operator', + effect: 'Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID.', + declarations: [{ name: 'requestPriceIfNeededAndStageLiquidation' }], + preconditions: 'Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution.', + signals: '`LiquidationRouteStaged`; `LiquidationApprovalReserved` on a delegated route; staged-operation lifecycle events', + }, + { + call: '`requestPriceIfNeededAndStageOperation(...)` with funding when stale', + caller: 'Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations.', + effect: + 'Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report.', + declarations: [{ name: 'requestPriceIfNeededAndStageOperation' }], + preconditions: + '`securityPool.isEscalationResolved()` is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund.', + signals: '`StagedOperationQueued`, possibly `PriceRequested`, then `ExecutedStagedOperation`; authoritative `CoordinatorStateCheckpoint` records', + }, + { + call: '`requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth)` with report funding', + caller: 'Anyone when no fresh price or report is pending', + effect: 'Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position.', + declarations: [{ name: 'requestPrice' }], + preconditions: + 'Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund.', + signals: '`PriceRequested` and `CoordinatorStateCheckpoint`', + }, + { + call: '`executeStagedOperation(operationId)`', + caller: 'Anyone', + effect: + "Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds.", + declarations: [{ name: 'executeStagedOperation' }], + preconditions: 'Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried.', + signals: '`ExecutedStagedOperation`, either `LiquidationApprovalConsumed` or `LiquidationApprovalReleased` for a delegated liquidation, and `CoordinatorStateCheckpoint`', + }, + { + call: '`expireStagedOperation(operationId)`', + caller: 'Anyone', + effect: 'Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price.', + declarations: [{ name: 'expireStagedOperation' }], + preconditions: 'Operation exists and its settlement-plus-validity window has elapsed.', + signals: '`ExecutedStagedOperation`, `LiquidationApprovalReleased` for a delegated liquidation, and `CoordinatorStateCheckpoint`', + }, + { + call: '`recoverSettledPendingReport()`', + caller: 'Anyone', + effect: 'Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active.', + declarations: [{ name: 'recoverSettledPendingReport' }], + preconditions: 'A pending report ID exists and its stored OpenOracle `storedGame(reportId).settlementTimestamp` is nonzero.', + signals: '`PendingReportRecovered`, failed `ExecutedStagedOperation` for each live attached operation, `LiquidationApprovalReleased` for each attached delegated liquidation, and `CoordinatorStateCheckpoint`', + }, + { + call: '`openOracleCallback(...)`', + caller: 'Configured `OpenOracle` only', + effect: 'A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation.', + declarations: [{ name: 'openOracleCallback' }], + preconditions: 'Callback report matches the pending report; excessive settlement basefee, a saturated `uint24` report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state.', + signals: '`PriceReported` or `PriceReportRejected`; operation execution events; authoritative `CoordinatorStateCheckpoint` records', + }, + { + call: '`setLiquidationApprovalRegistry(registry)`', + caller: 'Coordinator deployment factory only', + effect: 'Binds the coordinator-local approval registry once.', + declarations: [{ name: 'setLiquidationApprovalRegistry' }], + preconditions: 'Registry is nonzero and no registry was previously installed.', + signals: 'No event; deterministic factory deployment and the public getter identify the registry.', + }, + { + call: '`setSecurityPool(pool)`', + caller: 'Anyone while `securityPool` remains zero; normal factory deployment calls atomically', + effect: 'A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator.', + declarations: [{ name: 'setSecurityPool' }], + preconditions: 'Current `securityPool` is zero; the argument itself is not required to be nonzero.', + signals: '`SecurityPoolSet` and `CoordinatorStateCheckpoint`', + }, + { + call: '`setRepEthPrice(price)`', + caller: 'Configured nonzero `SecurityPool` only', + effect: "Seeds the coordinator's price value, including zero, for inherited child state.", + declarations: [{ name: 'setRepEthPrice' }], + preconditions: 'Caller equals the configured pool.', + signals: '`RepEthPriceSet` and `CoordinatorStateCheckpoint`', + }, + ], + }, + { + compiledAbiFingerprint: 'b4d43db4a275c3118a700ca255a7f63d42dfdca1fb1e7c554d681e589a76ac85', + name: 'ShareToken', + purpose: "Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches.", + readAbiFingerprint: '6093653de73a0e5fa1e400d77bbded71a92de1197f58bd89da82a657887f349e', + readSurface: + 'Base and relationship getters are `name`, `symbol`, `zoltar`, `canonicalPoolByUniverse`, `_balances`, `_supplies`, and `_operatorApprovals`. Standard ERC-1155 reads are `supportsInterface`, `balanceOf`, `totalSupply`, `balanceOfBatch`, and `isApprovedForAll`; protocol-specific reads are `isAuthorized`, `totalSupplyForOutcome`, `maximumOutcomeSupply`, `balanceOfOutcome`, `balanceOfShares`, `getMigratedShareAmountAttoShares`, `getTokenId`, `getTokenIds`, and `unpackTokenId`.', + readDeclarations: [ + { name: 'supportsInterface', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: 'balanceOf', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: 'totalSupply', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: 'balanceOfBatch', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: 'isApprovedForAll', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: 'isAuthorized' }, + { name: 'totalSupplyForOutcome' }, + { name: 'maximumOutcomeSupply' }, + { name: 'balanceOfOutcome' }, + { name: 'balanceOfShares' }, + { name: 'getMigratedShareAmountAttoShares' }, + { name: 'getTokenId' }, + { name: 'getTokenIds' }, + { name: 'unpackTokenId' }, + ], + readStorageDeclarations: [ + { name: 'name' }, + { name: 'symbol' }, + { name: 'zoltar' }, + { name: 'canonicalPoolByUniverse' }, + { name: '_balances', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: '_supplies', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + { name: '_operatorApprovals', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, + ], + sourcePath: 'solidity/contracts/peripherals/tokens/ShareToken.sol', + interactions: [ + { + call: '`setApprovalForAll(operator, approved)`', + caller: 'Any token account setting its own operator approval', + effect: "Sets or clears the operator's authority over all of the caller's outcome-token balances.", + declarations: [{ name: 'setApprovalForAll', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], + preconditions: 'The operator differs from the caller.', + signals: '`ApprovalForAll`', + }, + { + call: 'Both `safeTransferFrom(...)` overloads', + caller: 'Share holder or approved ERC-1155 operator', + effect: 'Transfers one outcome-token balance without changing supply.', + declarations: [{ name: 'safeTransferFrom', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], + preconditions: + 'Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract recipient accepts the ERC-1155 callback.', + signals: '`TransferSingle`', + }, + { + call: 'Both `safeBatchTransferFrom(...)` overloads', + caller: 'Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch', + effect: 'A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event.', + declarations: [{ name: 'safeBatchTransferFrom', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], + preconditions: + 'ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks.', + signals: '`TransferBatch` for a nonempty batch; no event for an empty batch', + }, + { + call: '`migrate(fromId, targetOutcomeIndexes)`', + caller: 'Holder of the source token ID', + effect: + "If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup.", + declarations: [{ name: 'migrate' }], + preconditions: + 'Source universe forked; canonical source pool is `Operational` or `PoolForked`, and an `Operational` source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract holder accepts `onERC1155Received` for every target mint.', + signals: + '`PoolForkModeActivated`, `PoolAccountingCheckpoint`, `SecurityPoolForkSnapshot`, `ParentRepLocked`, and optionally `DisputeStakedRepDrainedAtFork` when auto-forking; `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, and `ChildPoolLinked` when lazily deploying, plus `DeployChild`, `ChildRepSplit`, `PoolHeldRepSweptToChild`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, and `ChildDisputeStakedRepMaterialized` as applicable; then one ERC-1155 mint `TransferSingle` and `Migrate` per materialized target on successful callbacks', + }, + { + call: '`authorize(securityPoolCandidate)`', + caller: 'Initially authorized `SecurityPoolFactory` for an origin pool; an authorized parent `SecurityPool` for a child pool', + effect: 'Establishes the candidate as `canonicalPoolByUniverse` for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op.', + declarations: [{ name: 'authorize' }], + preconditions: 'Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool.', + signals: '`AuthorizationUpdated` on first authorization; no event when the same candidate is already authorized', + }, + { + call: '`mintCompleteSets(universeId, account, amountAttoShares)`', + caller: 'An authorized `SecurityPool`', + effect: "Mints `amount` each of Invalid, Yes, and No to `account`, then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction.", + declarations: [{ name: 'mintCompleteSets' }], + preconditions: 'Caller is authorized; `account` is nonzero; `amount` is positive; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract account accepts `onERC1155BatchReceived`.', + signals: '`TransferBatch` on a successful callback', + }, + { + call: '`burnCompleteSets(universeId, account, amountAttoShares)`', + caller: 'An authorized `SecurityPool`', + effect: 'Burns `amount` each of Invalid, Yes, and No from `account`; global outcome supplies may differ.', + declarations: [{ name: 'burnCompleteSets' }], + preconditions: 'Caller is authorized; `account` is nonzero and has at least `amount` of every outcome.', + signals: '`TransferBatch`', + }, + { + call: '`burnTokenIdAndGetRemainingSupply(tokenId, account)`', + caller: 'An authorized `SecurityPool`', + effect: "Burns `account`'s full balance of `tokenId` and returns the burned amount and that token ID's remaining supply.", + declarations: [{ name: 'burnTokenIdAndGetRemainingSupply' }], + preconditions: '`account` is nonzero; caller is authorized.', + signals: '`TransferSingle`, including when the burned balance is zero', + }, + ], + }, + { + compiledAbiFingerprint: '0f7cbe10566e33d0de1300b8613ef64fff72b11845bff8c4f2aa470d1ee1eb16', + name: 'UniformPriceDualCapBatchAuction', + purpose: + 'Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. AVL, cumulative-allocation, and refund-prefix mechanics live in [UniformPriceDualCapBatchAuctionStorage](../../solidity/contracts/peripherals/UniformPriceDualCapBatchAuctionStorage.sol), an internal storage library.', + readAbiFingerprint: 'e4ad6ab91244711a2008716cfbdf62b6237d39321eefa984a4fdc7856267b8bc', + readSurface: + 'Auction summary getters are `maxAttoRepBeingSold`, `attoEthRaiseCap`, `finalized`, `clearingTick`, `ethFilledAtClearingAttoEth`, `attoEthRaised`, `totalAttoRepPurchased`, `auctionStarted`, `minBidSizeAttoEth`, `owner`, `underfunded`, `underfundedThreshold`, `underfundedWinningAttoEth`, and `activeTickCount`. `pendingEthRefundsAttoEth` reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use `computeClearing`, `previewFinalization`, `tickToPrice`, `getTickSummary`, `getTickCount`, `getTickPage`, `getActiveTickPage`, `getBidCountAtTick`, `getBidPageAtTick`, `getBidderBidCount`, and `getBidderBidPage` before finalizing or submitting settlement indexes.', + readDeclarations: [ + { name: 'computeClearing' }, + { name: 'previewFinalization' }, + { name: 'tickToPrice' }, + { name: 'getTickSummary' }, + { name: 'getTickCount' }, + { name: 'getTickPage' }, + { name: 'getActiveTickPage' }, + { name: 'getBidCountAtTick' }, + { name: 'getBidPageAtTick' }, + { name: 'getBidderBidCount' }, + { name: 'getBidderBidPage' }, + ], + readStorageDeclarations: [ + { name: 'maxAttoRepBeingSold' }, + { name: 'attoEthRaiseCap' }, + { name: 'finalized' }, + { name: 'clearingTick' }, + { name: 'ethFilledAtClearingAttoEth' }, + { name: 'attoEthRaised' }, + { name: 'totalAttoRepPurchased' }, + { name: 'auctionStarted' }, + { name: 'minBidSizeAttoEth' }, + { name: 'owner' }, + { name: 'underfunded' }, + { name: 'underfundedThreshold' }, + { name: 'underfundedWinningAttoEth' }, + { name: 'activeTickCount' }, + { name: 'pendingEthRefundsAttoEth' }, + ], + sourcePath: 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol', + interactions: [ + { + call: '`startAuction(attoEthRaiseCap, maxAttoRepBeingSold)`', + caller: 'Auction owner (`SecurityPoolForker`) only', + effect: 'Starts the one-week auction and fixes its two caps and minimum bid.', + declarations: [{ name: 'startAuction' }], + preconditions: 'Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in `uint128`; the block timestamp fits in `uint48`.', + signals: '`AuctionStarted`', + }, + { + call: '`submitBid(tick)` with ETH', + caller: 'Any bidder', + effect: "Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again.", + declarations: [{ name: 'submitBid' }], + preconditions: 'Auction active and unfinalized; before one-week deadline; bid meets `minBidSizeAttoEth`; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in `uint128`.', + signals: '`BidSubmitted`', + }, + { + call: '`refundLosingBids(tickIndices)`', + caller: 'Bidder for its own bids', + declarations: [{ name: 'refundLosingBids' }], + effect: + "A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in `pendingEthRefundsAttoEth` without restoring the bid. An empty list changes no bids and makes no external call.", + preconditions: 'Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded.', + signals: '`BidSettled` per refunded bid; `EthRefundDeferred` when a positive push fails', + }, + { + call: '`refundLosingBidsFor(bidder, tickIndices)`', + caller: 'Auction owner (`SecurityPoolForker`) only; public callers use `settleAuctionBids`', + declarations: [{ name: 'refundLosingBidsFor' }], + effect: + "A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in `pendingEthRefundsAttoEth` without restoring the bid. An empty list changes no bids and makes no external call.", + preconditions: 'Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded.', + signals: '`BidSettled` per refunded bid; `EthRefundDeferred` when a positive push fails', + }, + { + call: '`finalize()`', + caller: 'Auction owner (`SecurityPoolForker`) only; users reach it through `finalizeTruthAuction`', + effect: 'Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event.', + declarations: [{ name: 'finalize' }], + preconditions: 'Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value.', + signals: '`AuctionFinalized`', + }, + { + call: '`withdrawBids(withdrawFor, tickIndices, proRataTotal)`', + caller: 'Auction owner only', + effect: + 'For a nonempty list, returns refunds, purchased REP, and a companion pro-rata allocation for the selected beneficiary bids so the forker can credit REP backing units and capacity ownership. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP and capacity-ownership settlement. An empty list returns three zeros without changing bids, emitting events, or calling the beneficiary.', + declarations: [{ name: 'withdrawBids' }], + preconditions: 'Auction finalized; caller is owner. Nonempty indexes belong to `withdrawFor` and remain unsettled.', + signals: '`BidSettled` per processed bid; `EthRefundDeferred` when a positive push fails', + }, + { + call: '`withdrawPendingEthRefund()`', + caller: 'Bidder with deferred ETH', + effect: "Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event.", + declarations: [{ name: 'withdrawPendingEthRefund' }], + preconditions: 'Caller has a positive `pendingEthRefundsAttoEth` balance and currently accepts ETH.', + signals: '`PendingEthRefundWithdrawn`', + }, + ], + }, +] diff --git a/scripts/generate-contract-interaction-reference.mts b/scripts/generate-contract-interaction-reference.mts index a34f1d72a..df83c9fa7 100644 --- a/scripts/generate-contract-interaction-reference.mts +++ b/scripts/generate-contract-interaction-reference.mts @@ -4,1814 +4,28 @@ import { readdir, readFile, writeFile } from 'node:fs/promises' import { keccak256 } from '../shared/ts/ethereum' import { renderReferencePage } from './docs-html-page.mts' import { ensureContractArtifactsAreCurrent } from './ensure-contract-artifacts.mts' - -type Interaction = { - call: string - caller: string - declarations: ContractDeclaration[] - effect: string - preconditions: string - signals: string -} - -type ContractDeclaration = { - kind?: 'receive' - name: string - sourcePath?: string -} - -type ContractReference = { - compiledAbiFingerprint: string - interactions: Interaction[] - name: string - purpose: string - readAbiFingerprint: string - readDeclarations: ContractDeclaration[] - readStorageDeclarations?: ContractDeclaration[] - readSurface: string - securityBoundary?: string - securityBoundaryHeading?: string - sourcePath: string -} - -type AssemblyDelegateCall = { - abiSignature: string - argumentOffsets: Array<{ argument: string; offset: string }> - calldataLength: string - selector: string - sourcePath: string - targetEntrypointSignature: string - targetFunctionName: string - targetSourcePath: string -} - -const outputPath = 'docs/reference/contracts.html' -const expectedProductionSoliditySourceFingerprint = 'd7f143000d729b03a6a7a0d37923ae035c2e13f80f86c0fb6807419d63fbd1dc' - -const eventSourceByName: Record = { - VaultBadDebtMigrated: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - Approval: 'solidity/contracts/IERC20.sol', - ApprovalForAll: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - AuctionStarted: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - AwaitingForkContinuationSet: 'solidity/contracts/peripherals/SecurityPool.sol', - AuctionFinalized: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - AuthorizationUpdated: 'solidity/contracts/peripherals/interfaces/IShareToken.sol', - BidSettled: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - BidSubmitted: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - Burn: 'solidity/contracts/ReputationToken.sol', - CarryDepositConsumed: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - ChildDisputeStakedRepMaterialized: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - ChildPoolLinked: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - PoolHeldRepSweptToChild: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - ChildRepSplit: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - ClaimAuctionProceeds: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - ClaimDeposit: 'solidity/contracts/peripherals/EscalationGameState.sol', - ClaimForkedEscalationDepositsToWallet: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - CompleteSetCreated: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', - CompleteSetRedeemed: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', - CoordinatorStateCheckpoint: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - DeployChild: 'solidity/contracts/Zoltar.sol', - DeploySecurityPool: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', - DepositOnOutcome: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - RepDepositedToVault: 'solidity/contracts/peripherals/SecurityPool.sol', - DepositToEscalationGame: 'solidity/contracts/peripherals/SecurityPool.sol', - EscalationGameSet: 'solidity/contracts/peripherals/SecurityPool.sol', - EscalationMigrationEntitlementInitialized: 'solidity/contracts/peripherals/EscalationGameForker.sol', - EscalationMigrationEntitlementMaterialized: 'solidity/contracts/peripherals/EscalationGameForker.sol', - DisputeStakedRepDrainedAtFork: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - TruthAuctionHaircutApplied: 'solidity/contracts/peripherals/EscalationGameState.sol', - EthRefundDeferred: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - ExecutedStagedOperation: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - ForkContinuationResumed: 'solidity/contracts/peripherals/EscalationGameState.sol', - ForkCarryCheckpoint: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - ForkedEscrowExported: 'solidity/contracts/peripherals/EscalationGameState.sol', - ForkedEscrowRecorded: 'solidity/contracts/peripherals/EscalationGameState.sol', - GameContinuedFromFork: 'solidity/contracts/peripherals/EscalationGameState.sol', - GameStarted: 'solidity/contracts/peripherals/EscalationGameState.sol', - InheritedThresholdTie: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - LocalDepositAppended: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - LiquidationApprovalConsumed: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationApprovalNonceInvalidated: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationApprovalReleased: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationApprovalReserved: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationApprovalRevoked: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationApprovalSet: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - LiquidationRouteStaged: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - Migrate: 'solidity/contracts/peripherals/tokens/ShareToken.sol', - VaultMigrationCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - MigrationRepAdded: 'solidity/contracts/Zoltar.sol', - MigrationRepSplit: 'solidity/contracts/Zoltar.sol', - Mint: 'solidity/contracts/ReputationToken.sol', - NonDecisionReached: 'solidity/contracts/peripherals/interfaces/IEscalationGame.sol', - TotalRepBackingUnitsSet: 'solidity/contracts/peripherals/SecurityPool.sol', - VaultTargetHealthFactorSet: 'solidity/contracts/peripherals/SecurityPool.sol', - PendingEthRefundWithdrawn: 'solidity/contracts/peripherals/interfaces/IUniformPriceDualCapBatchAuction.sol', - PendingReportRecovered: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - ParentRepLocked: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - RepWithdrawnFromVault: 'solidity/contracts/peripherals/SecurityPool.sol', - PoolForkModeActivated: 'solidity/contracts/peripherals/SecurityPool.sol', - PriceReportRejected: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - PriceReported: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - PriceRequested: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - QuestionCreated: 'solidity/contracts/ZoltarQuestionData.sol', - RepRedeemedFromVault: 'solidity/contracts/peripherals/SecurityPool.sol', - RepBurned: 'solidity/contracts/Zoltar.sol', - RepEthPriceSet: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - ResidualRepSweptToSecurityPool: 'solidity/contracts/peripherals/EscalationGameState.sol', - ForkContinuationResidualRepBurned: 'solidity/contracts/peripherals/EscalationGameState.sol', - SecurityPoolSet: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - SecurityPoolForkSnapshot: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - SecurityPoolRegistered: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', - ShareTokenSupplySet: 'solidity/contracts/peripherals/SecurityPool.sol', - SharesRedeemed: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', - StagedOperationQueued: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - SystemStateSet: 'solidity/contracts/peripherals/SecurityPool.sol', - TruthAuctionFinalized: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - TruthAuctionStarted: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - TheoreticalSupplySet: 'solidity/contracts/ReputationToken.sol', - Transfer: 'solidity/contracts/IERC20.sol', - TransferBatch: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - TransferSingle: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - UniverseForked: 'solidity/contracts/Zoltar.sol', - PoolAccountingCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', - VaultAccountingCheckpoint: 'solidity/contracts/peripherals/interfaces/ISecurityPool.sol', - VaultBadDebtRecorded: 'solidity/contracts/peripherals/SecurityPool.sol', - VaultLiquidated: 'solidity/contracts/peripherals/SecurityPool.sol', - VaultEscrowUpdated: 'solidity/contracts/peripherals/EscalationGameState.sol', - VaultUnresolvedTotalsExported: 'solidity/contracts/peripherals/EscalationGameState.sol', -} - -const documentedEventSchemas: Array<{ name: string; parameters: string; sourcePath: string }> = [ - { - name: 'Transfer', - parameters: 'address indexed from,address indexed to,uint256 value', - sourcePath: 'solidity/contracts/IERC20.sol', - }, - { - name: 'Approval', - parameters: 'address indexed owner,address indexed spender,uint256 value', - sourcePath: 'solidity/contracts/IERC20.sol', - }, - { - name: 'TransferSingle', - parameters: 'address indexed operator,address indexed from,address indexed to,uint256 id,uint256 value', - sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - }, - { - name: 'TransferBatch', - parameters: 'address indexed operator,address indexed from,address indexed to,uint256[] ids,uint256[] values', - sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - }, - { - name: 'ApprovalForAll', - parameters: 'address indexed owner,address indexed operator,bool approved', - sourcePath: 'solidity/contracts/peripherals/interfaces/IERC1155.sol', - }, - { - name: 'QuestionCreated', - parameters: 'uint256 indexed questionId,uint256 createdTimestamp,QuestionData questionData,string[] outcomeOptions', - sourcePath: 'solidity/contracts/ZoltarQuestionData.sol', - }, - { - name: 'UniverseInitialized', - parameters: 'uint248 indexed universeId,uint256 forkTime,uint256 forkQuestionId,uint256 forkingOutcomeIndex,ReputationToken reputationToken,uint248 indexed parentUniverseId,uint256 universeTheoreticalSupplyAttoRep', - sourcePath: 'solidity/contracts/Zoltar.sol', - }, - { - name: 'DeployChild', - parameters: 'address deployer,uint248 indexed universeId,uint256 indexed outcomeIndex,uint248 indexed childUniverseId,ReputationToken childReputationToken,uint256 childUniverseTheoreticalSupplyAttoRep', - sourcePath: 'solidity/contracts/Zoltar.sol', - }, - { - name: 'SecurityPoolRegistered', - parameters: 'bytes32 indexed originId,bytes32 indexed poolId,uint248 indexed universeId,ISecurityPool securityPool', - sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', - }, - { - name: 'DeploySecurityPool', - parameters: - 'ISecurityPool indexed securityPool,UniformPriceDualCapBatchAuction truthAuction,OpenOraclePriceCoordinator priceOracleManagerAndOperatorQueuer,IShareToken shareToken,ISecurityPool indexed parent,uint248 indexed universeId,uint256 questionId,uint256 statoblastSecurityMultiplierBps,uint256 initialReportPriorityFeeAttoEthPerGas,uint256 currentRetentionRate,uint256 settlementCollateralAttoEth', - sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', - }, - { - name: 'ChildPoolLinked', - parameters: 'ISecurityPool indexed parent,uint256 indexed outcomeIndex,ISecurityPool indexed child,UniformPriceDualCapBatchAuction truthAuction', - sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - }, - { - name: 'ChildRepSplit', - parameters: 'ISecurityPool indexed parent,uint256 indexed outcomeIndex,uint256 childPoolRepSplitAttoRep,uint256 pendingChildAttoRep', - sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - }, - { - name: 'ChildDisputeStakedRepMaterialized', - parameters: 'ISecurityPool indexed parentPool,ISecurityPool indexed childPool,address indexed childGame,uint256 outcomeIndex,uint256 attoRepAmount,uint256 resultingDisputeStakedRepBalanceAttoRep', - sourcePath: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - }, - { - name: 'PoolHeldRepSweptToChild', - parameters: 'ISecurityPool indexed parentPool,ISecurityPool indexed childPool,uint256 indexed outcomeIndex,uint256 attoRepAmount,uint256 resultingChildPoolHeldRepBalanceAttoRep', - sourcePath: 'solidity/contracts/peripherals/interfaces/ISecurityPoolForker.sol', - }, - { - name: 'EscalationMigrationEntitlementInitialized', - parameters: 'ISecurityPool indexed parent,address indexed vault,uint256[3] sourcePrincipalByOutcomeAttoRep,uint256[3] currentRepByOutcomeAttoRep,uint256 totalCurrentAttoRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameForker.sol', - }, - { - name: 'EscalationMigrationEntitlementMaterialized', - parameters: 'ISecurityPool indexed parent,address indexed vault,uint256 indexed childOutcomeIndex,ISecurityPool child,uint256 childAttoRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameForker.sol', - }, - { name: 'TheoreticalSupplySet', parameters: 'uint256 totalTheoreticalSupplyAttoRep', sourcePath: 'solidity/contracts/ReputationToken.sol' }, - { name: 'Mint', parameters: 'address indexed account,uint256 valueAttoRep', sourcePath: 'solidity/contracts/ReputationToken.sol' }, - { - name: 'Burn', - parameters: 'address indexed account,uint256 valueAttoRep,uint256 totalTheoreticalSupplyAttoRep', - sourcePath: 'solidity/contracts/ReputationToken.sol', - }, - { - name: 'AwaitingForkContinuationSet', - parameters: 'bool awaitingForkContinuation', - sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', - }, - { - name: 'TotalRepBackingUnitsSet', - parameters: 'uint256 totalRepBackingUnits', - sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', - }, - { - name: 'ShareTokenSupplySet', - parameters: 'uint256 shareTokenSupplyAttoShares', - sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', - }, - { name: 'SystemStateSet', parameters: 'SystemState systemState', sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol' }, - { - name: 'VaultEscrowUpdated', - parameters: 'address indexed vault,uint256 disputeStakedRepByVaultAttoRep,uint256 totalDisputeStakedAttoRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', - }, - { - name: 'ForkedEscrowRecorded', - parameters: 'address indexed depositor,BinaryOutcomes.BinaryOutcome indexed outcome,uint256 sourcePrincipalTotalAttoRep,uint256 childRepTotalAttoRep,uint256 disputeStakedRepByVaultAttoRep,uint256 totalDisputeStakedAttoRep,uint256 outcomeBalanceAttoRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', - }, - { - name: 'VaultUnresolvedTotalsExported', - parameters: 'address indexed vault,address repReceiver,uint256[3] principalByOutcomeAttoRep,uint256 principalToTransferAttoRep,bool transferredRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', - }, - { - name: 'ForkedEscrowExported', - parameters: 'address indexed vault,address repReceiver,uint256[3] sourcePrincipalByOutcomeAttoRep,uint256[3] childRepByOutcomeAttoRep,uint256 totalChildRepToTransferAttoRep,bool transferredRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', - }, - { - name: 'ForkedEscrowClaimed', - parameters: 'address indexed depositor,BinaryOutcomes.BinaryOutcome indexed outcome,uint256 sourcePrincipalClaimedAttoRep,uint256 childRepClaimedAttoRep', - sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol', - }, - { - name: 'InternalApproval', - parameters: 'address indexed owner,address indexed spender,address indexed token,uint256 amount', - sourcePath: 'solidity/contracts/peripherals/openOracle/OpenOracle.sol', - }, - { - name: 'DeploymentAddressesSet', - parameters: 'address[] deploymentAddresses', - sourcePath: 'solidity/contracts/DeploymentStatusOracle.sol', - }, -] - -const delegateEventDeclarationMirrors: Array<{ name: string; sourcePath: string }> = [ - { name: 'PoolAccountingCheckpoint', sourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol' }, - { name: 'VaultAccountingCheckpoint', sourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol' }, - { name: 'ChildPoolLinked', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, - { name: 'ChildRepSplit', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, - { name: 'ClaimForkedEscalationDepositsToWallet', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerVaultMigrationBase.sol' }, -] - -const assemblyEventEmissions: Array<{ - dataArguments: string - indexedArguments: string - name: string - signature: string - signatureConstant: string - sourcePath: string -}> = [ - { - dataArguments: 'carryRoots, nullifierRoots, leafCounts, unresolvedTotalsAttoRep, resolutionBalancesAttoRep', - indexedArguments: 'sourceGame, snapshotId', - name: 'ForkCarryCheckpoint', - signature: 'ForkCarryCheckpoint(address,bytes32,bytes32[3],bytes32[3],uint256[3],uint256[3],uint256[3])', - signatureConstant: 'FORK_CARRY_CHECKPOINT_SIGNATURE', - sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol', - }, - { - dataArguments: 'BinaryOutcomes.BinaryOutcome(outcomeIndex), amountAttoRep, reason, carryTotalAttoRep, _getCurrentNullifierRoot(outcomeIndex), carryRoot', - indexedArguments: 'parentDepositIndex, sourceNodeId, depositor', - name: 'CarryDepositConsumed', - signature: 'CarryDepositConsumed(uint256,uint256,address,uint8,uint256,uint8,uint256,bytes32,bytes32)', - signatureConstant: 'CARRY_DEPOSIT_CONSUMED_SIGNATURE', - sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol', - }, -] - -const assemblyDelegateCalls: AssemblyDelegateCall[] = [ - { - abiSignature: 'emitForkSnapshotEvents(address,address,address,uint256,uint256,uint256)', - argumentOffsets: [ - { argument: 'parent', offset: '0x04' }, - { argument: 'migrationProxy', offset: '0x24' }, - { argument: 'sourceGame', offset: '0x44' }, - { argument: 'totalPoolHeldRepAtForkAttoRep', offset: '0x64' }, - { argument: 'disputeStakedRepAtForkAttoRep', offset: '0x84' }, - { argument: 'resultingLockedAttoRep', offset: '0xa4' }, - ], - calldataLength: '0xc4', - selector: '0x408d33da', - sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - targetEntrypointSignature: 'external(ISecurityPool,address,address,uint256,uint256,uint256)', - targetFunctionName: 'emitForkSnapshotEvents', - targetSourcePath: 'solidity/contracts/peripherals/SecurityPoolEventEmitter.sol', - }, -] - -const referencedEventAbiFingerprint = 'c209c24aef4071e13c7598ed7b8a6dc733f3946e8646307befa3c9a60387ff72' - -const entrypointSignaturesBySource: Record> = { - 'solidity/contracts/ERC20.sol': { - approve: ['public(address,uint256)'], - transfer: ['public(address,uint256)'], - transferFrom: ['public(address,address,uint256)'], - }, - 'solidity/contracts/ZoltarQuestionData.sol': { - createQuestion: ['external(QuestionData,string[])'], - }, - 'solidity/contracts/Zoltar.sol': { - addRepToMigrationBalance: ['public(uint248,uint256)'], - burnRep: ['external(uint248,uint256)'], - deployChild: ['public(uint248,uint256)'], - forkUniverse: ['public(uint248,uint256)'], - splitMigrationRep: ['public(uint248,uint256,uint256[])'], - }, - 'solidity/contracts/ReputationToken.sol': { - burn: ['external(address,uint256)'], - mint: ['external(address,uint256)'], - setMaxTheoreticalSupplyAttoRep: ['external(uint256)'], - }, - 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol': { - deployChildSecurityPool: ['external(ISecurityPool,IShareToken,uint248,uint256,uint256,uint256,uint256)'], - deployOriginSecurityPool: ['external(uint248,uint256,uint256,uint256)'], - }, - 'solidity/contracts/peripherals/EscalationGame.sol': { - applyTruthAuctionHaircut: ['external(uint256)'], - recordDepositFromSecurityPool: ['external(address,BinaryOutcomes.BinaryOutcome,uint256,uint256)'], - resumeFromFork: ['external()'], - start: ['external(uint256,uint256)'], - startFromFork: ['external(uint256,uint256,uint256,BinaryOutcomes.BinaryOutcome,bool,uint256)'], - }, - 'solidity/contracts/peripherals/EscalationGameCarry.sol': { - initializeForkCarrySnapshotWithResolutionBalances: ['external(address,bytes32,bytes32[MERKLE_MOUNTAIN_RANGE_MAX_PEAKS][3],uint256[3],uint256[3],uint256[3],bytes32[3])'], - }, - 'solidity/contracts/peripherals/EscalationGameEscrow.sol': { - exportForkedEscrowByOutcome: ['external(address,address)'], - exportForkedEscrowByOutcomeWithoutTransfer: ['external(address)'], - exportVaultUnresolvedTotals: ['external(address,address)'], - exportVaultUnresolvedTotalsWithoutTransfer: ['external(address)'], - recordForkedEscrowForOutcome: ['external(address,BinaryOutcomes.BinaryOutcome,uint256,uint256)'], - }, - 'solidity/contracts/peripherals/EscalationGameSettlement.sol': { - claimDepositForWinning: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], - claimDepositForWinningWithoutTransfer: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], - drainAllRep: ['external(address)'], - exportUnresolvedDeposit: ['public(uint256,BinaryOutcomes.BinaryOutcome)'], - sweepResidualRepToSecurityPool: ['external()'], - withdrawDeposit: ['public(CarriedDepositProof,BinaryOutcomes.BinaryOutcome)', 'public(uint256,BinaryOutcomes.BinaryOutcome)'], - }, - 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': { - executeStagedOperation: ['public(uint256)'], - expireStagedOperation: ['external(uint256)'], - openOracleCallback: ['external(uint256,uint256,uint256,uint256,address,address)'], - recoverSettledPendingReport: ['public()'], - requestPrice: ['public(uint256,uint256)'], - requestPriceIfNeededAndStageLiquidation: ['external(address,address,uint256,bytes32,uint256,uint256,uint256)'], - requestPriceIfNeededAndStageOperation: ['public(OperationType,address,uint256,uint256,uint256,uint256)'], - setLiquidationApprovalRegistry: ['external(LiquidationApprovalRegistry)'], - setRepEthPrice: ['public(uint256)'], - setSecurityPool: ['public(ISecurityPool)'], - }, - 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': { - consume: ['external(uint256,uint256)'], - initialize: ['external(address)'], - invalidateLiquidationApprovalNonce: ['external(uint256)'], - permitLiquidationApproval: ['external(LiquidationApprovalParams,bytes)'], - release: ['external(uint256)'], - reserve: ['external(uint256,bytes32,address,address,address,uint256,uint256,uint256)'], - revokeLiquidationApproval: ['external(bytes32)'], - setLiquidationApproval: ['external(LiquidationApprovalParams)'], - }, - 'solidity/contracts/peripherals/SecurityPool.sol': { - activateForkMode: ['external()'], - addFeeEligibleCapacityOwnershipAttoRep: ['external(address,uint256)'], - authorizeChildPool: ['external(ISecurityPool)'], - burnEscalationWinnerHaircut: ['external(uint256)'], - configureVault: ['external(address,uint256,uint256,uint256,uint256,uint256,uint256)'], - createCompleteSet: ['external()'], - depositRepToVault: ['external(uint256,uint256)'], - depositToEscalationGame: ['external(BinaryOutcomes.BinaryOutcome,uint256)'], - initializeForkCarrySnapshotWithResolutionBalances: ['external(address,bytes32,bytes32[64][3],uint256[3],uint256[3],uint256[3],bytes32[3])'], - initializeForkedEscalationGame: ['external(uint256,uint256,uint256,BinaryOutcomes.BinaryOutcome)'], - performLiquidation: ['external(LiquidationRequest)'], - withdrawRepFromVault: ['external(address,uint256)'], - receive: ['external payable()'], - redeemCompleteSet: ['external(uint256)'], - redeemFees: ['external(address)'], - redeemRepFromVault: ['external(address)'], - redeemShares: ['external()'], - resumeForkedEscalationGame: ['external()'], - setAwaitingForkContinuation: ['external(bool)'], - setTotalRepBackingUnits: ['external(uint256)'], - setPoolFinancials: ['external(uint256,uint256,uint256,uint256)'], - setStartingParams: ['external(uint256,uint256)'], - setSystemState: ['external(SystemState)'], - setTotalSharesAttoShares: ['external(uint256)'], - transferEth: ['external(address payable,uint256)'], - updateSettlementCollateral: ['public()'], - updateRetentionRate: ['public()'], - updateVaultFees: ['public(address)'], - withdrawForkedEscalationDeposits: ['external(QuestionOutcome,CarriedDepositProof[])'], - withdrawFromEscalationGame: ['external(BinaryOutcomes.BinaryOutcome,uint256[])'], - }, - 'solidity/contracts/peripherals/SecurityPoolForker.sol': { - claimAuctionProceeds: ['external(ISecurityPool,address,IUniformPriceDualCapBatchAuction.TickIndex[])'], - claimForkedEscalationDeposits: ['external(ISecurityPool,address,BinaryOutcomes.BinaryOutcome,uint256[])'], - createChildUniverse: ['external(ISecurityPool,uint256)'], - finalizeTruthAuction: ['external(ISecurityPool)'], - forkZoltarWithOwnEscalationGame: ['external(ISecurityPool)'], - initiateSecurityPoolFork: ['external(ISecurityPool)'], - initializeChildForkedEscalationGameIfNeeded: ['external(ISecurityPool,ISecurityPool,EscalationGame)'], - migrateRepToZoltar: ['external(ISecurityPool,uint256[])'], - migrateVault: ['public(ISecurityPool,uint256)'], - migrateVaultWithUnresolvedEscalation: ['external(ISecurityPool,address,uint256)'], - receive: ['external payable()'], - settleAuctionBids: ['external(ISecurityPool,address,IUniformPriceDualCapBatchAuction.TickIndex[],IUniformPriceDualCapBatchAuction.TickIndex[])'], - startTruthAuction: ['external(ISecurityPool)'], - }, - 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol': { - finalize: ['external()'], - refundLosingBids: ['external(IUniformPriceDualCapBatchAuction.TickIndex[])'], - refundLosingBidsFor: ['external(address,IUniformPriceDualCapBatchAuction.TickIndex[])'], - startAuction: ['public(uint256,uint256)'], - submitBid: ['external(int256)'], - withdrawBids: ['external(address,IUniformPriceDualCapBatchAuction.TickIndex[],uint256)'], - withdrawPendingEthRefund: ['external()'], - }, - 'solidity/contracts/peripherals/tokens/ShareToken.sol': { - authorize: ['external(ISecurityPool)'], - burnCompleteSets: ['external(uint248,address,uint256)'], - burnTokenIdAndGetRemainingSupply: ['external(uint256,address)'], - migrate: ['external(uint256,uint256[])'], - mintCompleteSets: ['external(uint248,address,uint256)'], - }, - 'solidity/contracts/peripherals/tokens/ERC1155.sol': { - safeBatchTransferFrom: ['external(address,address,uint256[],uint256[])', 'external(address,address,uint256[],uint256[],bytes)'], - safeTransferFrom: ['external(address,address,uint256,uint256)', 'external(address,address,uint256,uint256,bytes)'], - setApprovalForAll: ['external(address,bool)'], - }, -} - -const stateChangingAbiFingerprintBySource: Record = { - 'solidity/contracts/Context.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/ERC20.sol': '6c4161bf27a2ed1bc2de94b58253a8ec4201e28d125571cb2124238753387a22', - 'solidity/contracts/ReputationToken.sol': 'b3e68791ded4f7fd9cc70785bdd3c55d5ec7fde5ad64b7fbe8aee03d5d273e3b', - 'solidity/contracts/Zoltar.sol': '6479e6b24905f8f3299e486703df934aa7811152a9d20517596da64cbcd4b471', - 'solidity/contracts/ZoltarQuestionData.sol': '904b4369195f070fa3b04bbcbc1acba529810ffa2da4667569cd9168ac568d65', - 'solidity/contracts/peripherals/EscalationGame.sol': '41394612ae4488f08c9f4c18ff912cec089fc40a3f5d501a27cb8b2fabc4db57', - 'solidity/contracts/peripherals/EscalationGameCalculations.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/EscalationGameCarry.sol': 'bdd7cfe47523c5e0c8985eec993214de44caf88fc4f2e6f1586d2d03c0a02ef0', - 'solidity/contracts/peripherals/EscalationGameEscrow.sol': 'c75cd0c9ea134a3bfa03227d0500485049818553447b4b258ff220cb0d201dde', - 'solidity/contracts/peripherals/EscalationGameSettlement.sol': '73f9aad63165cacbff5bd02fd57a6b5a3f73737545018ecdf152c46f905c8c32', - 'solidity/contracts/peripherals/EscalationGameState.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/EscalationGameStorage.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': '2a27b7ed5407ac8067de39d67bbe84902f4c1c36ab070eeaeb99375db6f8b8e1', - 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': '986a20fc0e4cfe0898be8fc91c6b911b93ef0ae1086d4cb1142a93c66f315684', - 'solidity/contracts/peripherals/SecurityPool.sol': '7de24a5d15ed2b8ffc052c498eefb96f92997d59ee8b8d075b42efad4a17012d', - 'solidity/contracts/peripherals/SecurityPoolForker.sol': '282c464a68623405a6241816a1c5fcef4b80e9db39e42e89d77177d8a4f10eae', - 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/SecurityPoolForkerStorage.sol': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol': 'af052a0723644556a488b365d578205eb331e53a1e47fff8b869ff77fab9c7ef', - 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol': '618aed7f3f8bdfd50267b9d7533db3f489f45715f1cd448f5107f67631814d34', - 'solidity/contracts/peripherals/tokens/ERC1155.sol': '7bb87695bc3df8fa177c545209ed58d2e4571c19c869b5598bb0a829e764b218', - 'solidity/contracts/peripherals/tokens/ShareToken.sol': '2a3339ca5db0ccabc2bc10318ff3baf52273b90837f01683d3e5147a13fd2d0d', -} - -const readDeclarationExclusionsBySource: Record = { - 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol': ['securityPool'], - 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol': ['storedGame', 'disputeHistory'], - 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol': ['securityPool'], - 'solidity/contracts/peripherals/SecurityPool.sol': ['eventEmitter', 'factory'], - 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol': [], -} +import { + assemblyDelegateCalls, + type AssemblyDelegateCall, + assemblyEventEmissions, + contractReferences, + delegateEventDeclarationMirrors, + documentedEventSchemas, + entrypointSignaturesBySource, + eventSourceByName, + expectedProductionSoliditySourceFingerprint, + outputPath, + readDeclarationExclusionsBySource, + referencedEventAbiFingerprint, + stateChangingAbiFingerprintBySource, + type ContractDeclaration, +} from './contract-reference-metadata.mts' assertDeclarationCheckerRegression() await ensureContractArtifactsAreCurrent() const productionSoliditySourceFingerprint = await getProductionSoliditySourceFingerprint() assert.equal(productionSoliditySourceFingerprint, expectedProductionSoliditySourceFingerprint, 'Production Solidity source changed; re-audit every affected contract behavior against the documentation, then update the pinned source fingerprint') -const contractReferences: ContractReference[] = [ - { - compiledAbiFingerprint: '580109cfcebb3ce505def01895f7b6567e75bbd8e8ccac857bdd00d54f15c37f', - name: 'ZoltarQuestionData', - purpose: 'Creates immutable, content-addressed scalar or categorical questions and exposes their display metadata.', - readAbiFingerprint: '964d0ce318d2890011ff485c8d78e933cabc8d10e489a0c22f0e266fa2563ded', - readSurface: - 'Use `getQuestionId` before submission; `questionCreatedTimestamp` and `questions` for direct lookup; `getQuestionCount` and `getQuestions` for indexed or paged discovery; and `getQuestionEndDate`, `getOutcomeLabels`, `splitUint256IntoTwoWithInvalid`, `hasNonZeroScalarReservedBits`, `isMalformedAnswerOption`, and `getAnswerOptionName` when validating or displaying answers. In the `QuestionData` tuple, `startTime` and `endTime` are `uint48`, while `numTicks` is `uint120`; clients must use these exact widths because they determine the `getQuestionId` and `createQuestion` selectors.', - readDeclarations: [ - { name: 'getQuestionId' }, - { name: 'getQuestionCount' }, - { name: 'getQuestions' }, - { name: 'getQuestionEndDate' }, - { name: 'getOutcomeLabels' }, - { name: 'splitUint256IntoTwoWithInvalid' }, - { name: 'hasNonZeroScalarReservedBits' }, - { name: 'isMalformedAnswerOption' }, - { name: 'getAnswerOptionName' }, - ], - readStorageDeclarations: [{ name: 'questionCreatedTimestamp' }, { name: 'questions' }], - sourcePath: 'solidity/contracts/ZoltarQuestionData.sol', - interactions: [ - { - call: '`createQuestion(questionData, outcomeOptions)`', - caller: 'Anyone', - effect: 'Stores the question at its deterministic content hash, records the creation timestamp, appends it to discovery order, and stores categorical labels when supplied.', - declarations: [{ name: 'createQuestion' }], - preconditions: 'Question ID not already created; end time is on or after start time. Scalar questions use no labels, require display maximum greater than minimum, and positive ticks. Categorical questions require nonempty labels whose `keccak256(abi.encode(label))` values are strictly descending.', - signals: '`QuestionCreated`', - }, - ], - }, - { - compiledAbiFingerprint: '023e5a38bcf613044e07d23e84095e1125be871017388a0c5a6cf7a41958b350', - name: 'Zoltar', - purpose: 'Registers universe forks, charges the fork admission haircut, and mints branch-specific child REP.', - readAbiFingerprint: '1916e3480c70c4ccd5962f4b8069988d7dc36f0ea89337ebe04b8bca089d1492', - readSurface: - 'Use `universes`, `forkThresholdDivisor`, `forkBurnDivisor`, `zoltarQuestionData`, `genesisReputationToken`, `getForkTime`, `forkQuestionMatches`, `getRepToken`, `getForkThresholdAttoRep`, `getNonDecisionThresholdAttoRep`, `getUniverseTheoreticalSupplyAttoRep`, `getChildUniverseId`, `getDeployedChildUniverses`, and `getMigrationRepBalanceAttoRep` to reconstruct universe and migration state. Construction requires a deployed genesis REP token with theoretical supply from one attoREP through 11 million REP and `forkBurnDivisor >= 5`, which caps the uncredited fork haircut at 20% of the threshold.', - securityBoundary: 'Security boundaries for these calls are [A15 intended question selection](./security-model.html#assumption-a15) and [A25 safe immutable parameters](./security-model.html#assumption-a25).', - readDeclarations: [ - { name: 'getForkTime' }, - { name: 'forkQuestionMatches' }, - { name: 'getRepToken' }, - { name: 'getForkThresholdAttoRep' }, - { name: 'getNonDecisionThresholdAttoRep' }, - { name: 'getUniverseTheoreticalSupplyAttoRep' }, - { name: 'getChildUniverseId' }, - { name: 'getDeployedChildUniverses' }, - { name: 'getMigrationRepBalanceAttoRep' }, - ], - readStorageDeclarations: [{ name: 'universes' }, { name: 'forkThresholdDivisor' }, { name: 'forkBurnDivisor' }, { name: 'zoltarQuestionData' }, { name: 'genesisReputationToken' }], - sourcePath: 'solidity/contracts/Zoltar.sol', - interactions: [ - { - call: '`forkUniverse(universeId, questionId)`', - caller: 'Any address able to fund the current fork threshold', - effect: 'Records the fork, removes threshold REP from the parent universe, and credits the caller with the threshold minus the configured uncredited haircut.', - declarations: [{ name: 'forkUniverse' }], - preconditions: 'Initialized and unforked universe; existing ended question; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance.', - signals: '`UniverseForked`', - }, - { - call: '`burnRep(universeId, amountAttoRep)`', - caller: 'Any REP holder; the caller can burn only its own balance', - effect: 'Permanently removes REP without creating migration credit; escalation settlement uses this when the haircut was not paid through its own fork.', - declarations: [{ name: 'burnRep' }], - preconditions: 'Initialized universe; positive amount; sufficient caller REP and theoretical supply. Genesis REP requires allowance.', - signals: '`RepBurned` and the token burn or transfer event', - }, - { - call: '`deployChild(universeId, outcomeIndex)`', - caller: 'Anyone', - effect: 'Deploys the deterministic child REP token and initializes the child universe.', - declarations: [{ name: 'deployChild' }], - preconditions: 'Parent forked; outcome is well formed; child is not already deployed.', - signals: '`DeployChild`', - }, - { - call: '`addRepToMigrationBalance(universeId, amountAttoRep)`', - caller: 'Parent REP holder', - effect: "Burns or sinks additional parent REP and increases the caller's reusable migration balance.", - declarations: [{ name: 'addRepToMigrationBalance' }], - preconditions: 'Universe forked; sufficient caller REP. Genesis REP requires allowance; child REP is burned directly without allowance.', - signals: '`MigrationRepAdded`', - }, - { - call: '`splitMigrationRep(universeId, amountAttoRep, outcomeIndexes)`', - caller: 'Migration-balance holder', - effect: - 'Mints `amount` of child REP into every selected branch, deploying missing children lazily. An empty outcome list returns after the universe-fork guard without outcome validation, deployment, minting, or events. A nonempty zero-amount call still validates every outcome, may deploy missing children, performs zero-value child REP mints, and records a zero split for every branch.', - declarations: [{ name: 'splitMigrationRep' }], - preconditions: "Universe forked. A nonempty list additionally requires every outcome to be well formed and the cumulative amount per child not to exceed the caller's migration balance.", - signals: '`TheoreticalSupplySet` and `DeployChild` when needed; child REP `Transfer` and `Mint`, then `MigrationRepSplit`, per selected branch, including at zero amount; no event for an empty list', - }, - ], - }, - { - compiledAbiFingerprint: '14cee3c68c22f454d0d83f16aad27d40b686fa8abc7fb0220c03ba19ba609f64', - name: 'ReputationToken', - purpose: 'Implements universe-specific ERC-20 REP and enforces the supply ceiling maintained by Zoltar.', - readAbiFingerprint: '1385406a6e5989eb754a8adeb36f946309659127e088734528cdffa7f8bbe7c8', - readSurface: 'Use `getTotalTheoreticalSupplyAttoRep`, `zoltar`, and the standard ERC-20 `name`, `symbol`, `decimals`, `totalSupply`, `balanceOf`, and `allowance` reads.', - readDeclarations: [ - { name: 'getTotalTheoreticalSupplyAttoRep' }, - { name: 'name', sourcePath: 'solidity/contracts/ERC20.sol' }, - { name: 'symbol', sourcePath: 'solidity/contracts/ERC20.sol' }, - { name: 'decimals', sourcePath: 'solidity/contracts/ERC20.sol' }, - { name: 'totalSupply', sourcePath: 'solidity/contracts/ERC20.sol' }, - { name: 'balanceOf', sourcePath: 'solidity/contracts/ERC20.sol' }, - { name: 'allowance', sourcePath: 'solidity/contracts/ERC20.sol' }, - ], - readStorageDeclarations: [{ name: 'zoltar' }], - sourcePath: 'solidity/contracts/ReputationToken.sol', - interactions: [ - { - call: '`setMaxTheoreticalSupplyAttoRep(totalTheoreticalSupplyAttoRep)`', - caller: '`Zoltar` only', - effect: 'Sets the child token theoretical-supply ceiling used to bound subsequent migration mints.', - declarations: [{ name: 'setMaxTheoreticalSupplyAttoRep' }], - preconditions: 'Called by Zoltar as part of child-universe creation; theoretical supply does not exceed 11 million REP.', - signals: '`TheoreticalSupplySet`', - }, - { - call: '`mint(account, valueAttoRep)`', - caller: '`Zoltar` only', - effect: 'Mints branch REP to an account.', - declarations: [{ name: 'mint' }], - preconditions: '`account` is nonzero; resulting ERC-20 supply does not exceed theoretical supply.', - signals: '`Mint` and ERC-20 `Transfer`', - }, - { - call: '`burn(account, valueAttoRep)`', - caller: '`Zoltar` only', - effect: 'Burns account REP and reduces both actual and theoretical supply by the same amount.', - declarations: [{ name: 'burn' }], - preconditions: '`account` is nonzero and has sufficient REP; theoretical supply covers the burn.', - signals: '`Burn` and ERC-20 `Transfer`', - }, - { - call: '`transfer(to, value)`', - caller: 'REP holder', - effect: 'Moves REP from the caller without changing actual or theoretical supply.', - declarations: [{ name: 'transfer', sourcePath: 'solidity/contracts/ERC20.sol' }], - preconditions: 'Destination is nonzero; caller has sufficient balance.', - signals: '`Transfer`', - }, - { - call: '`approve(spender, value)`', - caller: 'Any REP account setting its own allowance', - effect: 'Replaces the named spender allowance without moving REP.', - declarations: [{ name: 'approve', sourcePath: 'solidity/contracts/ERC20.sol' }], - preconditions: 'Spender is nonzero.', - signals: '`Approval`', - }, - { - call: '`transferFrom(from, to, value)`', - caller: 'A spender with sufficient allowance from `from`', - effect: 'Moves REP from `from`; a finite allowance decreases by `value`, while an infinite allowance remains unchanged. Neither allowance path emits `Approval`.', - declarations: [{ name: 'transferFrom', sourcePath: 'solidity/contracts/ERC20.sol' }], - preconditions: 'Source and destination are nonzero; source has sufficient balance; caller has sufficient allowance, including when caller equals source.', - signals: '`Transfer` only', - }, - ], - }, - { - compiledAbiFingerprint: 'c502f414482a9872fb01eaa78dccd3c19d5e1af5227c10047ba645da00fb3406', - name: 'SecurityPoolFactory', - purpose: 'Creates and canonically registers origin and child security pools with their share token, oracle coordinator, and optional truth auction.', - readAbiFingerprint: '855853487a8ab201b9e990820bac4f51ec6ae6520ae2dcf49efcc93e81c9a474', - readSurface: - 'Use `initialEscalationGameDepositAttoRep`, `minimumSecurityBondDebtAttoEth`, and `minimumVaultRepDepositAttoRep` for immutable deployment floors. The factory requires the escalation baseline to equal 1 REP, so each pool fixes its effective escalation deposit at construction as exactly `max(1 REP, theoretical REP supply / 10,000,000)`. A zero configured vault REP floor selects the default `theoretical REP supply / 100,000`; a nonzero constructor value is the exact override. The security-bond debt floor defaults to 1 ETH. Use `securityPoolDeploymentCount` with the strict `securityPoolDeploymentsRange(startIndex, count)` pager, which reverts rather than truncating when the requested range exceeds the array. Use `getOriginId`, `getPoolId`, `getSecurityPool`, `getSecurityPoolOriginId`, and `getSecurityPoolHasInheritedForkOutcome` for canonical lookup.', - readDeclarations: [{ name: 'securityPoolDeploymentCount' }, { name: 'securityPoolDeploymentsRange' }, { name: 'getOriginId' }, { name: 'getPoolId' }, { name: 'getSecurityPool' }, { name: 'getSecurityPoolOriginId' }, { name: 'getSecurityPoolHasInheritedForkOutcome' }], - readStorageDeclarations: [{ name: 'initialEscalationGameDepositAttoRep' }, { name: 'minimumSecurityBondDebtAttoEth' }, { name: 'minimumVaultRepDepositAttoRep' }], - sourcePath: 'solidity/contracts/peripherals/factories/SecurityPoolFactory.sol', - interactions: [ - { - call: '`deployOriginSecurityPool(universeId, questionId, statoblastSecurityMultiplierBps, initialReportPriorityFeeAttoEthPerGas)`', - caller: 'Anyone', - effect: 'Creates the canonical origin pool, its lineage-wide share token, and its price coordinator with the configured initial-report priority fee, then wires and registers them atomically.', - declarations: [{ name: 'deployOriginSecurityPool' }], - preconditions: - '`statoblastSecurityMultiplierBps > 10_001`, which makes the halfway migration component strictly greater than one; the effective pool-held vault REP backing multiplier separately floors that component at the 10,500-BPS liquidation-award reserve described by the [liquidation design](../explanation/liquidations.html#rule). `initialReportPriorityFeeAttoEthPerGas > 0` and remains within the coordinator-computed OpenOracle `uint128` report/escalation-halt capacity bound; question exists and has exactly the categorical labels `Yes`, then `No`; universe is unforked and has a REP token; the non-decision threshold exceeds the construction-time effective escalation deposit `max(1 REP, theoretical REP supply / 10,000,000)`; the origin/universe/priority-fee slot has not already been claimed.', - signals: '`SecurityPoolRegistered`, then `DeploySecurityPool`', - }, - { - call: '`deployChildSecurityPool(parent, shareToken, universeId, questionId, statoblastSecurityMultiplierBps, currentRetentionRate, settlementCollateralAttoEth)`', - caller: '`SecurityPoolForker` only', - effect: 'Creates and registers a canonical child pool with a coordinator that inherits `initialReportPriorityFeeAttoEthPerGas` from the parent coordinator and a forker-owned truth auction, while retaining the parent lineage share token.', - declarations: [{ name: 'deployChildSecurityPool' }], - preconditions: 'Parent is the canonical pool for its lineage; supplied share token equals the parent share token; target origin/universe slot is unclaimed; deployment arguments satisfy downstream constructors and wiring.', - signals: '`SecurityPoolRegistered`, then `DeploySecurityPool`', - }, - ], - }, - { - compiledAbiFingerprint: '4e95ce668a620668593258b22e33ff0fdaa591d23455a7ac0dffafd4d003354f', - name: 'SecurityPool', - purpose: 'Holds ETH collateral and REP underwriting, accounts for vaults and fees, mints shares, and routes local escalation.', - readAbiFingerprint: '25c286eef854f7f8c3a60fb339e29063f864976fb0c650594e6b5478a5af13f8', - readSurface: - 'Immutable relationship and configuration getters are `questionId`, `universeId`, `initialEscalationGameDepositAttoRep`, `zoltar`, `parent`, `shareToken`, `repToken`, `priceOracleManagerAndOperatorQueuer`, `openOracle`, `escalationGameFactory`, `questionData`, `securityPoolForker`, `truthAuction`, `securityPoolFactory`, and `statoblastSecurityMultiplierBps`; the current game is `escalationGame`. Accounting getters include `totalCapacityOwnershipAttoRep`, `settlementCollateralAttoEth`, `totalRepBackingUnits`, `shareTokenSupplyAttoShares`, `securityVaults`, `minimumSecurityBondDebtAttoEth`, `minimumVaultRepDepositAttoRep`, `vaultTargetHealthFactorBps`, `totalBadDebtAttoEth`, and `vaultBadDebtAttoEth`. Use `getCurrentMintingCapacityAttoEth` for price-converted aggregate capacity and `getVaultOpenInterestAttoEth` for a vault’s live proportional obligation. Other derived and paged reads are `getVaultCount`, `getVaults`, `attoSharesToAttoEth`, `attoEthToAttoShares`, `attoRepToBackingUnits`, `backingUnitsToAttoRep`, `getTotalPoolHeldAttoRep`, `totalAccruedFeesAttoEth`, `getPoolAccountingSnapshot`, `getVaultFeeRemainder`, and `isEscalationResolved`. The vault registry is append-only and newest-registered first. Registration requires only a nonzero address and can occur without economic state; consumers filter current positions from `securityVaults`, escalation stake, and bad debt. `isEscalationResolved()` is true only when a local escalation game is configured and the forker routes a non-`None` outcome; an operational fixed-outcome child without a local game returns false. Lifecycle and fee getters are `totalClaimableVaultFeesAttoEth`, `lastUpdatedFeeAccumulator`, `feeIndex`, `currentRetentionRate`, `awaitingForkContinuation`, and `systemState`.', - securityBoundary: - 'Price-sensitive withdrawal, dynamic-capacity, and liquidation calls depend on [A16 timely inclusion](./security-model.html#assumption-a16), [A21 genesis REP and WETH behavior](./security-model.html#assumption-a21), [A19 observable correctable price](./security-model.html#assumption-a19), and [A06 lifecycle executors](./security-model.html#assumption-a06). User-initiated pool calls additionally depend on [A28 account authority](./security-model.html#assumption-a28).', - readDeclarations: [ - { name: 'getVaultCount' }, - { name: 'getVaults' }, - { name: 'attoSharesToAttoEth' }, - { name: 'attoEthToAttoShares' }, - { name: 'attoRepToBackingUnits' }, - { name: 'backingUnitsToAttoRep' }, - { name: 'getTotalPoolHeldAttoRep' }, - { name: 'totalAccruedFeesAttoEth' }, - { name: 'getPoolAccountingSnapshot' }, - { name: 'getVaultFeeRemainder' }, - { name: 'getCurrentMintingCapacityAttoEth' }, - { name: 'getVaultOpenInterestAttoEth' }, - { name: 'isEscalationResolved' }, - ], - readStorageDeclarations: [ - { name: 'questionId' }, - { name: 'universeId' }, - { name: 'initialEscalationGameDepositAttoRep' }, - { name: 'zoltar' }, - { name: 'parent' }, - { name: 'shareToken' }, - { name: 'repToken' }, - { name: 'priceOracleManagerAndOperatorQueuer' }, - { name: 'openOracle' }, - { name: 'escalationGameFactory' }, - { name: 'escalationGame', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'questionData' }, - { name: 'securityPoolForker' }, - { name: 'truthAuction' }, - { name: 'securityPoolFactory' }, - { name: 'totalCapacityOwnershipAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'settlementCollateralAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'totalRepBackingUnits', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'statoblastSecurityMultiplierBps', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'shareTokenSupplyAttoShares', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'totalClaimableVaultFeesAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'lastUpdatedFeeAccumulator', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'feeIndex', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'currentRetentionRate', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'awaitingForkContinuation', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'securityVaults', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'totalBadDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'vaultBadDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'systemState', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'minimumSecurityBondDebtAttoEth', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'minimumVaultRepDepositAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - { name: 'vaultTargetHealthFactorBps', sourcePath: 'solidity/contracts/peripherals/SecurityPoolStorage.sol' }, - ], - sourcePath: 'solidity/contracts/peripherals/SecurityPool.sol', - interactions: [ - { - call: '`burnEscalationWinnerHaircut(amountAttoRep)`', - caller: "This pool's `EscalationGame` only", - effect: 'Burns the winning-deposit haircut from REP already escrowed in the game.', - declarations: [{ name: 'burnEscalationWinnerHaircut' }], - preconditions: 'Caller is the configured escalation game; amount is positive and the game has already transferred enough REP to the pool.', - signals: '`RepBurned` and ERC-20 `Transfer`; child REP also emits `Burn`', - }, - { - call: '`depositRepToVault(attoRepAmount, targetHealthFactorBps)`', - caller: 'Vault owner', - effect: 'Transfers REP into the pool, credits proportional REP backing units, and creates REP-denominated fee-earning capacity ownership from the deposit and selected target health factor.', - declarations: [{ name: 'depositRepToVault' }], - preconditions: 'Operational and unforked; `isEscalationResolved()` is false; target health factor is at least 10,000; resulting vault REP meets the configured supply-scaled minimum.', - signals: '`RepDepositedToVault`, the vault target-health-factor event, and accounting checkpoints', - }, - { - call: '`redeemFees(vault)`', - caller: 'Anyone; any nonzero ETH payment is always sent to `vault`', - effect: "First accrues the vault's fees. If resulting claimable fees are zero, returns without payment; otherwise clears and pays the full amount.", - declarations: [{ name: 'redeemFees' }], - preconditions: 'A nonzero payment path requires `vault` to accept ETH.', - signals: 'Accrual checkpoints only when accrual state changes; both `VaultAccountingCheckpoint` and `PoolAccountingCheckpoint` for a nonzero redemption; no event when fees and accrual state are unchanged', - }, - { - call: '`createCompleteSet()` with ETH', - caller: 'Trader', - effect: 'Adds collateral and mints one `Invalid`, `Yes`, and `No` share per complete-set unit, then invokes the ERC-1155 batch-receiver callback for a contract trader. Callback rejection rolls back the ETH, pool accounting, events, and share mint.', - declarations: [{ name: 'createCompleteSet' }], - preconditions: - 'Operational and unforked; `isEscalationResolved()` is false; not awaiting continuation; positive ETH converts to at least one complete-set unit; live oracle-priced minting capacity covers the resulting settlement collateral, not merely this deposit; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract trader accepts `onERC1155BatchReceived`.', - signals: '`CompleteSetCreated`, `PoolAccountingCheckpoint`, then ERC-1155 `TransferBatch` on a successful callback', - }, - { - call: '`redeemCompleteSet(amountAttoShares)`', - caller: 'Anyone; positive redemption requires the caller to hold the complete set', - effect: - "Burns equal balances of all three outcomes and pays `amountAttoShares * settlementCollateralAttoEth / shareTokenSupplyAttoShares` using the pool's remaining economic claim supply as its collateral denominator. Complete-set issuance adds to that denominator, while complete-set and winning-share redemption consume it; fork-time source entitlements materialize without changing it because their claims are already reserved. Zero passes the token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction.", - declarations: [{ name: 'redeemCompleteSet' }], - preconditions: 'Operational and unforked; caller holds every outcome amount requested; caller accepts the resulting ETH call, including zero value. Zero is accepted without a token balance.', - signals: '`CompleteSetRedeemed` and `PoolAccountingCheckpoint`', - }, - { - call: '`redeemShares()`', - caller: 'Anyone; a positive payout requires the caller to hold winning shares', - effect: "Burns the caller's full winning balance and pays its pro-rata remaining collateral. A zero winning balance passes token and accounting checks and follows the normal zero-value event, checkpoint, and ETH-send path; rejection of that ETH call reverts the transaction.", - declarations: [{ name: 'redeemShares' }], - preconditions: 'Operational pool with a final outcome; caller accepts the resulting ETH call, including zero value.', - signals: '`SharesRedeemed` and `PoolAccountingCheckpoint`', - }, - { - call: '`redeemRepFromVault(vault)`', - caller: 'Anyone; REP is always sent to `vault`', - effect: "Burns the vault's REP backing units and returns its proportional vault REP backing.", - declarations: [{ name: 'redeemRepFromVault' }], - preconditions: 'Operational pool with a final outcome; the specified `vault` has no escalation escrow and has redeemable REP.', - signals: '`RepRedeemedFromVault`', - }, - { - call: '`depositToEscalationGame(outcome, maxAmount)`', - caller: 'Vault owner', - effect: - "Deploys the local game on the first deposit. The game factory uses the configured start bond while it is below the live non-decision threshold; if tracked REP supply later makes it too large, the factory uses `nonDecisionThresholdAttoRep - 1` instead. Repeat deposits use the existing game's stored `startBondAttoRep` and `nonDecisionThresholdAttoRep`. Every accepted deposit removes enough REP backing units and escrows dispute-staked REP on the selected outcome.", - declarations: [{ name: 'depositToEscalationGame' }], - preconditions: - 'Question end has passed; pool operational in an unforked universe, without an inherited fixed outcome, and not awaiting continuation. On the first deposit, the live non-decision threshold must exceed one attoREP; outcome and amount accepted; the remaining vault and aggregate pool totals each preserve both live open-interest health branches; a fresh price is required when total capacity ownership is nonzero.', - signals: '`EscalationGameSet` on first deposit; `DepositToEscalationGame`', - }, - { - call: '`withdrawFromEscalationGame(outcome, depositIndexes)`', - caller: 'Anyone; a nonempty list must select deposits belonging to one original depositor', - effect: 'A nonempty list settles local deposits and pays winning REP to the immutable depositor recorded by each deposit. Liquidation cannot change that payout address. An empty list returns after the outer lifecycle checks without settlement, state change, or event.', - declarations: [{ name: 'withdrawFromEscalationGame' }], - preconditions: - 'Game configured; operational pool; valid final outcome. If an external fork interrupted the game, parent withdrawal stays unavailable: winners settle in the child by carried proof, inherited losers require no transaction, and unresolved parent escalation-deposit accounting cleanup is optional. A nonempty list additionally requires valid local indexes and one common depositor.', - signals: 'Per processed deposit, escalation-game `CarryDepositConsumed`; additionally `ClaimDeposit` for a winning payout. No event for an empty list', - }, - { - call: '`withdrawForkedEscalationDeposits(outcome, proofs)`', - caller: 'Anyone; a nonempty list must name one original depositor across all proofs', - effect: - 'A nonempty list verifies and consumes carried proofs, then pays winning child REP to the immutable depositor committed in each leaf. Stable continuation identities retain the creating game, and the cumulative retention-index ratio applies every intervening auction haircut in constant ancestry work. An empty list returns after the outer lifecycle checks without proof verification, state change, or event.', - declarations: [{ name: 'withdrawForkedEscalationDeposits' }], - preconditions: 'Game configured; operational child pool; valid final outcome. A nonempty list additionally requires an initialized and fully resumed continuation game, valid unconsumed winning proofs, and one common depositor.', - signals: 'Per processed proof, escalation-game `CarryDepositConsumed` and `ClaimDeposit`. No event for an empty list', - }, - { - call: '`updateSettlementCollateral()`', - caller: 'Anyone', - effect: - "Accrues elapsed fees through question end while this pool's universe remains unforked; after that universe forks, its fork timestamp replaces question end as this pool epoch's cutoff, including a later question-end-to-fork interval. The cutoff is local to this pool: an activated child starts a separate fee epoch. It moves whole credited fees from settlement collateral into the unallocated accrued-fee reserve and advances the accumulator. With positive elapsed time but zero fee-eligible capacity ownership it clears denominator-specific remainder and advances the timestamp without charging fees.", - declarations: [{ name: 'updateSettlementCollateral' }], - preconditions: 'No caller or lifecycle restriction. It returns unchanged when the accumulator is already at or beyond the clamped timestamp.', - signals: '`PoolAccountingCheckpoint` whenever positive elapsed time is processed, including the zero-capacity-ownership branch; no event for an unchanged timestamp', - }, - { - call: '`updateRetentionRate()`', - caller: 'Anyone', - effect: 'Recalculates the retention rate from current collateral and live oracle-priced minting capacity.', - declarations: [{ name: 'updateRetentionRate' }], - preconditions: 'No caller restriction. It returns unchanged when the pool is not `Operational` or the calculated rate equals the stored rate. Zero live minting capacity selects the maximum retention rate.', - signals: '`PoolAccountingCheckpoint` only when the stored retention rate changes; no event for a no-op', - }, - { - call: '`updateVaultFees(vault)`', - caller: 'Anyone for any address', - effect: - 'First updates pool accrual, then advances the vault fee index and fractional remainder, moves whole assigned fees from reserve to the vault, registers any previously unseen nonzero vault address regardless of economic state, and returns leftover reserve to settlement collateral once a forked pool has checkpointed all fee-eligible capacity ownership.', - declarations: [{ name: 'updateVaultFees' }], - preconditions: 'No caller, nonzero-vault, or lifecycle restriction.', - signals: 'Accrual `PoolAccountingCheckpoint` when due; `VaultAccountingCheckpoint` when the vault index, remainder, or claimable fee balance changes; an additional `PoolAccountingCheckpoint` when pool accounting changes; no event when neither accrual nor vault or pool accounting changes', - }, - { - call: '`withdrawRepFromVault(vault, attoRepAmount)`', - caller: "This pool's `OpenOraclePriceCoordinator` only", - effect: 'Removes the requested proportional REP backing units, or all backing units when the requested remainder would fall below the REP minimum; proportionally reduces the vault and pool capacity ownership; recalculates retention; and transfers the resulting withdrawable REP to `vault`.', - declarations: [{ name: 'withdrawRepFromVault' }], - preconditions: 'Fresh coordinator price; operational pool in an unforked universe; `isEscalationResolved()` is false; no vault REP escrow; the remaining vault and aggregate pool totals each meet the upward-rounded associated-REP and free-REP backing requirements, with equality healthy.', - signals: '`VaultTargetHealthFactorSet`; REP `Transfer`; `RepWithdrawnFromVault`; `VaultAccountingCheckpoint`; and applicable fee-accrual or retention `PoolAccountingCheckpoint` events, including a zero-value transfer/event path if the trusted coordinator supplies zero', - }, - { - call: '`performLiquidation(request)`', - caller: "This pool's `OpenOraclePriceCoordinator` only", - effect: - "Capped by the target vault's open interest and fundable REP award, a nominal debt quote selects proportional capacity ownership rounded downward and moves that ownership to the explicitly selected receiver vault. Moved security-bond debt is the receiver's exact live open-interest increase and cannot exceed the nominal quote or request. On a delegated route, the coordinator additionally bounds it by the staged approval reservation; the self-receiving route has no approval reservation. The operator only submits the transaction. Dispute-staked REP claims, accrued claimable fees, surplus vault REP backing, and unmatched ownership remain with the target. On a full-target request, target open interest minus exact moved debt is recorded as attoETH-denominated bad debt; that residual can include both an award-unfunded slice and integer-allocation residue. Receiver or target dust cannot turn otherwise funded debt into bad debt.", - declarations: [{ name: 'performLiquidation' }], - preconditions: - 'In ABI order, `request` contains `operationId`, `operator`, `receiverVault`, `targetVault`, `requestedDebtAttoEth`, `snapshot`, `minimumReceiverHealthFactorBps`, and `minLiquidationPriceDistanceBps`. The nested snapshot contains `targetBackingUnits`, `targetCapacityOwnershipAttoRep`, `totalPoolHeldAttoRep`, and `totalRepBackingUnits`. Fresh settled coordinator price; operational pool in an unforked universe; `isEscalationResolved()` is false; receiver differs from target. The target backing and capacity-ownership snapshot fields must match; the two pool-total snapshot fields are reconstruction evidence, while execution uses live pool totals. After target and receiver fee checkpoints, the liquidation delegate requires live target backing, dispute-staked REP, and open interest to remain at least `minLiquidationPriceDistanceBps` beyond the liquidation threshold and requires the live target state to remain unhealthy. When debt moves, the receiver must satisfy the protocol backing checks multiplied by its approved minimum health factor, using live post-liquidation state and upward-rounded requirements; its resulting debt must meet the configured debt floor and its REP must meet the vault floor. The target resulting debt must be zero or meet the debt floor; when debt remains, target REP must meet the vault floor.', - signals: - 'Fee-accrual and target or receiver `VaultAccountingCheckpoint` events as needed; `VaultLiquidated` identifies operation, operator, receiver, target, moved debt, moved ownership, and bad debt; `VaultBadDebtRecorded` records residual target debt on a full-target request; final pool accounting checkpoint', - }, - { - call: '`setStartingParams(...)`', - caller: '`SecurityPoolFactory` only', - effect: "Sets the fee timestamp, retention, and collateral, seeds the coordinator with zero for an origin or the parent's last price for a child, then checkpoints initialization.", - declarations: [{ name: 'setStartingParams' }], - preconditions: 'Factory caller. The pool has no internal one-shot or lifecycle guard; the factory exposes it only through atomic deployment wiring.', - signals: 'Coordinator `RepEthPriceSet` and `CoordinatorStateCheckpoint`, then pool `PoolAccountingCheckpoint`, even for zero or repeated values if the factory were to call again', - }, - { - call: '`activateForkMode()`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'activateForkMode' }], - effect: - "Sets `PoolForked`, accrues through the fork clamp, transfers the pool's entire REP balance to the forker, then makes the pool drain its configured escalation game's entire REP balance to the forker. Repeated calls are not lifecycle-guarded and transfer any balances replenished since the prior call before repeating the checkpoints.", - preconditions: "The pool has no inherited fixed outcome, so a fixed child cannot reopen for a later universe fork. There is no current-state guard otherwise. A configured game's drain must succeed or the entire activation reverts without propagating its reason data.", - signals: 'Pool-held REP `Transfer` always, including at zero; configured-game REP `Transfer` only for a positive game balance; accrual checkpoint when due; always `PoolForkModeActivated` and fork-activation `PoolAccountingCheckpoint`', - }, - { - call: '`initializeForkedEscalationGame(...)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'initializeForkedEscalationGame' }], - effect: "Deploys and starts the pool's paused fork-continuation game with inherited timing and optional fixed outcome.", - preconditions: 'No game is configured; downstream `startFromFork` parameters are valid.', - signals: 'Escalation `GameContinuedFromFork`, then pool `EscalationGameSet`', - }, - { - call: '`initializeForkCarrySnapshotWithResolutionBalances(...)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'initializeForkCarrySnapshotWithResolutionBalances' }], - effect: "Installs the continuation game's immutable carry peaks, counts, totals, resolution balances, and normalized nullifier roots.", - preconditions: 'A game is configured; it is a fork continuation with no prior snapshot; leaf counts fit the MMR; supplied or computed snapshot ID matches the data.', - signals: '`ForkCarryCheckpoint`', - }, - { - call: '`resumeForkedEscalationGame()`', - caller: 'Anyone', - declarations: [{ name: 'resumeForkedEscalationGame' }], - effect: "Checks the already-installed immutable carry commitment and aggregate REP funding, clears the pool wait flag, records the resume timestamp, and starts the continuation's remaining escalation clock in one bounded call.", - preconditions: 'Pool is operational, awaiting a configured fork continuation, and the game has not resumed.', - signals: '`ForkContinuationResumed` and `AwaitingForkContinuationSet(false)`', - }, - { - call: '`setAwaitingForkContinuation(shouldAwait)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'setAwaitingForkContinuation' }], - effect: 'Stores whether complete-set minting must wait for continuation initialization.', - preconditions: 'No lifecycle or value-change guard.', - signals: '`AwaitingForkContinuationSet`, including for a repeated value', - }, - { - call: '`setSystemState(newState)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'setSystemState' }], - effect: 'Replaces the pool lifecycle state directly.', - preconditions: 'No transition or value-change guard.', - signals: '`SystemStateSet`, including for a repeated state', - }, - { - call: '`configureVault(vault, repBackingUnits, capacityOwnershipAttoRep, vaultFeeIndex, targetHealthFactorBps, newVaultBadDebtAttoEth, newTotalBadDebtAttoEth)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'configureVault' }], - effect: 'Replaces the vault REP backing units, price-independent capacity ownership, fee index, target health factor, vault bad debt, and aggregate pool bad debt, clears pooled fee-index remainder when capacity ownership changes, and registers the nonzero vault address regardless of the supplied state.', - preconditions: '`vault` is nonzero; no lifecycle or value-change guard.', - signals: 'Always `VaultAccountingCheckpoint` and `PoolAccountingCheckpoint`, including when all supplied values repeat current state', - }, - { - call: '`setTotalRepBackingUnits(newDenominator)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'setTotalRepBackingUnits' }], - effect: 'Replaces the REP backing units denominator.', - preconditions: 'No lifecycle or value-change guard.', - signals: '`TotalRepBackingUnitsSet`, including for zero or a repeated value', - }, - { - call: '`setTotalSharesAttoShares(newTotalSharesAttoShares)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'setTotalSharesAttoShares' }], - effect: 'Replaces stored `shareTokenSupplyAttoShares`, the denominator used by `attoSharesToAttoEth` and complete-set redemption.', - preconditions: 'No lifecycle or value-change guard.', - signals: '`ShareTokenSupplySet`, including for zero or a repeated value', - }, - { - call: '`setPoolFinancials(newSettlementCollateralAttoEth, newTotalCapacityOwnershipAttoRep, newFeeEligibleCapacityOwnershipAttoRep, newTotalBadDebtAttoEth)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'setPoolFinancials' }], - effect: 'Replaces settlement collateral, both price-independent capacity-ownership totals, and aggregate pool bad debt, resets the fee timestamp to the current block, and clears fee-index rounding carry.', - preconditions: 'Fee-eligible capacity ownership does not exceed total capacity ownership, and the supplied settlement collateral does not exceed the current price-converted minting capacity; no lifecycle or value-change guard.', - signals: '`PoolAccountingCheckpoint`, including for repeated financial values', - }, - { - call: '`authorizeChildPool(pool)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'authorizeChildPool' }], - effect: 'Asks the lineage share token to establish `pool` as the canonical authorized pool for its universe; reauthorizing the same pool is a no-op.', - preconditions: 'This parent pool is already authorized; candidate reports this share token; candidate universe has no different canonical pool. No pool-lifecycle guard.', - signals: '`AuthorizationUpdated` only on first authorization; no event when already authorized', - }, - { - call: '`transferEth(receiver, amountAttoEth)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'transferEth' }], - effect: 'Reduces tracked settlement collateral by `amount`, checkpoints the reconciliation, and calls `receiver` with that ETH. At zero amount it reduces no settlement collateral but still emits the checkpoint and performs a zero-value call; callback rejection rolls back the transaction and checkpoint.', - preconditions: 'Fee liabilities are covered; `amount` fits both unreserved pool ETH and tracked settlement collateral; `receiver` accepts the ETH call, including zero value.', - signals: '`PoolAccountingCheckpoint`, including at zero amount; no dedicated ETH-transfer event', - }, - { - call: '`addFeeEligibleCapacityOwnershipAttoRep(vault, amountAttoRep)`', - caller: '`SecurityPoolForker` only', - declarations: [{ name: 'addFeeEligibleCapacityOwnershipAttoRep' }], - effect: 'Adds newly auction-claimed capacity ownership to the live fee denominator, clears the pooled fee-index rounding remainder, then checkpoints elapsed fees and recalculates retention from collateral and unchanged total capacity ownership. The assignment itself does not change live minting capacity.', - preconditions: 'The resulting fee-eligible capacity ownership cannot exceed total capacity ownership; no lifecycle, vault, positive-amount, or value-change guard.', - signals: 'Retention-rate `PoolAccountingCheckpoint` first when the rate changes, then `VaultAccountingCheckpoint` and auction-claim `PoolAccountingCheckpoint`, including the latter two at zero amount; the calling forker emits `ClaimAuctionProceeds` only after the broader credit workflow completes', - }, - { - call: 'Direct ETH transfer to `receive()`', - caller: "Forker, this pool's truth auction, or parent pool only", - effect: 'Accepts protocol-routed ETH used by migration and auction settlement. Forced ETH remains raw, unaccounted surplus rather than settlement collateral or fees.', - declarations: [{ kind: 'receive', name: 'receive' }], - preconditions: 'Sender is one of the three authorized protocol addresses. Forced ETH bypasses this ordinary-call guard.', - signals: 'No dedicated receive event; the calling protocol step emits its own event', - }, - ], - }, - { - compiledAbiFingerprint: '53bc009e3dfbd79b99b31c22b2128cf93b16b73059ce344190ce65b39400183c', - name: 'SecurityPoolForker', - purpose: 'Freezes parent pools, creates selected child pools, migrates vault and escalation state, and settles collateral-repair auctions.', - readAbiFingerprint: '2d321031db910e3feec1b22c481203de331c894217e60456fa429472808aa4a4', - readSurface: - 'Use `zoltar`, `forkData`, `getMigratedAttoRep`, `getForkActivationTime`, `isEscalationDepositClaimedDirectly`, `getEscalationDepositId`, `getDirectlyClaimedEscalationPrincipal`, `isEscalationWinnerHaircutPaidByFork`, `getEscalationMigrationEntitlementStatus`, `getOwnForkRepBuckets`, `getOwnForkMigrationStatus`, `getMigrationProxyAddress`, `getQuestionOutcome`, `attoRepToBackingUnits`, and `backingUnitsToAttoRep` to reconstruct fork progress and preview migration conversions.', - readDeclarations: [ - { name: 'forkData' }, - { name: 'getMigratedAttoRep' }, - { name: 'getForkActivationTime' }, - { name: 'isEscalationDepositClaimedDirectly' }, - { name: 'getEscalationDepositId' }, - { name: 'getDirectlyClaimedEscalationPrincipal' }, - { name: 'isEscalationWinnerHaircutPaidByFork' }, - { name: 'getEscalationMigrationEntitlementStatus' }, - { name: 'getOwnForkRepBuckets' }, - { name: 'getOwnForkMigrationStatus' }, - { name: 'getMigrationProxyAddress' }, - { name: 'getQuestionOutcome' }, - { name: 'attoRepToBackingUnits', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }, - { name: 'backingUnitsToAttoRep', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }, - ], - readStorageDeclarations: [{ name: 'zoltar', sourcePath: 'solidity/contracts/peripherals/SecurityPoolForkerBase.sol' }], - securityBoundaryHeading: 'Child-game trust boundary', - securityBoundary: - 'Fork entrypoints and child setup may receive contracts through unauthenticated pool lineages. External-universe initiation requires the supplied pool to be authorized by its declared share token, but that relationship alone does not prove factory registration; own-game initiation does not perform that authorization check. Canonicality comes from the configured `SecurityPoolFactory` registry. A game relationship check is point-in-time: the reported nonzero game address must return the supplied pool or child from `securityPool()` when validated. This does not prove that an arbitrary game getter is immutable or that the address was factory-deployed. Child setup captures one reported game address, validates it before privileged use, and reuses that exact address for continuation backing and escrow work. When unresolved escalation requires a continuation and setup initially reports no game, initialization creates one; the forker then captures and validates it before continuation use. Combined vault migration passes the captured child/game pair into unresolved cleanup without reading the child getter again. Truth-auction completion performs a fresh point-in-time validation of the game reported then before checking continuation readiness. Genuine factory-deployed `EscalationGame` instances store their pool immutably, but safety on unauthenticated paths does not assume arbitrary contracts do.', - sourcePath: 'solidity/contracts/peripherals/SecurityPoolForker.sol', - interactions: [ - { - call: '`initiateSecurityPoolFork(securityPool)`', - caller: 'Anyone', - effect: 'Freezes the supplied pool after an external universe fork, drains its pool and game REP, and records a migration snapshot keyed by that address. The snapshot is canonical only when the supplied pool is already registered by the configured `SecurityPoolFactory`.', - declarations: [{ name: 'initiateSecurityPoolFork' }], - preconditions: - 'Pool operational with no inherited fixed outcome; the pool is authorized by its declared share token; its universe already forked; fork state not initialized; if an escalation game exists, it reports the supplied pool from `securityPool()` when validated and the universe fork occurred before that game settled. Declared-token authorization is not configured-factory registration; see the [child-game trust boundary](#child-game-trust-boundary).', - signals: '`SecurityPoolForkSnapshot` and `ParentRepLocked`; additionally `DisputeStakedRepDrainedAtFork` when unresolved escalation exists', - }, - { - call: '`forkZoltarWithOwnEscalationGame(securityPool)`', - caller: 'Anyone', - effect: "Uses the supplied pool game's non-decision to fork Zoltar, freezes that pool, and records own-fork REP buckets and snapshot state keyed by its address. The snapshot is canonical only when the supplied pool is already registered by the configured `SecurityPoolFactory`.", - declarations: [{ name: 'forkZoltarWithOwnEscalationGame' }], - preconditions: - 'Pool operational with no inherited fixed outcome; its escalation game reports the supplied pool from `securityPool()` when validated and `canTriggerOwnFork()` is true because it recorded a local non-decision or inherited a threshold tie without a game-level fixed outcome; universe not already forked. The game-local predicate does not bypass the pool guard. Unlike external-universe initiation, this entrypoint does not require declared-share-token authorization; neither path authenticates the supplied address against the configured pool factory. See the [child-game trust boundary](#child-game-trust-boundary).', - signals: '`SecurityPoolForkSnapshot`, `ParentRepLocked`, and Zoltar fork events; additionally `DisputeStakedRepDrainedAtFork` when unresolved escalation exists', - }, - { - call: '`migrateRepToZoltar(securityPool, outcomeIndices)`', - caller: 'Anyone', - effect: "For a positive migration amount and nonempty list, ensures that the forker's recorded pool migration amount has been split into each selected child REP branch. A zero migration amount or empty list returns after the proxy and pool-state guards without per-outcome validation or events.", - declarations: [{ name: 'migrateRepToZoltar' }], - preconditions: - 'Migration proxy exists and the pool is `PoolForked`. Only a positive migration amount with at least one selected outcome checks the eight-week window, existing child `ForkMigration` state, outcome validity, and cumulative split bound. A zero amount skips those checks even when outcome values are supplied.', - signals: '`MigrationRepSplit` and `ChildRepSplit` when a selected branch requires a new split; no event for a zero amount, empty list, or already-satisfied branch', - }, - { - call: '`createChildUniverse(securityPool, outcomeIndex)`', - caller: 'Anyone', - effect: - "Loads an already deployed child universe and REP token or deploys them when absent, then lazily deploys the selected child pool, coordinator, and auction; authorizes and links the child; captures and validates the child's escalation game; and initializes any continuation snapshot and materializes or sweeps child backing through that validated game.", - declarations: [{ name: 'createChildUniverse' }], - preconditions: - "Parent in migration window; selected fork outcome is well formed; child pool is not already deployed. The returned auction is nonzero, deployed, and has never been trusted by this forker; the child's fork-data slot is unused; and the child reports the expected parent, universe, source factory, forker, and auction. The selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary). These relationship checks do not independently prove configured-factory registration.", - signals: - '`DeployChild` only when child REP was absent; always `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, `ChildPoolLinked`, and `TotalRepBackingUnitsSet`; `AwaitingForkContinuationSet`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, `MigrationRepSplit`, `ChildDisputeStakedRepMaterialized`, and `PoolHeldRepSweptToChild` as continuation and backing state requires', - }, - { - call: '`migrateVault(securityPool, outcomeIndex)`', - caller: 'Vault owner for their non-escrowed position', - declarations: [{ name: 'migrateVault' }], - effect: - "Converts the caller's parent REP backing-unit claim to REP at the fork snapshot and credits that REP amount as child-local backing units; transfers REP-denominated capacity ownership, target health factor, and vault bad debt into one child pool; checkpoints but retains claimable fees in the parent vault; and separately routes proportional pool-level settlement collateral while preserving aggregate bad debt. Repeat calls can have no additional REP backing units, capacity ownership, or vault bad debt to move.", - preconditions: "Migration window open; the selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary). The optional unresolved parent escalation-deposit accounting cleanup wrapper calls this function first to migrate transferable vault state.", - signals: '`VaultBadDebtMigrated` and `VaultMigrationCheckpoint`', - }, - { - call: '`migrateVaultWithUnresolvedEscalation(securityPool, vault, childOutcomeIndex)`', - caller: 'The named vault owner', - effect: - "First runs ordinary migration for the same vault, which may convert its parent REP backing-unit claim to REP and credit that REP as child-local backing units; transfer capacity ownership, target health factor, and vault bad debt to the selected child while preserving aggregate bad debt; checkpoint but retain claimable fees in the parent vault; and separately route proportional pool-level settlement collateral. It returns the selected child and its captured, validated escalation game to the unresolved-accounting cleanup phase, which reuses those exact addresses without reading the child's game again. The cleanup then clears that vault's unresolved parent escalation-deposit accounting in constant-size work and records it; the cleanup neither funds dispute-staked REP backing nor authorizes carried proofs.", - declarations: [{ name: 'migrateVaultWithUnresolvedEscalation' }], - preconditions: "Migration window open; caller equals `vault`; selected child not already recorded for this optional cleanup; the selected child's reported nonzero escalation game passes the [child-game trust boundary](#child-game-trust-boundary).", - signals: 'Vault migration events, including `VaultBadDebtMigrated`, plus `EscalationMigrationEntitlementInitialized` on first export and `EscalationMigrationEntitlementMaterialized` for the selected child', - }, - { - call: '`claimForkedEscalationDeposits(...)`', - caller: 'The named vault owner', - effect: - "First gets or lazily deploys the selected child universe, REP token, pool, coordinator, and auction, then captures and validates the child's escalation game and uses that same game for continuation backing and escrow payment. A nonempty list claims winning own-fork parent deposits and records their stable identities against descendant replay. An empty list still performs child setup and emits a zero-valued claim summary.", - declarations: [{ name: 'claimForkedEscalationDeposits' }], - preconditions: - 'Caller equals `vault`; unresolved escalation existed when the pool initiated its own fork and the parent game still satisfies `canTriggerOwnFork()` by having either a local non-decision or an inherited threshold tie without a fixed outcome; selected child can be created or loaded, remains in `ForkMigration`, has a continuation game that passes the [child-game trust boundary](#child-game-trust-boundary), and is inside the eight-week claim window. A nonempty list additionally requires the matching winning outcome, unclaimed deposit identities, and every deposit to commit `vault` as its immutable depositor.', - signals: - '`DeployChild`, `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, `ChildPoolLinked`, `TotalRepBackingUnitsSet`, `AwaitingForkContinuationSet`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, `MigrationRepSplit`, `ChildDisputeStakedRepMaterialized`, and `PoolHeldRepSweptToChild` as setup requires; per claimed deposit, `CarryDepositConsumed` and `ClaimDeposit`; escrow record/export events when REP is paid; always `ClaimForkedEscalationDepositsToWallet`, including for an empty list', - }, - { - call: '`startTruthAuction(securityPool)`', - caller: 'Anyone', - effect: "Copies the frozen parent's remaining economic claim supply into the child, closes migration accounting, and either reopens a fully backed child or starts its repair auction.", - declarations: [{ name: 'startTruthAuction' }], - preconditions: 'Child migration window ended; pool is in fork migration; required child REP is available. If unresolved escalation existed at fork, any game reported during immediate completion passes the [child-game trust boundary](#child-game-trust-boundary).', - signals: '`ShareTokenSupplySet` and `TruthAuctionStarted`; immediate no-auction completion also emits `TruthAuctionFinalized`, pool accounting checkpoints, and `ForkContinuationResumed` for an unresolved continuation', - }, - { - call: '`finalizeTruthAuction(securityPool)`', - caller: 'Anyone', - effect: 'Finalizes the ended auction, accounts migration-routed settlement collateral plus accepted bid ETH, activates the child at that settlement-collateral level, and fixes bidder REP-backing-unit and capacity-ownership rates. A nonzero repair contribution is rejected.', - declarations: [{ name: 'finalizeTruthAuction' }], - preconditions: - 'Truth auction started, its one-week window has passed, `msg.value` is zero, and migrated collateral plus accepted bid ETH does not exceed current price-converted minting capacity. If unresolved escalation existed at fork, the game reported at completion passes the [child-game trust boundary](#child-game-trust-boundary).', - signals: '`TruthAuctionFinalized`, auction `AuctionFinalized`, and pool accounting checkpoints; `TruthAuctionHaircutApplied` when purchased REP removes a positive escalation allocation; `ForkContinuationResumed` for an unresolved continuation', - }, - { - call: '`settleAuctionBids(securityPool, vault, claimTickIndices, refundTickIndices)`', - caller: 'Anyone on behalf of the named bidder vault', - declarations: [{ name: 'settleAuctionBids' }], - effect: - 'Before finalization, refunds only provably losing bids. After finalization, combines claim and refund indexes into one settlement withdrawal and credits each fixed-position REP backing and capacity-ownership result. It also assigns the bidder vault its cumulative share of auctioned bad debt: intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid may receive capacity ownership even when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion.', - preconditions: 'At least one index; before finalization the claim list must be empty and refund indexes must be eligible; after finalization all indexes must belong to the named vault owner and remain unsettled.', - signals: 'Underlying auction `BidSettled`; `EthRefundDeferred` when the named bidder rejects a positive refund; `ClaimAuctionProceeds` with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited', - }, - { - call: '`claimAuctionProceeds(securityPool, vault, tickIndices)`', - caller: 'Anyone on behalf of the named bidder vault', - declarations: [{ name: 'claimAuctionProceeds' }], - effect: - 'For a nonempty list, withdraws finalized bid settlements, converts purchased REP into child REP backing units, independently credits the bid positional capacity-ownership allocation, and assigns the bidder vault its cumulative share of auctioned bad debt. Intermediate cumulative shares round down and the final capacity claim receives the exact residual, so claim order cannot change the total. A winning dust bid can receive positive capacity ownership when its REP allocation rounds to zero. A positive ETH push is gas-bounded and defers on rejection, revert, or gas exhaustion, so recipient code cannot block the subsequent credit. For an empty list, the underlying auction withdrawal returns three zeros and the wrapper exits after the finalization guard without validating bids or the named beneficiary, calling it, changing state, or emitting events.', - preconditions: 'Auction finalized. A nonempty list additionally requires every index to belong to the named vault owner and remain unsettled.', - signals: 'For processed bids, underlying auction `BidSettled`; `EthRefundDeferred` when the named bidder rejects a positive refund; `ClaimAuctionProceeds` with cumulative claimed and total auctioned bad debt when REP backing, capacity ownership, or bad debt is credited; no event for an empty list', - }, - { - call: '`initializeChildForkedEscalationGameIfNeeded(parent, child, childEscalationGame)`', - caller: 'This `SecurityPoolForker` contract only, through its migration delegate callback', - effect: - 'Allows delegated migration code to initialize a child continuation while preserving the forker as the authoritative caller and the already captured child-game identity. When unresolved escalation requires a continuation and no game existed, it captures and validates the game created by initialization before any continuation use.', - declarations: [{ name: 'initializeChildForkedEscalationGameIfNeeded' }], - preconditions: 'External caller is the forker itself; parent and child match the active migration path; a supplied nonzero game passes the [child-game trust boundary](#child-game-trust-boundary).', - signals: '`ChildDisputeStakedRepMaterialized` and escalation-continuation events when initialization is required', - }, - { - call: 'Direct ETH transfer to `receive()`', - caller: 'A child-pool truth auction trusted by this forker during `ChildPoolLinked`', - effect: 'Accepts auction ETH during forker-controlled auction finalization.', - declarations: [{ kind: 'receive', name: 'receive' }], - preconditions: '`trustedAuctionAddresses[msg.sender]` was set when the forker linked the child and emitted `ChildPoolLinked`; configured-factory registration determines whether that lineage is canonical.', - signals: 'No dedicated receive event; auction `AuctionFinalized` is followed by forker `TruthAuctionFinalized` and pool accounting checkpoints', - }, - ], - }, - { - compiledAbiFingerprint: 'aa111e15b811c762945753415ef818ed6f85ec81553ab7ede082aca87869ad64', - name: 'EscalationGame', - purpose: 'Escrows outcome REP, raises the running resolution cost, detects non-decision, and settles local or carried deposits.', - readAbiFingerprint: 'ed587e847ca84dfb0faa31896f294197b8e84a13c229b3bab68447f262dae58d', - readSurface: - 'Base getters are `securityPool`, `repToken`, `activationTime`, `nonDecisionThresholdAttoRep`, `startBondAttoRep`, `nonDecisionTimestamp`, `nonDecisionState`, `forkContinuation`, `forkElapsedAtStart`, `forkResumedAt`, `fixedQuestionOutcome`, `nodes`, `disputeStakedRepByVaultAttoRep`, `totalDisputeStakedAttoRep`, `truthAuctionRepBeforeAttoRep`, `truthAuctionRepRemainingAttoRep`, `cumulativeClaimRetention`, and `cumulativeClaimRetentionExponent`. The claim delegate fallback exposes `rootClaimSourceGame`, `applyInheritedClaimRetention`, and `applyInheritedSourceStorageBasis`. The source-storage-basis read allocates retained carry by cumulative-prefix differences so leaf allocations sum to the aggregate checkpoint. `disputeStakedRepByVaultAttoRep` is locally attributed current-game escrow used for health; inherited carry remains aggregate commitment state until proof settlement. Use `previewDepositOnOutcome`, `computeIterativeAttritionCostAttoRep`, `computeTimeSinceStartFromAttritionCostAttoRep`, `totalCostAttoRep`, `getEscalationGameEndDate`, `getQuestionResolution`, `getFinalQuestionResolution`, `hasReachedNonDecision`, `canTriggerOwnFork`, `getBindingCapitalAttoRep`, `getOutcomeBalancesAttoRep`, `getDepositsByOutcome`, `getDepositsByOutcomeLength`, `forkCarrySnapshotInitialized`, `getOutcomeState`, `getForkCarrySnapshot`, `getForkCarryRoots`, `isForkCarryFundingComplete`, `getCarryLeafPageByOutcome`, `getProofConsumedCarriedDepositIndexesByOutcome`, `getLocalUnresolvedPrincipalByVaultAndOutcome`, and `getForkedEscrowByVaultAndOutcome` for calculations, lifecycle authorization, pages, carry state, and escrow. Ordinary users route deposits and withdrawals through `SecurityPool`.', - readDeclarations: [ - { name: 'previewDepositOnOutcome' }, - { name: 'disputeStakedRepByVaultAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, - { name: 'rootClaimSourceGame', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, - { name: 'applyInheritedClaimRetention', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, - { name: 'applyInheritedSourceStorageBasis', sourcePath: 'solidity/contracts/peripherals/EscalationGameClaimDelegate.sol' }, - { name: 'computeIterativeAttritionCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'computeTimeSinceStartFromAttritionCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'totalCostAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getEscalationGameEndDate', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getQuestionResolution', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getFinalQuestionResolution', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'hasReachedNonDecision', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'canTriggerOwnFork', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getBindingCapitalAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getOutcomeBalancesAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameCalculations.sol' }, - { name: 'getDepositsByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }, - { name: 'getDepositsByOutcomeLength', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }, - { name: 'forkCarrySnapshotInitialized', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getOutcomeState', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getForkCarrySnapshot', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getForkCarryRoots', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'isForkCarryFundingComplete', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getCarryLeafPageByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getProofConsumedCarriedDepositIndexesByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }, - { name: 'getLocalUnresolvedPrincipalByVaultAndOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }, - { name: 'getForkedEscrowByVaultAndOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }, - ], - readStorageDeclarations: [ - { name: 'securityPool', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, - { name: 'repToken', sourcePath: 'solidity/contracts/peripherals/EscalationGameState.sol' }, - { name: 'activationTime', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'nonDecisionThresholdAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'startBondAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'nonDecisionTimestamp', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'nonDecisionState', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'forkContinuation', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'forkElapsedAtStart', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'forkResumedAt', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'nodes', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'totalDisputeStakedAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'truthAuctionRepBeforeAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'truthAuctionRepRemainingAttoRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'cumulativeClaimRetention', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'cumulativeClaimRetentionExponent', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - { name: 'fixedQuestionOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameStorage.sol' }, - ], - sourcePath: 'solidity/contracts/peripherals/EscalationGame.sol', - interactions: [ - { - call: '`start(startBondAttoRep, nonDecisionThresholdAttoRep)`', - caller: '`EscalationGameFactory` contract during atomic deployment', - effect: 'Initializes a local game and sets activation three days after deployment. For ordinary pool games, the factory lowers an oversized configured bond to `nonDecisionThresholdAttoRep - 1` before this call.', - declarations: [{ name: 'start' }], - preconditions: 'Game not already started; threshold exceeds the positive start bond. Positive attoREP values are valid.', - signals: '`GameStarted`', - }, - { - call: '`startFromFork(startBondAttoRep, nonDecisionThresholdAttoRep, elapsedAtFork, fixedQuestionOutcome, winnerHaircutPaidByFork, forkCarryInitialBackingAttoRep)`', - caller: 'Immutable owner (`EscalationGameFactory`) during atomic continuation deployment', - effect: 'Initializes a paused continuation with inherited elapsed time, an optional fixed matching child outcome, and immutable fork-time haircut/backing accounting. It does not start the remaining clock until `resumeFromFork`.', - declarations: [{ name: 'startFromFork' }], - preconditions: 'Game not started; threshold exceeds the positive start bond; inherited elapsed time is no greater than seven weeks. Positive attoREP values are valid.', - signals: '`GameContinuedFromFork`', - }, - { - call: '`resumeFromFork()`', - caller: 'Owning `SecurityPool` only', - effect: - 'Records the resume timestamp once the immutable carry commitment is installed and funded. The new deadline is `max(rebasedCurveEnd, forkResumedAt + 3 days)`, so even an exhausted inherited clock receives a fresh response period. After that deadline, `getFinalQuestionResolution` returns the fixed outcome when the continuation has one.', - declarations: [{ name: 'resumeFromFork' }], - preconditions: - 'Fork-continuation mode; not previously resumed; immutable carry snapshot installed; aggregate REP funding complete. An unrelated fork requires one-to-one backing of effective unresolved principal. For an own-fork continuation, recorded initial backing must be at least `sourcePrincipalAtForkAttoRep - ⌊sourcePrincipalAtForkAttoRep / 5⌋`, where `sourcePrincipalAtForkAttoRep` is the aggregate raw unresolved principal installed by the snapshot before effective direct-claim deductions. The live balance must cover that initial backing minus child REP already exported by valid direct pre-resume claims.', - signals: '`ForkContinuationResumed`', - }, - { - call: '`applyTruthAuctionHaircut(repToRemove)`', - caller: "The child pool's `SecurityPoolForker` only", - declarations: [{ name: 'applyTruthAuctionHaircut' }], - effect: 'Transfers the sold child REP to the pool, applies one retention ratio to escrow and outcome balances, and rebases elapsed curve time. The fork remains final and the game remains paused until the pool resumes it.', - preconditions: "Paused fork continuation; no prior auction haircut; the requested amount is below the game's live REP balance.", - signals: '`TruthAuctionHaircutApplied` and REP `Transfer`', - }, - { - call: '`recordDepositFromSecurityPool(...)`', - caller: 'Owning `SecurityPool` only', - effect: 'Appends an accepted local deposit, updates outcome and vault escrow, and records its carry leaf.', - declarations: [{ name: 'recordDepositFromSecurityPool' }], - preconditions: 'Explicit non-decision state is `None`; game unresolved; valid outcome; preview and accepted cumulative amount match; room remains below threshold.', - signals: '`LocalDepositAppended`, `DepositOnOutcome`, optionally `NonDecisionReached`', - }, - { - call: '`withdrawDeposit(uint256 depositIndex, outcome)`', - caller: 'Owning `SecurityPool` only', - declarations: [{ name: 'withdrawDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: "Consumes one local deposit after resolution. A winner pays the deposit's immutable depositor after its haircut; a loser only retires its escrow accounting.", - preconditions: 'Explicit non-decision state is `None`; non-`None` supplied outcome; game final; game and pool final outcomes match; valid unsettled local deposit index.', - signals: '`CarryDepositConsumed` and `VaultEscrowUpdated`; for a winner, `ClaimDeposit`, positive REP payout `Transfer`, and haircut burn signals when nonzero', - }, - { - call: '`initializeForkCarrySnapshotWithResolutionBalances(...)`', - caller: 'Owning `SecurityPool` only', - declarations: [{ name: 'initializeForkCarrySnapshotWithResolutionBalances', sourcePath: 'solidity/contracts/peripherals/EscalationGameCarry.sol' }], - effect: 'Installs the immutable inherited peaks, leaf counts, carry totals, resolution balances, and normalized nullifier roots; zero snapshot ID selects the computed ID. Two or more threshold-full inherited balances set `nonDecisionState` to `InheritedThresholdTie` without creating a local timestamp.', - preconditions: 'Fork-continuation mode; no prior snapshot; each leaf count fits the MMR; supplied nonzero snapshot ID equals the hash of the normalized data.', - signals: '`ForkCarryCheckpoint`; additionally `InheritedThresholdTie` when the installed balances meet the non-decision threshold', - }, - { - call: '`claimDepositForWinning(depositIndex, outcome)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'claimDepositForWinning', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: "Consumes a selected local deposit as a winner, consumes its vault escrow, burns the computed haircut when nonzero, and transfers the remaining positive REP payout to the deposit's immutable depositor.", - preconditions: 'Non-`None` supplied outcome and valid unsettled local deposit with sufficient escrow. This entrypoint itself does not check final resolution or that the supplied outcome won; its trusted caller selects that path.', - signals: '`CarryDepositConsumed`, `VaultEscrowUpdated`, `ClaimDeposit` with `transferredRep = true`; REP payout `Transfer` and haircut burn signals only when their amounts are positive', - }, - { - call: '`claimDepositForWinningWithoutTransfer(depositIndex, outcome)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'claimDepositForWinningWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: - "Consumes a selected local deposit and its vault escrow. The depositor's raw escrow backing decreases by the inverse-retention claim units corresponding to the deposit's original principal: the principal itself with no local auction checkpoint, or `⌈originalPrincipal × truthAuctionRepBeforeAttoRep / truthAuctionRepRemainingAttoRep⌉` after a local haircut. Other unconsumed deposits by the same depositor remain backed. The game returns the computed winner amount to the trusted caller but deliberately neither transfers REP nor burns the computed haircut.", - preconditions: 'Valid in-range supplied outcome and unsettled local deposit with sufficient escrow. Unlike the transferring form, it has no explicit non-`None` guard; neither form checks final resolution or that the outcome won.', - signals: '`CarryDepositConsumed`, `VaultEscrowUpdated`, and `ClaimDeposit` with `transferredRep = false`; no REP transfer or haircut burn', - }, - { - call: '`exportUnresolvedDeposit(depositIndex, outcome)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'exportUnresolvedDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: 'Returns deposit identity and amount to the trusted caller while consuming the local deposit from unresolved/escrow accounting without transferring REP.', - preconditions: 'Non-`None` outcome and a valid unsettled local deposit. Final resolution is not required.', - signals: '`CarryDepositConsumed` and `VaultEscrowUpdated`; no `ClaimDeposit` or REP transfer', - }, - { - call: '`withdrawDeposit(CarriedDepositProof proof, outcome)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'withdrawDeposit', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: 'Consumes an inherited proof, transfers any positive winning payout, and burns the positive haircut unless the fork already paid it.', - preconditions: 'Non-`None` supplied outcome; game final and matching the pool final outcome; supplied outcome is the winner; parent deposit was not directly claimed; valid unconsumed Merkle/nullifier proof.', - signals: '`CarryDepositConsumed` and `ClaimDeposit` with `transferredRep = true`; REP payout `Transfer` and haircut burn signals only when positive', - }, - { - call: '`exportVaultUnresolvedTotals(vault, repReceiver)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'exportVaultUnresolvedTotals', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], - effect: "Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, consumes aggregate unresolved and escrow accounting when positive, and transfers the positive total to `repReceiver`.", - preconditions: '`vault` is nonzero and has not exported before. There is no explicit nonzero-receiver guard: a zero receiver succeeds when the total is zero but the token rejects it when a positive transfer is attempted.', - signals: 'Always `VaultUnresolvedTotalsExported`, including when every amount is zero; `VaultEscrowUpdated` and REP `Transfer` only for a positive total', - }, - { - call: '`exportVaultUnresolvedTotalsWithoutTransfer(vault)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'exportVaultUnresolvedTotalsWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], - effect: "Marks the vault's local unresolved totals exported exactly once, clears each outcome amount, and consumes aggregate unresolved and escrow accounting when positive, but leaves token movement to its caller.", - preconditions: '`vault` is nonzero and has not exported before.', - signals: 'Always `VaultUnresolvedTotalsExported` with `transferredRep = false`, including when every amount is zero; `VaultEscrowUpdated` only for a positive total; no REP transfer', - }, - { - call: '`drainAllRep(receiver)`', - caller: 'Owning `SecurityPool` only', - declarations: [{ name: 'drainAllRep', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - effect: "Transfers the game's full REP balance to `receiver`. A zero balance returns zero without a transfer or event.", - preconditions: '`receiver` is nonzero; no positive-balance requirement. The protocol reaches this call from the owning pool after `activateForkMode` enters `PoolForked`.', - signals: 'REP `Transfer` for a positive balance; no event at zero balance', - }, - { - call: '`recordForkedEscrowForOutcome(depositor, outcome, sourcePrincipalAttoRep, childRepAmountAttoRep)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'recordForkedEscrowForOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], - effect: - 'Accumulates source principal and child REP escrow for the depositor and outcome. The depositor remains the immutable payout owner; inherited claims remain in the carry commitment and are not copied into child-local ownership state. When both amounts are zero, returns without changing state or emitting an event.', - preconditions: 'Outcome is not `None`; depositor is nonzero. Source principal and child REP may independently be zero; when both are zero, the call is a no-op.', - signals: '`ForkedEscrowRecorded` for a nonzero record; no event when both amounts are zero', - }, - { - call: '`exportForkedEscrowByOutcome(vault, repReceiver)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'exportForkedEscrowByOutcome', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], - effect: 'Marks every remaining per-outcome escrow amount exported and transfers its positive child REP. When all outcomes were already empty or exported, returns zero arrays without state change, token transfer, or event.', - preconditions: '`vault` and `repReceiver` are nonzero.', - signals: '`ForkedEscrowExported` when any source principal or child REP remains; REP `Transfer` when positive child REP is transferred; no event for an already-empty export', - }, - { - call: '`exportForkedEscrowByOutcomeWithoutTransfer(vault)`', - caller: 'Owning `SecurityPool` or its `SecurityPoolForker`', - declarations: [{ name: 'exportForkedEscrowByOutcomeWithoutTransfer', sourcePath: 'solidity/contracts/peripherals/EscalationGameEscrow.sol' }], - effect: 'Marks every remaining per-outcome escrow amount exported without transferring child REP. When all outcomes were already empty or exported, returns zero arrays without state change or event.', - preconditions: '`vault` is nonzero.', - signals: '`ForkedEscrowExported` with `transferredRep = false` when any source principal or child REP remains; no REP transfer; no event for an already-empty export', - }, - { - call: '`sweepResidualRepToSecurityPool()`', - caller: 'Anyone', - effect: 'Returns ordinary-game residual REP to the owning pool. Burns fork-continuation residual so pre-child capital cannot accrue to late or nonexistent child owners.', - declarations: [{ name: 'sweepResidualRepToSecurityPool', sourcePath: 'solidity/contracts/peripherals/EscalationGameSettlement.sol' }], - preconditions: 'Final outcome; no unresolved principal; no vault escrow; positive residual balance.', - signals: '`ResidualRepSweptToSecurityPool` for an ordinary game; `ForkContinuationResidualRepBurned` for a fork continuation', - }, - ], - }, - { - compiledAbiFingerprint: '24fef7375af443bf5477c0c4afa6d6ce6ef852f82a8b17d46bd1cea15bc3c264', - name: 'LiquidationApprovalRegistry', - purpose: 'Stores coordinator-local, bounded authorization for a receiver vault to accept liquidation debt from an exact operator.', - readAbiFingerprint: '04465d90cef2bd37454bf8496fffcaccf07f0ec5808f31ffd6481d4cdc46f810', - readSurface: - 'Use `coordinator` to identify the validating coordinator and implied security pool. `LIQUIDATION_APPROVAL_TYPEHASH`, `DOMAIN_SEPARATOR`, and `liquidationApprovalDigest` define the chain- and registry-bound EIP-712 message. `getLiquidationApproval` reports parameters plus available, reserved, consumed, and revoked state; `minimumLiquidationApprovalNonce` reports receiver invalidation state; `liquidationReservations` and `minimumHealthFactorBps` expose operation reservation state and its execution-time health floor.', - readDeclarations: [{ name: 'DOMAIN_SEPARATOR' }, { name: 'liquidationApprovalDigest' }, { name: 'getLiquidationApproval' }, { name: 'minimumHealthFactorBps' }], - readStorageDeclarations: [{ name: 'coordinator' }, { name: 'LIQUIDATION_APPROVAL_TYPEHASH' }, { name: 'minimumLiquidationApprovalNonce' }, { name: 'liquidationReservations' }], - sourcePath: 'solidity/contracts/peripherals/LiquidationApprovalRegistry.sol', - interactions: [ - { - call: '`initialize(coordinator)`', - caller: 'Anyone while the registry remains uninitialized; normal factory deployment initializes the clone atomically', - effect: 'Binds this registry clone to one coordinator and therefore one security pool.', - declarations: [{ name: 'initialize' }], - preconditions: 'Coordinator is nonzero and the registry has not been initialized.', - signals: 'No event; the public `coordinator` getter records the binding.', - }, - { - call: '`setLiquidationApproval(params)`', - caller: 'The receiver vault named by `params`', - effect: 'Installs explicit onchain bounded approval state and consumes the receiver-scoped nonce.', - declarations: [{ name: 'setLiquidationApproval' }], - preconditions: 'Correct local pool; nonzero receiver and operator; positive cumulative and per-operation limits with per-operation no greater than cumulative; health factor at least 10,000 BPS; live ordered validity window; unused, non-invalidated nonce.', - signals: '`LiquidationApprovalSet`', - }, - { - call: '`permitLiquidationApproval(params, signature)`', - caller: 'Anyone relaying the receiver vault signature', - effect: 'Validates an EIP-712 EOA or ERC-1271 signature immediately, installs explicit approval state, and consumes the receiver-scoped nonce.', - declarations: [{ name: 'permitLiquidationApproval' }], - preconditions: 'Signature is valid for `params.receiverVault`; the chain ID, registry address, stable name/version, pool, receiver, operator, target scope, limits, health factor, window, and nonce are bound by the digest; direct-install validation rules also pass.', - signals: '`LiquidationApprovalSet`', - }, - { - call: '`revokeLiquidationApproval(approvalId)`', - caller: 'Approval receiver vault only', - effect: 'Prevents new reservations while leaving reservations already attached to staged operations intact.', - declarations: [{ name: 'revokeLiquidationApproval' }], - preconditions: 'Approval exists and is not already revoked.', - signals: '`LiquidationApprovalRevoked` with available, reserved, and consumed totals', - }, - { - call: '`invalidateLiquidationApprovalNonce(newNonce)`', - caller: 'Receiver vault invalidating its own older nonce range', - effect: 'Raises the minimum nonce accepted for new approval installation or reservation.', - declarations: [{ name: 'invalidateLiquidationApprovalNonce' }], - preconditions: 'New nonce is greater than the receiver current minimum.', - signals: '`LiquidationApprovalNonceInvalidated`', - }, - { - call: '`reserve(operationId, approvalId, receiverVault, targetVault, operator, requestedDebtAttoEth, snapshotTargetDebtAttoEth, latestExecutionTimestamp)`', - caller: 'Bound coordinator only', - effect: 'Moves quota from available to pending reserved at staging, bounded by requested debt, target snapshot debt, per-operation limit, and available cumulative quota.', - declarations: [{ name: 'reserve' }], - preconditions: 'Approval matches local pool, receiver, exact operator, and exact or wildcard target; it is active, unrevoked, non-invalidated, valid through latest execution, and has positive reservable quota.', - signals: '`LiquidationApprovalReserved`', - }, - { - call: '`release(operationId)`', - caller: 'Bound coordinator only', - effect: 'Returns an unsettled delegated reservation to available quota. A missing, self-route, or already settled reservation is a no-op.', - declarations: [{ name: 'release' }], - preconditions: 'Coordinator terminal cleanup path.', - signals: '`LiquidationApprovalReleased` when quota is returned', - }, - { - call: '`consume(operationId, debtMovedAttoEth)`', - caller: 'Bound coordinator only', - effect: 'Permanently consumes exactly moved debt, releases unused reservation, and settles the reservation once.', - declarations: [{ name: 'consume' }], - preconditions: 'For a delegated reservation, it is unsettled and moved debt does not exceed reserved debt. A self route is a no-op.', - signals: '`LiquidationApprovalConsumed`', - }, - ], - }, - { - compiledAbiFingerprint: '60cd10890a685efe179e17e93a783c660542bd6368f86fed87f46de03695b243', - name: 'OpenOraclePriceCoordinator', - purpose: 'Obtains a fresh REP-per-ETH price and coordinates withdrawals, delegated liquidation routing, approval reservations, and terminal cleanup.', - readAbiFingerprint: '288a73d13de5a0f593226105eb11eb177bf085ac2ee708a31645c3d7c4eb7237', - readSurface: - 'Configuration getters are `MAX_PENDING_SETTLEMENT_OPERATIONS`, `OPEN_INTEREST_DIVIDER`, `reputationToken`, `securityPool`, `openOracle`, `weth`, `liquidationApprovalRegistry`, `gasConsumedOpenOracleReportPrice`, `gasConsumedSettlement`, `gasUnitsForOneDispute`, `initialReportPriorityFeeAttoEthPerGas`, `targetPriceErrorForDispute`, `openOracleSecurityMultiplierBps`, `settlementTime`, `disputeDelay`, `protocolFee`, `feePercentage`, `multiplier`, `timeType`, `trackDisputes`, `protocolFeeRecipient`, `escalationHaltMultiplierBps`, `maxSettlementBaseFeeMultiplierBps`, and `minLiquidationPriceDistanceBps`. Current report and operation getters are `pendingReportId`, `pendingReportSponsor`, `pendingOperationSlotId`, `lastSettlementTimestamp`, `lastPrice`, `pendingReportMaxSettlementBaseFeeAttoEthPerGas`, `stagedOperationCounter`, and `stagedOperations`. Use `isPriceValid`, `minimumToken1ReportAttoEth`, `getRequestPriceCostAttoEth`, `getQueuedOperationCostAttoEth`, `getSettlementCallbackGasLimit`, `getPendingOperationSlot`, `getActiveStagedOperationCount`, `getActiveStagedOperations`, `getPendingSettlementOperationCount`, and `getPendingSettlementOperationIds` for derived or paged state.', - securityBoundary: - 'Report and staged-operation liveness depends on [A16 timely inclusion](./security-model.html#assumption-a16), [A17 corrector capability](./security-model.html#assumption-a17), [A18 independent correction incentive](./security-model.html#assumption-a18), [A19 observable correctable price](./security-model.html#assumption-a19), and [A06 lifecycle executors](./security-model.html#assumption-a06). When `lastPrice` is zero, the official client currently needs an offchain market quote to propose the first report; quote availability is a client limitation rather than a protocol security assumption. Proposals copied from a nonzero cached price do not use that quote path.', - readDeclarations: [ - { name: 'isPriceValid' }, - { name: 'minimumToken1ReportAttoEth' }, - { name: 'getRequestPriceCostAttoEth' }, - { name: 'getQueuedOperationCostAttoEth' }, - { name: 'getSettlementCallbackGasLimit' }, - { name: 'getPendingOperationSlot' }, - { name: 'getActiveStagedOperationCount' }, - { name: 'getPendingSettlementOperationCount' }, - { name: 'getPendingSettlementOperationIds' }, - { name: 'getActiveStagedOperations' }, - ], - readStorageDeclarations: [ - { name: 'MAX_PENDING_SETTLEMENT_OPERATIONS' }, - { name: 'OPEN_INTEREST_DIVIDER' }, - { name: 'pendingReportId' }, - { name: 'pendingReportSponsor' }, - { name: 'pendingOperationSlotId' }, - { name: 'lastSettlementTimestamp' }, - { name: 'lastPrice' }, - { name: 'reputationToken' }, - { name: 'securityPool' }, - { name: 'openOracle' }, - { name: 'weth' }, - { name: 'gasConsumedOpenOracleReportPrice' }, - { name: 'gasConsumedSettlement' }, - { name: 'gasUnitsForOneDispute' }, - { name: 'initialReportPriorityFeeAttoEthPerGas' }, - { name: 'targetPriceErrorForDispute' }, - { name: 'openOracleSecurityMultiplierBps' }, - { name: 'settlementTime' }, - { name: 'disputeDelay' }, - { name: 'protocolFee' }, - { name: 'feePercentage' }, - { name: 'multiplier' }, - { name: 'timeType' }, - { name: 'trackDisputes' }, - { name: 'protocolFeeRecipient' }, - { name: 'escalationHaltMultiplierBps' }, - { name: 'maxSettlementBaseFeeMultiplierBps' }, - { name: 'minLiquidationPriceDistanceBps' }, - { name: 'pendingReportMaxSettlementBaseFeeAttoEthPerGas' }, - { name: 'stagedOperationCounter' }, - { name: 'stagedOperations' }, - { name: 'liquidationApprovalRegistry' }, - ], - sourcePath: 'solidity/contracts/peripherals/OpenOraclePriceCoordinator.sol', - interactions: [ - { - call: '`requestPriceIfNeededAndStageLiquidation(targetVault, receiverVault, requestedDebtAttoEth, approvalId, ...)`', - caller: 'Liquidation operator; a delegated receiver must have approved this exact operator', - effect: 'Stages explicit operator, receiver, and target roles and reserves bounded receiver quota before any oracle work. The self-receiving operator path uses a zero approval ID.', - declarations: [{ name: 'requestPriceIfNeededAndStageLiquidation' }], - preconditions: 'Receiver differs from target; delegated approval matches pool, receiver, operator, and target scope, has available cumulative and per-operation quota, and remains valid through latest execution.', - signals: '`LiquidationRouteStaged`; `LiquidationApprovalReserved` on a delegated route; staged-operation lifecycle events', - }, - { - call: '`requestPriceIfNeededAndStageOperation(...)` with funding when stale', - caller: 'Vault owner for self withdrawal; legacy self-receiving liquidation callers remain supported. While a report is pending, only that report sponsor may stage more operations.', - effect: - 'Records the operation, executes immediately with a fresh price, or attaches it to a bounded pending settlement batch and opens a report when required. If unused ETH is positive, the final caller refund uses a low-level callback; rejection rolls back the entire transaction, including any queueing, immediate execution, or newly opened report.', - declarations: [{ name: 'requestPriceIfNeededAndStageOperation' }], - preconditions: - '`securityPool.isEscalationResolved()` is false; valid target, nonzero amount, and timeout from 1 second through 5 minutes. Bounty, buffered report funding, matching REP, and token approvals are required only when this call opens a new report. The caller must accept any positive unused-ETH refund.', - signals: '`StagedOperationQueued`, possibly `PriceRequested`, then `ExecutedStagedOperation`; authoritative `CoordinatorStateCheckpoint` records', - }, - { - call: '`requestPrice(proposedRepPerEthPrice, requestedInitialAttoWeth)` with report funding', - caller: 'Anyone when no fresh price or report is pending', - effect: 'Opens and atomically funds a fresh WETH/REP report without staging a new operation, then refunds any positive excess ETH through a low-level caller callback. Callback rejection rolls back the report and initial position.', - declarations: [{ name: 'requestPrice' }], - preconditions: - 'Cached price stale; no pending report; nonzero proposed REP/ETH price, ETH bounty, and funding and approvals for at least the configured priority report plus the larger of the base-fee and open-interest WETH reports, plus matching REP. Zero requested WETH uses the minimum; a larger request voluntarily increases the initial report. The caller must accept any positive excess-ETH refund.', - signals: '`PriceRequested` and `CoordinatorStateCheckpoint`', - }, - { - call: '`executeStagedOperation(operationId)`', - caller: 'Anyone', - effect: - "Consumes an expired operation and releases its delegated reservation without requiring a valid price. Otherwise, consumes and attempts the active operation using the current fresh price. Price-report funding is independent of the operation's notional; the downstream operation applies its own protocol bounds.", - declarations: [{ name: 'executeStagedOperation' }], - preconditions: 'Operation exists. Expired cleanup requires no valid price; a non-expired operation requires a fresh coordinator price. Lifecycle failures are emitted rather than retried.', - signals: '`ExecutedStagedOperation`, either `LiquidationApprovalConsumed` or `LiquidationApprovalReleased` for a delegated liquidation, and `CoordinatorStateCheckpoint`', - }, - { - call: '`expireStagedOperation(operationId)`', - caller: 'Anyone', - effect: 'Permissionlessly consumes an expired operation and releases its liquidation reservation without requiring a valid oracle price.', - declarations: [{ name: 'expireStagedOperation' }], - preconditions: 'Operation exists and its settlement-plus-validity window has elapsed.', - signals: '`ExecutedStagedOperation`, `LiquidationApprovalReleased` for a delegated liquidation, and `CoordinatorStateCheckpoint`', - }, - { - call: '`recoverSettledPendingReport()`', - caller: 'Anyone', - effect: 'Clears a pending report whose normal callback path did not clear coordinator state, consumes every live operation attached to that report, and releases each delegated-liquidation reservation. Operations that were active but outside the bounded pending callback batch remain active.', - declarations: [{ name: 'recoverSettledPendingReport' }], - preconditions: 'A pending report ID exists and its stored OpenOracle `storedGame(reportId).settlementTimestamp` is nonzero.', - signals: '`PendingReportRecovered`, failed `ExecutedStagedOperation` for each live attached operation, `LiquidationApprovalReleased` for each attached delegated liquidation, and `CoordinatorStateCheckpoint`', - }, - { - call: '`openOracleCallback(...)`', - caller: 'Configured `OpenOracle` only', - effect: 'A valid settlement updates the price and auto-executes the bounded pending batch. A terminally rejected settlement consumes the pending batch and releases every liquidation reservation.', - declarations: [{ name: 'openOracleCallback' }], - preconditions: 'Callback report matches the pending report; excessive settlement basefee, a saturated `uint24` report counter, an uneconomic final history record at its recorded base fee plus configured priority fee, or zero values reject the price after clearing pending report state.', - signals: '`PriceReported` or `PriceReportRejected`; operation execution events; authoritative `CoordinatorStateCheckpoint` records', - }, - { - call: '`setLiquidationApprovalRegistry(registry)`', - caller: 'Coordinator deployment factory only', - effect: 'Binds the coordinator-local approval registry once.', - declarations: [{ name: 'setLiquidationApprovalRegistry' }], - preconditions: 'Registry is nonzero and no registry was previously installed.', - signals: 'No event; deterministic factory deployment and the public getter identify the registry.', - }, - { - call: '`setSecurityPool(pool)`', - caller: 'Anyone while `securityPool` remains zero; normal factory deployment calls atomically', - effect: 'A nonzero value binds the pool permanently. A zero value emits and checkpoints zero but leaves the setter callable. Normal factory deployment supplies the nonzero canonical pool before returning the coordinator.', - declarations: [{ name: 'setSecurityPool' }], - preconditions: 'Current `securityPool` is zero; the argument itself is not required to be nonzero.', - signals: '`SecurityPoolSet` and `CoordinatorStateCheckpoint`', - }, - { - call: '`setRepEthPrice(price)`', - caller: 'Configured nonzero `SecurityPool` only', - effect: "Seeds the coordinator's price value, including zero, for inherited child state.", - declarations: [{ name: 'setRepEthPrice' }], - preconditions: 'Caller equals the configured pool.', - signals: '`RepEthPriceSet` and `CoordinatorStateCheckpoint`', - }, - ], - }, - { - compiledAbiFingerprint: 'b4d43db4a275c3118a700ca255a7f63d42dfdca1fb1e7c554d681e589a76ac85', - name: 'ShareToken', - purpose: "Stores universe-aware ERC-1155 outcome shares and materializes a holder's persistent source entitlement in selected fork branches.", - readAbiFingerprint: '6093653de73a0e5fa1e400d77bbded71a92de1197f58bd89da82a657887f349e', - readSurface: - 'Base and relationship getters are `name`, `symbol`, `zoltar`, `canonicalPoolByUniverse`, `_balances`, `_supplies`, and `_operatorApprovals`. Standard ERC-1155 reads are `supportsInterface`, `balanceOf`, `totalSupply`, `balanceOfBatch`, and `isApprovedForAll`; protocol-specific reads are `isAuthorized`, `totalSupplyForOutcome`, `maximumOutcomeSupply`, `balanceOfOutcome`, `balanceOfShares`, `getMigratedShareAmountAttoShares`, `getTokenId`, `getTokenIds`, and `unpackTokenId`.', - readDeclarations: [ - { name: 'supportsInterface', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: 'balanceOf', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: 'totalSupply', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: 'balanceOfBatch', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: 'isApprovedForAll', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: 'isAuthorized' }, - { name: 'totalSupplyForOutcome' }, - { name: 'maximumOutcomeSupply' }, - { name: 'balanceOfOutcome' }, - { name: 'balanceOfShares' }, - { name: 'getMigratedShareAmountAttoShares' }, - { name: 'getTokenId' }, - { name: 'getTokenIds' }, - { name: 'unpackTokenId' }, - ], - readStorageDeclarations: [ - { name: 'name' }, - { name: 'symbol' }, - { name: 'zoltar' }, - { name: 'canonicalPoolByUniverse' }, - { name: '_balances', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: '_supplies', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - { name: '_operatorApprovals', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }, - ], - sourcePath: 'solidity/contracts/peripherals/tokens/ShareToken.sol', - interactions: [ - { - call: '`setApprovalForAll(operator, approved)`', - caller: 'Any token account setting its own operator approval', - effect: "Sets or clears the operator's authority over all of the caller's outcome-token balances.", - declarations: [{ name: 'setApprovalForAll', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], - preconditions: 'The operator differs from the caller.', - signals: '`ApprovalForAll`', - }, - { - call: 'Both `safeTransferFrom(...)` overloads', - caller: 'Share holder or approved ERC-1155 operator', - effect: 'Transfers one outcome-token balance without changing supply.', - declarations: [{ name: 'safeTransferFrom', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], - preconditions: - 'Caller holds the source balance or has operator approval; the source account has not materialized that token into any child branch; destination is nonzero; the source balance is sufficient; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract recipient accepts the ERC-1155 callback.', - signals: '`TransferSingle`', - }, - { - call: 'Both `safeBatchTransferFrom(...)` overloads', - caller: 'Share holder or approved ERC-1155 operator for a nonempty batch; any caller for an empty batch', - effect: 'A nonempty batch transfers each listed outcome-token balance without changing supply. Equal empty ID and value arrays return as a no-op without an event.', - declarations: [{ name: 'safeBatchTransferFrom', sourcePath: 'solidity/contracts/peripherals/tokens/ERC1155.sol' }], - preconditions: - 'ID and value array lengths match. A nonempty batch also requires holder or operator authority, no listed source token that the source account has already materialized into a child branch, a nonzero destination, sufficient source balances, and, under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), an accepting ERC-1155 callback from a contract recipient; the empty-batch no-op performs none of those checks.', - signals: '`TransferBatch` for a nonempty batch; no event for an empty batch', - }, - { - call: '`migrate(fromId, targetOutcomeIndexes)`', - caller: 'Holder of the source token ID', - effect: - "If needed, first freezes the operational source pool and records its fork snapshot. A single-target call may lazily create that child while the branch-creation window is open. It keeps and locks the holder's source entitlement, then mints each selected child-universe token ID up to the current source balance. Later source additions materialize only the unminted delta. A contract holder receives the ERC-1155 single-receiver callback for each mint; rejection rolls back the mint and preceding fork or child setup.", - declarations: [{ name: 'migrate' }], - preconditions: - 'Source universe forked; canonical source pool is `Operational` or `PoolForked`, and an `Operational` source has no inherited fixed outcome because auto-fork activation rejects one; positive source balance; nonempty, strictly increasing, well-formed outcomes; every target in a multi-target call already has a canonical child pool; after the branch-creation window, a single target must also already exist; at least one selected child has an unmaterialized balance; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract holder accepts `onERC1155Received` for every target mint.', - signals: - '`PoolForkModeActivated`, `PoolAccountingCheckpoint`, `SecurityPoolForkSnapshot`, `ParentRepLocked`, and optionally `DisputeStakedRepDrainedAtFork` when auto-forking; `SecurityPoolRegistered`, `DeploySecurityPool`, `AuthorizationUpdated`, and `ChildPoolLinked` when lazily deploying, plus `DeployChild`, `ChildRepSplit`, `PoolHeldRepSweptToChild`, `EscalationGameSet`, `GameContinuedFromFork`, `ForkCarryCheckpoint`, and `ChildDisputeStakedRepMaterialized` as applicable; then one ERC-1155 mint `TransferSingle` and `Migrate` per materialized target on successful callbacks', - }, - { - call: '`authorize(securityPoolCandidate)`', - caller: 'Initially authorized `SecurityPoolFactory` for an origin pool; an authorized parent `SecurityPool` for a child pool', - effect: 'Establishes the candidate as `canonicalPoolByUniverse` for its universe and adds it to the set allowed to mint, burn, and authorize descendants. Reauthorizing the same candidate is a no-op.', - declarations: [{ name: 'authorize' }], - preconditions: 'Caller is already authorized; the candidate reports this exact share token; its universe has no different canonical pool.', - signals: '`AuthorizationUpdated` on first authorization; no event when the same candidate is already authorized', - }, - { - call: '`mintCompleteSets(universeId, account, amountAttoShares)`', - caller: 'An authorized `SecurityPool`', - effect: "Mints `amount` each of Invalid, Yes, and No to `account`, then invokes its ERC-1155 batch-receiver callback when it is a contract. Rejection rolls back the mint and the authorized pool's surrounding transaction.", - declarations: [{ name: 'mintCompleteSets' }], - preconditions: 'Caller is authorized; `account` is nonzero; `amount` is positive; under [A22 asset-recipient compatibility](./security-model.html#assumption-a22), a contract account accepts `onERC1155BatchReceived`.', - signals: '`TransferBatch` on a successful callback', - }, - { - call: '`burnCompleteSets(universeId, account, amountAttoShares)`', - caller: 'An authorized `SecurityPool`', - effect: 'Burns `amount` each of Invalid, Yes, and No from `account`; global outcome supplies may differ.', - declarations: [{ name: 'burnCompleteSets' }], - preconditions: 'Caller is authorized; `account` is nonzero and has at least `amount` of every outcome.', - signals: '`TransferBatch`', - }, - { - call: '`burnTokenIdAndGetRemainingSupply(tokenId, account)`', - caller: 'An authorized `SecurityPool`', - effect: "Burns `account`'s full balance of `tokenId` and returns the burned amount and that token ID's remaining supply.", - declarations: [{ name: 'burnTokenIdAndGetRemainingSupply' }], - preconditions: '`account` is nonzero; caller is authorized.', - signals: '`TransferSingle`, including when the burned balance is zero', - }, - ], - }, - { - compiledAbiFingerprint: '0f7cbe10566e33d0de1300b8613ef64fff72b11845bff8c4f2aa470d1ee1eb16', - name: 'UniformPriceDualCapBatchAuction', - purpose: - 'Collects ETH bids under ETH-raise and REP-sale caps, computes one clearing result, and supports paged settlement. AVL, cumulative-allocation, and refund-prefix mechanics live in [UniformPriceDualCapBatchAuctionStorage](../../solidity/contracts/peripherals/UniformPriceDualCapBatchAuctionStorage.sol), an internal storage library.', - readAbiFingerprint: 'e4ad6ab91244711a2008716cfbdf62b6237d39321eefa984a4fdc7856267b8bc', - readSurface: - 'Auction summary getters are `maxAttoRepBeingSold`, `attoEthRaiseCap`, `finalized`, `clearingTick`, `ethFilledAtClearingAttoEth`, `attoEthRaised`, `totalAttoRepPurchased`, `auctionStarted`, `minBidSizeAttoEth`, `owner`, `underfunded`, `underfundedThreshold`, `underfundedWinningAttoEth`, and `activeTickCount`. `pendingEthRefundsAttoEth` reports ETH whose gas-bounded push failed during settlement and can still be pulled. Use `computeClearing`, `previewFinalization`, `tickToPrice`, `getTickSummary`, `getTickCount`, `getTickPage`, `getActiveTickPage`, `getBidCountAtTick`, `getBidPageAtTick`, `getBidderBidCount`, and `getBidderBidPage` before finalizing or submitting settlement indexes.', - readDeclarations: [ - { name: 'computeClearing' }, - { name: 'previewFinalization' }, - { name: 'tickToPrice' }, - { name: 'getTickSummary' }, - { name: 'getTickCount' }, - { name: 'getTickPage' }, - { name: 'getActiveTickPage' }, - { name: 'getBidCountAtTick' }, - { name: 'getBidPageAtTick' }, - { name: 'getBidderBidCount' }, - { name: 'getBidderBidPage' }, - ], - readStorageDeclarations: [ - { name: 'maxAttoRepBeingSold' }, - { name: 'attoEthRaiseCap' }, - { name: 'finalized' }, - { name: 'clearingTick' }, - { name: 'ethFilledAtClearingAttoEth' }, - { name: 'attoEthRaised' }, - { name: 'totalAttoRepPurchased' }, - { name: 'auctionStarted' }, - { name: 'minBidSizeAttoEth' }, - { name: 'owner' }, - { name: 'underfunded' }, - { name: 'underfundedThreshold' }, - { name: 'underfundedWinningAttoEth' }, - { name: 'activeTickCount' }, - { name: 'pendingEthRefundsAttoEth' }, - ], - sourcePath: 'solidity/contracts/peripherals/UniformPriceDualCapBatchAuction.sol', - interactions: [ - { - call: '`startAuction(attoEthRaiseCap, maxAttoRepBeingSold)`', - caller: 'Auction owner (`SecurityPoolForker`) only', - effect: 'Starts the one-week auction and fixes its two caps and minimum bid.', - declarations: [{ name: 'startAuction' }], - preconditions: 'Auction not previously started; both caps are positive; the REP cap does not exceed 11 million REP; the ETH cap fits in `uint128`; the block timestamp fits in `uint48`.', - signals: '`AuctionStarted`', - }, - { - call: '`submitBid(tick)` with ETH', - caller: 'Any bidder', - effect: "Adds ETH demand at the selected positive-price tick while extending that tick's append-only cumulative bid and refund history, including when a fully refunded tick becomes active again.", - declarations: [{ name: 'submitBid' }], - preconditions: 'Auction active and unfinalized; before one-week deadline; bid meets `minBidSizeAttoEth`; tick maps to nonzero price; the individual bid and the resulting cumulative ETH at that tick each fit in `uint128`.', - signals: '`BidSubmitted`', - }, - { - call: '`refundLosingBids(tickIndices)`', - caller: 'Bidder for its own bids', - declarations: [{ name: 'refundLosingBids' }], - effect: - "A nonempty list marks the caller's bids already provably below the current clearing tick and attempts an immediate gas-bounded ETH refund. Rejected, reverted, or gas-exhausted pushes are recorded in `pendingEthRefundsAttoEth` without restoring the bid. An empty list changes no bids and makes no external call.", - preconditions: 'Auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to the caller and are strictly losing and unrefunded.', - signals: '`BidSettled` per refunded bid; `EthRefundDeferred` when a positive push fails', - }, - { - call: '`refundLosingBidsFor(bidder, tickIndices)`', - caller: 'Auction owner (`SecurityPoolForker`) only; public callers use `settleAuctionBids`', - declarations: [{ name: 'refundLosingBidsFor' }], - effect: - "A nonempty list marks and attempts a gas-bounded refund of a named bidder's bids already provably below the current clearing tick. Rejected, reverted, or gas-exhausted pushes are recorded in `pendingEthRefundsAttoEth` without restoring the bid. An empty list changes no bids and makes no external call.", - preconditions: 'Named bidder is nonzero; auction started and unfinalized; auction has reached a clearing price. Nonempty indexes additionally belong to that bidder and are strictly losing and unrefunded.', - signals: '`BidSettled` per refunded bid; `EthRefundDeferred` when a positive push fails', - }, - { - call: '`finalize()`', - caller: 'Auction owner (`SecurityPoolForker`) only; users reach it through `finalizeTruthAuction`', - effect: 'Fixes the clearing mode, clearing tick, ETH totals, and aggregate REP allocation, then calls the owner with the resulting proceeds, including when zero. A rejected call reverts finalization and its event.', - declarations: [{ name: 'finalize' }], - preconditions: 'Auction started, not finalized, and one-week deadline reached; owner accepts the proceeds ETH call, including zero value.', - signals: '`AuctionFinalized`', - }, - { - call: '`withdrawBids(withdrawFor, tickIndices, proRataTotal)`', - caller: 'Auction owner only', - effect: - 'For a nonempty list, returns refunds, purchased REP, and a companion pro-rata allocation for the selected beneficiary bids so the forker can credit REP backing units and capacity ownership. Withdrawal-time allocation assigns division dust from deterministic cumulative ETH positions, making each payout independent of claim order. A rejected, reverted, or gas-exhausted positive refund push is gas-bounded and deferred rather than reverting or starving the REP and capacity-ownership settlement. An empty list returns three zeros without changing bids, emitting events, or calling the beneficiary.', - declarations: [{ name: 'withdrawBids' }], - preconditions: 'Auction finalized; caller is owner. Nonempty indexes belong to `withdrawFor` and remain unsettled.', - signals: '`BidSettled` per processed bid; `EthRefundDeferred` when a positive push fails', - }, - { - call: '`withdrawPendingEthRefund()`', - caller: 'Bidder with deferred ETH', - effect: "Clears the caller's complete deferred refund and emits its withdrawal before transferring without the push-refund gas cap, so callback-created deferrals follow the clear in log order. A rejected pull reverts the transfer, clear, and event.", - declarations: [{ name: 'withdrawPendingEthRefund' }], - preconditions: 'Caller has a positive `pendingEthRefundsAttoEth` balance and currently accepts ETH.', - signals: '`PendingEthRefundWithdrawn`', - }, - ], - }, -] - const content = await generateReferenceContent() assert.match(content, /^/, 'contract reference must identify its canonical generator') const html = await renderReferencePage('Contract interactions', content, outputPath) diff --git a/shared/ts/ethereum.test.ts b/shared/ts/ethereum.test.ts index a1d51b37c..6cfcfe24c 100644 --- a/shared/ts/ethereum.test.ts +++ b/shared/ts/ethereum.test.ts @@ -33,6 +33,7 @@ import { recoverTransactionAddress, toHex, type EIP1193Provider, + type BlockTransaction, type Hash, type Hex, } from '@zoltar/shared/ethereum' @@ -965,6 +966,82 @@ describe('shared ethereum compatibility layer', () => { expect(calls.map(call => call.method)).toEqual(['eth_getTransactionByHash', 'eth_getTransactionReceipt', 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getTransactionReceipt']) }) + test('waitForTransactionReceipt uses a supplied transaction when its hash is no longer available', async () => { + const originalHash = `0x${'77'.repeat(32)}` satisfies Hash + const replacementHash = `0x${'78'.repeat(32)}` satisfies Hash + const replacements: Hash[] = [] + const calls: { method: string; params: unknown }[] = [] + const originalTransaction = { + from: getAddress(OWNER_ADDRESS), + gas: 21_000n, + hash: originalHash, + input: '0x1234', + nonce: 7n, + to: getAddress(RECIPIENT_ADDRESS), + type: '0x2', + value: 5n, + } satisfies BlockTransaction + const provider = createProvider(({ method, params }) => { + if (method === 'eth_getTransactionByHash') return null + if (method === 'eth_getTransactionReceipt') { + const hash = getArrayEntry(params, 0, 'receipt params') + if (hash === originalHash) return null + if (hash === replacementHash) { + return { + blockHash: BLOCK_HASH, + blockNumber: '0x0', + cumulativeGasUsed: '0x5208', + effectiveGasPrice: '0x9', + from: OWNER_ADDRESS, + gasUsed: '0x5208', + logs: [], + status: '0x1', + to: RECIPIENT_ADDRESS, + transactionHash: replacementHash, + transactionIndex: '0x0', + type: '0x2', + } + } + } + if (method === 'eth_blockNumber') return '0x0' + if (method === 'eth_getBlockByNumber') { + return { + hash: BLOCK_HASH, + number: '0x0', + parentHash: `0x${'44'.repeat(32)}`, + timestamp: '0x5', + transactions: [ + { + from: OWNER_ADDRESS, + gas: '0x5208', + hash: replacementHash, + input: '0x1234', + nonce: '0x7', + to: RECIPIENT_ADDRESS, + transactionIndex: '0x0', + type: '0x2', + value: '0x5', + }, + ], + } + } + throw new Error(`Unexpected rpc method: ${method}`) + }, calls) + const client = createPublicClient({ chain: mainnet, transport: custom(provider) }) + + const receipt = await client.waitForTransactionReceipt({ + hash: originalHash, + onReplaced: replacement => replacements.push(replacement.transaction.hash), + pollingInterval: 0, + transaction: originalTransaction, + timeout: 20, + }) + + expect(receipt.transactionHash).toBe(replacementHash) + expect(replacements).toEqual([replacementHash]) + expect(calls.map(call => call.method)).toEqual(['eth_getTransactionReceipt', 'eth_blockNumber', 'eth_getBlockByNumber', 'eth_getTransactionReceipt']) + }) + test('public client rejects malformed fixed-width rpc hashes', async () => { const calls: { method: string; params: unknown }[] = [] const provider = createProvider(({ method }) => { diff --git a/shared/ts/ethereum.ts b/shared/ts/ethereum.ts index d71ae28bd..d2d3b5b69 100644 --- a/shared/ts/ethereum.ts +++ b/shared/ts/ethereum.ts @@ -1,6 +1,6 @@ import { keccak_256 } from '@noble/hashes/sha3.js' import { bytesToHex as nobleBytesToHex, concatBytes, hexToBytes as nobleHexToBytes, utf8ToBytes } from '@noble/hashes/utils.js' -import { addr, amounts, Transaction as MicroTransaction } from 'micro-eth-signer' +import { addr, amounts, eip191Signer, Transaction as MicroTransaction } from 'micro-eth-signer' import { Decoder, createContract, deployContract, events } from 'micro-eth-signer/advanced/abi.js' export type Hex = `0x${string}` @@ -276,10 +276,11 @@ export type TransactionReplacement = { transactionReceipt: TransactionReceipt } -type WaitForTransactionReceiptParameters = { +export type WaitForTransactionReceiptParameters = { hash: Hash onReplaced?: ((replacement: TransactionReplacement) => void) | undefined pollingInterval?: number | undefined + transaction?: BlockTransaction | undefined timeout?: number | undefined } @@ -318,6 +319,7 @@ export type RpcLog = Transa export type Account = { address: Address + signMessage?: (message: string | Uint8Array) => Promise signTransaction?: (parameters: SignTransactionParameters) => Promise type: 'json-rpc' | 'local' | string } @@ -349,6 +351,7 @@ export type ParsedTransaction = { export type RpcRequestScheduler = (method: string, operation: () => Promise) => Promise export type RpcFetchFn = (input: string | URL | Request, init?: RequestInit | undefined) => Promise +export type RpcResponseParser = (response: Response, method: string) => Promise type TransportRetryOptions = { batch?: unknown @@ -360,6 +363,7 @@ type TransportRetryOptions = { export type HttpTransportOptions = TransportRetryOptions & { fetchFn?: RpcFetchFn | undefined requestTimeout?: number | undefined + responseParser?: RpcResponseParser | undefined } type TypedTransport = @@ -375,6 +379,7 @@ type TypedTransport = fetchFn?: RpcFetchFn | undefined requestTimeout: number requestScheduler?: RpcRequestScheduler | undefined + responseParser?: RpcResponseParser | undefined retryCount: number retryDelay: number url: string @@ -450,7 +455,7 @@ type PublicClientShape Promise getBytecode: (parameters: { address: Address; blockNumber?: bigint | undefined; blockTag?: BlockTag | undefined }) => Promise getGasPrice: () => Promise - getTransactionCount: (parameters: { address: Address; blockTag?: BlockTag | undefined }) => Promise + getTransactionCount: (parameters: { address: Address; blockNumber?: bigint | undefined; blockTag?: BlockTag | undefined }) => Promise getLogs: (parameters: { address?: Address | readonly Address[] | undefined args?: Readonly> | undefined @@ -1135,7 +1140,7 @@ async function requestTransportOnce(transport: Transport, parameters: Cl }) } - const payload: unknown = await response.json() + const payload: unknown = transport.responseParser === undefined ? await response.json() : await transport.responseParser(response, parameters.method) if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new RpcError(`Malformed JSON-RPC response while calling ${parameters.method}`) const envelope = payload as Record const hasResult = Object.prototype.hasOwnProperty.call(envelope, 'result') @@ -1186,6 +1191,10 @@ async function requestTransport(transport: Transport, parameters: Client }) } +export async function requestRpc(transport: Transport, parameters: { method: string; params?: unknown }) { + return await requestTransport(transport, parameters) +} + function toRpcError(error: unknown, fallbackMessage: string) { if (error instanceof RpcError) return error if (typeof error === 'object' && error !== null) { @@ -1470,7 +1479,7 @@ function buildPublicClientActions(transport, { method: 'eth_getTransactionCount', - params: [getAddress(parameters.address), parameters.blockTag ?? 'latest'], + params: [getAddress(parameters.address), parameters.blockNumber === undefined ? (parameters.blockTag ?? 'latest') : hexQuantity(parameters.blockNumber)], }), ), getLogs: async (parameters: { address?: Address | readonly Address[] | undefined; args?: Readonly> | undefined; event?: TEvent; fromBlock?: bigint | undefined; toBlock?: bigint | undefined; topics?: readonly LogTopicFilter[] | undefined }) => { @@ -1613,9 +1622,9 @@ function buildPublicClientActions @@ -1883,7 +1892,12 @@ function normalizeTransportRetryOptions(options: TransportRetryOptions = {}) { function normalizeHttpTransportOptions(options: HttpTransportOptions = {}) { const requestTimeout = options.requestTimeout ?? 30_000 if (!Number.isSafeInteger(requestTimeout) || requestTimeout < 1) throw new Error('RPC request timeout must be a positive safe integer') - return { ...(options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn }), ...normalizeTransportRetryOptions(options), requestTimeout } + return { + ...(options.fetchFn === undefined ? {} : { fetchFn: options.fetchFn }), + ...(options.responseParser === undefined ? {} : { responseParser: options.responseParser }), + ...normalizeTransportRetryOptions(options), + requestTimeout, + } } export function http(url: string, options?: HttpTransportOptions) { @@ -2060,6 +2074,9 @@ export function decodeEventLog(parameters: { abi: Abi; data: Hex; topics: readon } } +type EncodedEventTopic = TArgs extends readonly unknown[] ? (Extract extends never ? Hex : Hex | readonly Hex[]) : TArgs extends Readonly> ? (Extract extends never ? Hex : Hex | readonly Hex[]) : Hex + +export function encodeEventTopics | undefined = undefined>(parameters: { abi: Abi; args?: TArgs; eventName: string }): readonly (EncodedEventTopic | null)[] export function encodeEventTopics(parameters: { abi: Abi; args?: readonly unknown[] | Record | undefined; eventName: string }): readonly (Hex | readonly Hex[] | null)[] { const eventAbi = getNamedEventAbi(parameters.abi, parameters.eventName) const decoder = getEventDecoder(eventAbi) @@ -2121,6 +2138,7 @@ export async function recoverTransactionAddress(parameters: { serializedTransact export function privateKeyToAccount(privateKey: Hex) { return { address: getAddress(addr.fromPrivateKey(privateKey)), + signMessage: async message => ensure0x(eip191Signer.sign(message, privateKey)), signTransaction: async parameters => { const type = parameters.gasPrice !== undefined ? 'legacy' : 'eip1559' const transaction = MicroTransaction.prepare({ diff --git a/trading/ui/ts/features/LiveTrading.tsx b/trading/ui/ts/features/LiveTrading.tsx index 3c0e18871..ae1f26000 100644 --- a/trading/ui/ts/features/LiveTrading.tsx +++ b/trading/ui/ts/features/LiveTrading.tsx @@ -1,6 +1,6 @@ -import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'preact/hooks' +import { useEffect, useId, useMemo, useRef, useState } from 'preact/hooks' import type { Address, Hash, PublicClient, WalletClient } from '@zoltar/shared/ethereum' -import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatMintingCapacity, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnits, parseUnitsOrUndefined, shortAddress } from '../app/format.ts' +import { bigintToSafeNumber, formatBpsMultiplier, formatCapacityOwnership, formatEthPerShare, formatMintingCapacity, formatOutcomeAmount, formatShareAmount, formatUnits, parseUnitsOrUndefined, shortAddress } from '../app/format.ts' import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../app/latestRequest.ts' import { AddressValue, SecurityPoolAddressLink, Status } from '../components/Status.tsx' import { ProbabilityBar } from '../components/ProbabilityBar.tsx' @@ -10,60 +10,60 @@ import { loadForkMigrationContext, type ForkMigrationContext, type ForkTarget } import { approveLpRouter, approveRouter, - connectWallet, - createSecurityPoolDeploymentIndex, createTradingPublicClient, - createTradingWalletClient, - discoverAllLiveMarketsInUniverse, - discoverLiveUniverseMarketPage, - loadLiveBalances, - loadWalletHeaderBalances, - liveBalancesForMarket, marketAcceptsNewRisk, marketNewRiskBlocker, - mapWithConcurrency, publicErrorMessage, settlementAvailability, shareBalanceScope, - simulateEntry, - simulateExit, simulateLiquidity, simulateSettlement, - submitFreshEntry, - submitFreshExit, submitFreshLiquidity, submitFreshSettlement, - switchWalletChain, - validateLiveDeployment, - walletChainId, type LiquidityOperation, type LiveBalances, type LiveMarket, type MarketLifecycle, type SettlementOperation, - type SecurityPoolDeployment, type ShareOutcome, } from '../protocol/live.ts' -import { getInjectedEthereum, subscribeToWalletContextChanges, type InjectedEthereum, type WalletContextChangeEvent } from '../protocol/injected.ts' import { maximumInsuredExit } from '../../../ts/sdk/positions.ts' - -type EntryQuote = Awaited> -type ExitQuote = Awaited> -type QuoteContext = Readonly<{ account: Address; configuration: DeploymentConfiguration; walletClient: WalletClient }> -type Quote = (Readonly<{ kind: 'entry'; value: EntryQuote }> | Readonly<{ kind: 'exit'; value: ExitQuote }>) & QuoteContext -type TransactionState = 'idle' | 'simulating' | 'ready' | 'preparing' | 'approval' | 'approval-pending' | 'approval-confirmed' | 'submitting' | 'pending' | 'confirmed' | 'error' -type BalanceState = 'disconnected' | 'loading' | 'ready' | 'error' -export type PortfolioBalanceEntry = Readonly<{ market: LiveMarket; balances: LiveBalances | undefined; error: string | undefined }> -export type WalletSummaryState = Readonly<{ - account: Address | undefined - ethAttoEth: bigint | undefined - repAttoRep: bigint | undefined - status: 'disconnected' | 'loading' | 'ready' | 'error' - error: string | undefined - errorLabel: string | undefined - universeId: string | undefined -}> -type GuardedWalletWrite = (write: () => Promise) => Promise +import { + approvalFailureTransition, + broadcastUncertainMessage, + failedSubmissionTransition, + livePairInitialized, + observeKnownReceipt, + parseSlippageBps, + parseTransactionValidityMinutes, + positionControlsWorkflowLocked, + useLiveTradingController, + type BalanceState, + type GuardedWalletWrite, + type PortfolioBalanceEntry, + type QuoteContext, + type TransactionState, + type WalletSummaryState, +} from './liveTradingController.ts' + +export { + approvalFailureTransition, + broadcastUncertainMessage, + discoveryCommitAllowed, + failedSubmissionTransition, + filterMarketsByUniverse, + livePairInitialized, + marketSelectionAfterDiscovery, + observeKnownReceipt, + parseSlippageBps, + parseTransactionValidityMinutes, + positionControlsWorkflowLocked, + securityPoolAddressFromRoute, + walletSummaryAvailability, + walletSummaryDiscoveryRetryStart, + walletSummaryRefreshState, +} from './liveTradingController.ts' +export type { PortfolioBalanceEntry, WalletSummaryState } from './liveTradingController.ts' type LiveSettlementServices = Readonly<{ createPublicClient(configuration: DeploymentConfiguration): PublicClient @@ -81,28 +81,6 @@ const liveSettlementServices: LiveSettlementServices = { const ignoreWalletSummaryChange = () => undefined -export function walletSummaryRefreshState(account: Address | undefined, universeId: string | undefined): WalletSummaryState { - return { account, ethAttoEth: undefined, repAttoRep: undefined, status: account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId } -} - -export async function observeKnownReceipt(receipt: Promise, onKnownReceipt: () => void): Promise { - const knownReceipt = await receipt - onKnownReceipt() - return knownReceipt -} - -export function walletSummaryDiscoveryRetryStart(discoveryState: 'loading' | 'ready' | 'error', selectedPoolAvailable: boolean, selectedPoolLoadError: string | undefined, currentPageStart: bigint) { - return discoveryState === 'error' || !selectedPoolAvailable || selectedPoolLoadError !== undefined ? currentPageStart : undefined -} - -export function walletSummaryAvailability(configurationAvailable: boolean, configurationError: string | undefined, discoveryState: 'loading' | 'ready' | 'error', discoveryError: string | undefined, selectedPoolAvailable: boolean) { - if (!configurationAvailable) return configurationError === undefined ? { status: 'loading' as const, error: undefined, errorLabel: undefined } : { status: 'error' as const, error: configurationError, errorLabel: 'Deployment unavailable' } - if (discoveryState === 'loading') return { status: 'loading' as const, error: undefined, errorLabel: undefined } - if (discoveryState === 'error') return { status: 'error' as const, error: `SecurityPool discovery failed: ${discoveryError ?? 'unknown discovery error'}`, errorLabel: 'SecurityPool discovery failed' } - if (selectedPoolAvailable) return undefined - return { status: 'error' as const, error: 'No SecurityPool is available in the selected universe', errorLabel: 'No SecurityPool in this universe' } -} - function statusLabel(market: LiveMarket, nowSeconds: bigint) { if (market.loadError !== undefined) return 'Market data unavailable' const blocker = marketNewRiskBlocker(market, nowSeconds) @@ -199,17 +177,6 @@ function renderLiveTradeSummary(quote: Quote, side: 'YES' | 'NO') { const DEFAULT_SLIPPAGE_PERCENT = '0.5' const DEFAULT_TRANSACTION_VALIDITY_MINUTES = '20' -export function parseSlippageBps(value: string) { - const parsed = parseUnitsOrUndefined(value, 2) - return parsed !== undefined && parsed >= 0n && parsed <= 500n ? parsed : undefined -} - -export function parseTransactionValidityMinutes(value: string) { - if (!/^\d+$/.test(value)) return undefined - const parsed = BigInt(value) - return parsed >= 1n && parsed <= 1_440n ? parsed : undefined -} - export function ExecutionProtectionFields({ slippage, validityMinutes, disabled, onSlippageInput, onValidityInput }: { slippage: string; validityMinutes: string; disabled: boolean; onSlippageInput(value: string): void; onValidityInput(value: string): void }) { const slippageBps = parseSlippageBps(slippage) const parsedValidityMinutes = parseTransactionValidityMinutes(validityMinutes) @@ -250,35 +217,6 @@ export function ExecutionProtectionFields({ slippage, validityMinutes, disabled, ) } -export function failedSubmissionTransition(caught: unknown, fallback: string) { - return { quote: undefined, state: 'error' as const, message: publicErrorMessage(caught, fallback) } -} - -export function broadcastUncertainMessage(label: string, hash: Hash) { - return `${label} ${hash} was broadcast, but its receipt could not be confirmed. Do not resubmit. Check this hash in your wallet or configured block explorer, then reload only after its final status is known.` -} - -export function approvalFailureTransition(label: string, broadcastHash: Hash | undefined, receiptKnown: boolean, caught: unknown, fallback: string) { - if (broadcastHash !== undefined && !receiptKnown) return { keepLocked: true, state: 'pending' as const, message: undefined, warning: broadcastUncertainMessage(label, broadcastHash) } - return { keepLocked: false, state: 'error' as const, message: publicErrorMessage(caught, fallback), warning: undefined } -} - -export function positionControlsWorkflowLocked(state: TransactionState, receiptWarning: string | undefined) { - return state === 'preparing' || state === 'approval' || state === 'approval-pending' || state === 'submitting' || state === 'pending' || receiptWarning !== undefined -} - -type WorkflowOwner = 'position' | 'liquidity' - -export function discoveryCommitAllowed(owner: WorkflowOwner | undefined, positionLocked: boolean, liquidityLocked: boolean) { - if (owner === 'position') return !liquidityLocked - if (owner === 'liquidity') return !positionLocked - return !positionLocked && !liquidityLocked -} - -function configuredClient(configuration: DeploymentConfiguration) { - return createTradingPublicClient(configuration) -} - function BalanceLoadError({ message, retry, disabled = false }: { message: string; retry(): Promise; disabled?: boolean }) { return (
@@ -322,20 +260,6 @@ function SecurityPoolIdentityRows({ market }: { market: Pick) { - return market.pair !== undefined && market.lpTotalSupply > 0n && market.yesReserve > 0n && market.noReserve > 0n && market.tradingStatus !== 6 -} - -export function marketSelectionAfterDiscovery(markets: readonly Pick[], currentPool: Address | undefined, preserveCurrentPage: boolean) { - if (preserveCurrentPage && markets.some(market => market.pool === currentPool)) return currentPool - return markets[0]?.pool -} - function PairInitializationAction({ market }: { market: LiveMarket }) { const blocker = marketNewRiskBlocker(market, BigInt(Math.floor(Date.now() / 1_000))) if (blocker !== undefined) @@ -509,21 +433,6 @@ export function SecurityPoolRouteEmptyState({ discoveryState, discoveryError, wo ) } -export function filterMarketsByUniverse(markets: readonly LiveMarket[], selectedUniverseId: string | undefined) { - if (selectedUniverseId === undefined) return [] - return markets.filter(market => market.universeId.toString() === selectedUniverseId) -} - -function parsedUniverseId(selectedUniverseId: string | undefined) { - if (selectedUniverseId === undefined) return undefined - try { - return BigInt(selectedUniverseId) - } catch (error) { - if (error instanceof SyntaxError) return undefined - throw error - } -} - export function LiveTrading({ route, configuration, @@ -545,837 +454,23 @@ export function LiveTrading({ walletSummaryRetryNonce?: number onDeploymentRetry?(): void }) { - const [markets, setMarkets] = useState([]) - const [selectedPool, setSelectedPool] = useState
() - const [account, setAccount] = useState
() - const accountRef = useRef(account) - accountRef.current = account - const [walletClient, setWalletClient] = useState() - const [walletProvider, setWalletProvider] = useState() - const [walletContextInvalidated, setWalletContextInvalidated] = useState(false) - const [walletSummaryStatus, setWalletSummaryStatus] = useState('disconnected') - const [walletEthAttoEth, setWalletEthAttoEth] = useState() - const [walletRepAttoRep, setWalletRepAttoRep] = useState() - const [walletSummaryError, setWalletSummaryError] = useState() - const [walletSummaryErrorLabel, setWalletSummaryErrorLabel] = useState() - const [walletSummaryUniverseId, setWalletSummaryUniverseId] = useState() - const [walletSummaryReceiptNonce, setWalletSummaryReceiptNonce] = useState(0) - const [balances, setBalances] = useState() - const [balanceState, setBalanceState] = useState('disconnected') - const [balanceError, setBalanceError] = useState() - const [portfolioEntries, setPortfolioEntries] = useState([]) - const [portfolioBalanceState, setPortfolioBalanceState] = useState('disconnected') - const [portfolioBalanceError, setPortfolioBalanceError] = useState() - const [portfolioRefreshNonce, setPortfolioRefreshNonce] = useState(0) - const [mode, setMode] = useState<'entry' | 'exit'>('entry') - const [side, setSide] = useState<'YES' | 'NO'>('YES') - const [amount, setAmount] = useState('0.01') - const [slippage, setSlippage] = useState(DEFAULT_SLIPPAGE_PERCENT) - const [transactionValidityMinutes, setTransactionValidityMinutes] = useState(DEFAULT_TRANSACTION_VALIDITY_MINUTES) - const [quote, setQuote] = useState() - const [state, setState] = useState('idle') - const [positionHash, setPositionHash] = useState() - const [message, setMessage] = useState() - const [positionReceiptWarning, setPositionReceiptWarning] = useState() - const [discoveryState, setDiscoveryState] = useState<'loading' | 'ready' | 'error'>('loading') - const [discoveryError, setDiscoveryError] = useState() - const [marketPage, setMarketPage] = useState({ start: 0n, total: 0n, previousStart: undefined as bigint | undefined, nextStart: undefined as bigint | undefined }) - const marketListRef = useRef(null) - const deploymentIndex = useRef(createSecurityPoolDeploymentIndex()).current - const portfolioBalanceRequests = useRef(createLatestRequestGuard()).current - const previousRoute = useRef(route) - const marketDetailRef = useRef(null) - const discoveryRequests = useRef(createLatestRequestGuard()).current - const balanceRequests = useRef(createLatestRequestGuard()).current - const walletSummaryRequests = useRef(createLatestRequestGuard()).current - const connectionRequests = useRef(createLatestRequestGuard()).current - const walletContextRevision = useRef(0) - const walletSubscriptionCleanup = useRef<(() => void) | undefined>() - const walletContextChangeHandler = useRef<(provider: InjectedEthereum, eventName: WalletContextChangeEvent, allowDisconnectedRefresh: boolean) => void>(() => undefined) - const walletConnectHandler = useRef<() => void>(() => undefined) - const walletComponentMounted = useRef(true) - const walletRenderContextKey = `${route}\u0000${selectedUniverseId ?? ''}\u0000${configuration?.chainId.toString() ?? ''}\u0000${configuration?.router ?? ''}` - const walletRenderContextKeyRef = useRef(walletRenderContextKey) - walletRenderContextKeyRef.current = walletRenderContextKey - const previousWalletSummaryRetryNonce = useRef(walletSummaryRetryNonce) - const simulationRequests = useRef(createLatestRequestGuard()).current - const positionWorkflow = useRef(createExclusiveWorkflowGuard()).current - const positionWorkflowLockedRef = useRef(false) - const liquidityWorkflowLockedRef = useRef(false) - const [positionWorkflowLocked, setPositionWorkflowLocked] = useState(false) - const [liquidityWorkflowLocked, setLiquidityWorkflowLocked] = useState(false) - const workflowLocked = positionWorkflowLocked || liquidityWorkflowLocked - const invalidateWalletIdentity = useCallback( - (detail: string) => { - walletContextRevision.current++ - walletSubscriptionCleanup.current?.() - walletSubscriptionCleanup.current = undefined - connectionRequests.invalidate() - balanceRequests.invalidate() - portfolioBalanceRequests.invalidate() - walletSummaryRequests.invalidate() - simulationRequests.invalidate() - accountRef.current = undefined - setWalletEthAttoEth(undefined) - setWalletRepAttoRep(undefined) - setWalletSummaryError(undefined) - setWalletSummaryErrorLabel(undefined) - setWalletSummaryUniverseId(selectedUniverseId) - setWalletSummaryStatus('disconnected') - onWalletSummaryChange(walletSummaryRefreshState(undefined, selectedUniverseId)) - setWalletClient(undefined) - setWalletProvider(undefined) - setAccount(undefined) - setBalances(undefined) - setBalanceState('error') - setBalanceError('Wallet context changed; reconnect to refresh balances and approvals') - setPortfolioBalanceError('Wallet context changed; reconnect before loading portfolio positions') - setWalletContextInvalidated(true) - setQuote(undefined) - if (!positionWorkflowLockedRef.current) { - setPositionHash(undefined) - setPositionReceiptWarning(undefined) - } - setMessage(detail) - }, - [balanceRequests, connectionRequests, onWalletSummaryChange, portfolioBalanceRequests, selectedUniverseId, simulationRequests, walletSummaryRequests], - ) - const executeWithCurrentWalletContext = useCallback( - async (expectedAccount: Address, networkFailure: string, accountFailure: string, action: () => Promise): Promise => { - const expectedRevision = walletContextRevision.current - const provider = getInjectedEthereum() - if (provider === undefined) { - const detail = 'No injected wallet was found; reconnect before continuing' - invalidateWalletIdentity(detail) - throw new Error(detail) - } - if (provider !== walletProvider) { - const detail = 'Wallet provider changed; reconnect before continuing' - invalidateWalletIdentity(detail) - throw new Error(detail) - } - const requireUnchangedProvider = () => { - if (walletContextRevision.current !== expectedRevision || getInjectedEthereum() !== provider || accountRef.current !== expectedAccount) { - const detail = 'Wallet context changed; reconnect before continuing' - invalidateWalletIdentity(detail) - throw new Error(detail) - } - } - let chainId: number - try { - chainId = await walletChainId(provider) - } catch (error) { - invalidateWalletIdentity(networkFailure) - throw new Error(networkFailure, { cause: error }) - } - requireUnchangedProvider() - if (configuration === undefined || chainId !== configuration.chainId) { - invalidateWalletIdentity(networkFailure) - throw new Error(networkFailure) - } - let connectedAccount: Address - try { - connectedAccount = await connectWallet(provider) - } catch (error) { - invalidateWalletIdentity(accountFailure) - throw new Error(accountFailure, { cause: error }) - } - requireUnchangedProvider() - if (connectedAccount !== expectedAccount) { - invalidateWalletIdentity(accountFailure) - throw new Error(accountFailure) - } - requireUnchangedProvider() - return action() - }, - [configuration, invalidateWalletIdentity, walletProvider], - ) - const createGuardedWalletWrite = useCallback( - (expectedAccount: Address, networkFailure: string, accountFailure: string) => { - const expectedRevision = walletContextRevision.current - const guardedWrite: GuardedWalletWrite = async write => { - if (walletContextRevision.current !== expectedRevision) throw new Error('Wallet context changed during transaction revalidation; reconnect and simulate again') - return await executeWithCurrentWalletContext(expectedAccount, networkFailure, accountFailure, async () => { - if (walletContextRevision.current !== expectedRevision) throw new Error('Wallet context changed during transaction revalidation; reconnect and simulate again') - return await write() - }) - } - return guardedWrite - }, - [executeWithCurrentWalletContext], - ) - const updatePositionWorkflowLock = useCallback( - (locked: boolean) => { - positionWorkflowLockedRef.current = locked - setPositionWorkflowLocked(locked) - onWorkflowLockChange(positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) - }, - [onWorkflowLockChange], - ) - const updateLiquidityWorkflowLock = useCallback( - (locked: boolean) => { - liquidityWorkflowLockedRef.current = locked - setLiquidityWorkflowLocked(locked) - onWorkflowLockChange(positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) - }, - [onWorkflowLockChange], - ) - const refreshWalletSummaryAfterReceipt = useCallback(() => { - walletSummaryRequests.invalidate() - const currentAccount = accountRef.current - const nextSummary = walletSummaryRefreshState(currentAccount, selectedUniverseId) - setWalletEthAttoEth(undefined) - setWalletRepAttoRep(undefined) - setWalletSummaryError(undefined) - setWalletSummaryErrorLabel(undefined) - setWalletSummaryUniverseId(selectedUniverseId) - setWalletSummaryStatus(currentAccount === undefined ? 'disconnected' : 'loading') - onWalletSummaryChange(nextSummary) - setWalletSummaryReceiptNonce(current => current + 1) - }, [onWalletSummaryChange, selectedUniverseId, walletSummaryRequests]) - const visibleMarkets = filterMarketsByUniverse(markets, selectedUniverseId) - const visiblePortfolioEntries = portfolioEntries.filter(entry => entry.market.universeId.toString() === selectedUniverseId) - const routePool = securityPoolAddressFromRoute(route) - const routeSelected = routePool === undefined ? undefined : visibleMarkets.find(market => market.pool.toLowerCase() === routePool) - const selected = routePool === undefined ? (visibleMarkets.find(market => market.pool.toLowerCase() === selectedPool?.toLowerCase()) ?? visibleMarkets[0]) : routeSelected - const selectedBalances = balanceState === 'ready' ? liveBalancesForMarket(balances, selected) : undefined - let selectedBalanceState = balanceState - if (balanceState !== 'error' && balances !== undefined && selectedBalances === undefined) selectedBalanceState = account === undefined ? 'disconnected' : 'loading' - const selectedPairInitialized = selected === undefined ? false : livePairInitialized(selected) - const [nowSeconds, setNowSeconds] = useState(() => BigInt(Math.floor(Date.now() / 1_000))) - const parsedAmount = useMemo(() => { - try { - return { value: parseUnits(amount), error: undefined } - } catch (error) { - return { value: undefined, error: error instanceof Error ? error.message : 'Invalid amount' } - } - }, [amount]) - - useEffect(() => { - onWalletSummaryChange({ account, ethAttoEth: walletEthAttoEth, repAttoRep: walletRepAttoRep, status: walletSummaryStatus, error: walletSummaryError, errorLabel: walletSummaryErrorLabel, universeId: walletSummaryUniverseId }) - }, [account, onWalletSummaryChange, walletEthAttoEth, walletRepAttoRep, walletSummaryError, walletSummaryErrorLabel, walletSummaryStatus, walletSummaryUniverseId]) - - useEffect(() => { - const request = walletSummaryRequests.begin() - setWalletEthAttoEth(undefined) - setWalletRepAttoRep(undefined) - setWalletSummaryError(undefined) - setWalletSummaryErrorLabel(undefined) - setWalletSummaryUniverseId(selectedUniverseId) - if (account === undefined) { - setWalletSummaryStatus('disconnected') - return - } - const availability = walletSummaryAvailability(configuration !== undefined, configurationError, discoveryState, discoveryError, selected !== undefined) - if (availability !== undefined) { - setWalletSummaryStatus(availability.status) - setWalletSummaryError(availability.error) - setWalletSummaryErrorLabel(availability.errorLabel) - return - } - if (configuration === undefined || selected === undefined) throw new Error('Wallet summary availability was resolved without a SecurityPool configuration') - if (selected.loadError !== undefined) { - setWalletSummaryStatus('error') - setWalletSummaryError(`Wallet balances could not be loaded because the selected SecurityPool is unavailable: ${selected.loadError}`) - setWalletSummaryErrorLabel('SecurityPool unavailable') - return - } - setWalletSummaryStatus('loading') - void loadWalletHeaderBalances(configuredClient(configuration), selected, account).then( - loaded => { - if (!walletSummaryRequests.isCurrent(request) || accountRef.current !== account) return - setWalletEthAttoEth(loaded.ethAttoEth) - setWalletRepAttoRep(loaded.repAttoRep) - setWalletSummaryStatus('ready') - }, - error => { - if (!walletSummaryRequests.isCurrent(request) || accountRef.current !== account) return - setWalletSummaryStatus('error') - setWalletSummaryError(publicErrorMessage(error, 'Wallet ETH and REP balances could not be loaded')) - setWalletSummaryErrorLabel('Wallet balance read failed') - }, - ) - return () => walletSummaryRequests.invalidate() - }, [account, configuration, configurationError, discoveryError, discoveryState, selected, walletSummaryReceiptNonce, walletSummaryRequests, walletSummaryRetryNonce]) - - async function refresh(nextConfiguration = configuration, requestedStart = marketPage.start, owner: WorkflowOwner | undefined = undefined) { - if (nextConfiguration === undefined) return - const request = discoveryRequests.begin() - simulationRequests.invalidate() - setQuote(undefined) - if (!positionWorkflowLockedRef.current) { - setState('idle') - if (owner !== 'position') { - setPositionHash(undefined) - setPositionReceiptWarning(undefined) - } - } - if (accountRef.current !== undefined) { - setBalanceState('loading') - setBalanceError(undefined) - setBalances(undefined) - } - if (route === 'portfolio') { - portfolioBalanceRequests.invalidate() - setPortfolioEntries([]) - setPortfolioBalanceState(accountRef.current === undefined ? 'disconnected' : 'loading') - setPortfolioBalanceError(undefined) - } - setDiscoveryState('loading') - setDiscoveryError(undefined) - try { - const client = configuredClient(nextConfiguration) - await validateLiveDeployment(client, nextConfiguration) - if (!discoveryRequests.isCurrent(request)) return - const requestedUniverseId = parsedUniverseId(selectedUniverseId) - const discovered = route === 'portfolio' || routePool !== undefined ? await discoverAllLiveMarketsInUniverse(client, nextConfiguration, requestedUniverseId, 25n, deploymentIndex) : await discoverLiveUniverseMarketPage(client, nextConfiguration, requestedUniverseId, requestedStart, 25n, deploymentIndex) - if (!discoveryRequests.isCurrent(request)) return - if (!discoveryCommitAllowed(owner, positionWorkflowLockedRef.current, liquidityWorkflowLockedRef.current)) { - setDiscoveryState('ready') - return - } - setMarkets(discovered.markets) - onUniversesChange(discovered.universeIds, discovered.selectedUniverseId) - setMarketPage({ start: discovered.start, total: discovered.total, previousStart: discovered.previousStart, nextStart: discovered.nextStart }) - setSelectedPool(currentPool => marketSelectionAfterDiscovery(discovered.markets, currentPool, requestedStart === marketPage.start)) - setDiscoveryState('ready') - } catch (error) { - if (!discoveryRequests.isCurrent(request)) return - if (!discoveryCommitAllowed(owner, positionWorkflowLockedRef.current, liquidityWorkflowLockedRef.current)) { - setDiscoveryState('ready') - return - } - const detail = publicErrorMessage(error, 'SecurityPool discovery failed') - setDiscoveryError(detail) - setDiscoveryState('error') - if (route === 'portfolio') { - setPortfolioBalanceState('error') - setPortfolioBalanceError(`SecurityPool discovery failed: ${detail}`) - } - if (accountRef.current !== undefined) { - setBalanceState('error') - setBalanceError('Market refresh failed before wallet balances could be revalidated') - } - } - } - - function refreshFromControl() { - if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) void refresh() - } - - function loadMarketPage(start: bigint | undefined) { - if (start !== undefined && !workflowLocked) void refresh(configuration, start) - } - - function focusSection(section: Readonly<{ current: HTMLElement | null }>) { - requestAnimationFrame(() => { - section.current?.focus({ preventScroll: true }) - section.current?.scrollIntoView({ block: 'start' }) - }) - } - - useEffect( - () => () => { - if (positionWorkflow.isActive()) positionWorkflow.finish() - onWorkflowLockChange(false) - }, - [onWorkflowLockChange], - ) - - useEffect(() => { - if (configuration === undefined) { - discoveryRequests.invalidate() - balanceRequests.invalidate() - simulationRequests.invalidate() - setMessage(configurationError) - return - } - void refresh(configuration, 0n) - }, [configuration, configurationError, selectedUniverseId]) - - useEffect(() => { - if (previousWalletSummaryRetryNonce.current === walletSummaryRetryNonce) return - previousWalletSummaryRetryNonce.current = walletSummaryRetryNonce - const retryStart = walletSummaryDiscoveryRetryStart(discoveryState, selected !== undefined, selected?.loadError, marketPage.start) - if (configuration !== undefined && retryStart !== undefined) void refresh(configuration, retryStart) - }, [configuration, discoveryState, marketPage.start, selected, walletSummaryRetryNonce]) - - useEffect(() => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setQuote(undefined) - setPositionHash(undefined) - setPositionReceiptWarning(undefined) - setState('idle') - if (previousRoute.current !== route) void refresh(configuration, 0n) - previousRoute.current = route - }, [route]) - - useEffect(() => { - let timeout: number | undefined - let active = true - const updateAtBoundary = () => { - if (!active) return - const current = BigInt(Math.floor(Date.now() / 1_000)) - setNowSeconds(current) - if (selected === undefined || current >= selected.endTime) return - const remainingSeconds = selected.endTime - current - const maximumDelay = 2_147_000_000 - const delay = remainingSeconds > BigInt(Math.floor(maximumDelay / 1_000)) ? maximumDelay : bigintToSafeNumber(remainingSeconds, 'Question-end delay') * 1_000 + 50 - timeout = window.setTimeout(updateAtBoundary, delay) - } - updateAtBoundary() - return () => { - active = false - if (timeout !== undefined) window.clearTimeout(timeout) - } - }, [selected?.endTime]) - - useEffect(() => { - if (selected === undefined || marketAcceptsNewRisk(selected, nowSeconds)) return - simulationRequests.invalidate() - setQuote(undefined) - if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) setState('idle') - }, [nowSeconds, selected]) - - useEffect( - () => () => { - walletComponentMounted.current = false - connectionRequests.invalidate() - walletContextRevision.current++ - walletSubscriptionCleanup.current?.() - walletSubscriptionCleanup.current = undefined - }, - [], - ) - - useEffect(() => { - const request = portfolioBalanceRequests.begin() - if (route !== 'portfolio') { - setPortfolioEntries([]) - setPortfolioBalanceState('disconnected') - setPortfolioBalanceError(undefined) - return - } - const emptyEntries = visibleMarkets.map(market => ({ market, balances: undefined, error: market.loadError })) - setPortfolioEntries(emptyEntries) - if (configuration === undefined || account === undefined) { - setPortfolioBalanceState(walletContextInvalidated ? 'error' : 'disconnected') - setPortfolioBalanceError(walletContextInvalidated ? 'Wallet context changed; reconnect before loading portfolio positions' : undefined) - return - } - setPortfolioBalanceState('loading') - setPortfolioBalanceError(undefined) - const client = configuredClient(configuration) - void mapWithConcurrency(visibleMarkets, 6, async (market, index) => { - if (market.loadError !== undefined) return { market, balances: undefined, error: market.loadError } - let entry: PortfolioBalanceEntry - try { - const loaded = await loadLiveBalances(client, market, account, configuration.router) - entry = { market, balances: liveBalancesForMarket(loaded, market), error: undefined } - } catch (error) { - entry = { market, balances: undefined, error: publicErrorMessage(error, 'Balance refresh failed') } - } - if (portfolioBalanceRequests.isCurrent(request) && accountRef.current === account) setPortfolioEntries(current => current.map((currentEntry, currentIndex) => (currentIndex === index ? entry : currentEntry))) - return entry - }) - .then(entries => { - if (!portfolioBalanceRequests.isCurrent(request) || accountRef.current !== account) return - setPortfolioEntries(entries) - setPortfolioBalanceState('ready') - setPortfolioBalanceError(undefined) - }) - .catch(error => { - if (!portfolioBalanceRequests.isCurrent(request) || accountRef.current !== account) return - setPortfolioBalanceState('error') - setPortfolioBalanceError(publicErrorMessage(error, 'Portfolio balance refresh failed')) - }) - return () => portfolioBalanceRequests.invalidate() - }, [account, configuration, markets, portfolioBalanceRequests, portfolioRefreshNonce, route, selectedUniverseId, walletContextInvalidated]) - - useEffect(() => { - const request = balanceRequests.begin() - if (route === 'portfolio') { - setBalances(undefined) - setBalanceState('disconnected') - setBalanceError(undefined) - return - } - if (configuration === undefined || account === undefined || selected === undefined || selected.loadError !== undefined) { - setBalances(undefined) - setBalanceState(walletContextInvalidated || selected?.loadError !== undefined ? 'error' : 'disconnected') - setBalanceError(selected?.loadError) - return - } - setBalanceState('loading') - setBalanceError(undefined) - setBalances(undefined) - void loadLiveBalances(configuredClient(configuration), selected, account, configuration.router).then( - loaded => { - if (balanceRequests.isCurrent(request)) { - setBalances(loaded) - setBalanceState('ready') - setBalanceError(undefined) - } - }, - error => { - if (balanceRequests.isCurrent(request)) { - setBalanceState('error') - setBalanceError(publicErrorMessage(error, 'Balance refresh failed')) - } - }, - ) - return () => balanceRequests.invalidate() - }, [account, configuration, route, selected, walletContextInvalidated]) - - async function retryBalances() { - if (configuration === undefined || selected === undefined) return - if (account === undefined) { - await connect() - return - } - const request = balanceRequests.begin() - simulationRequests.invalidate() - setQuote(undefined) - if (!positionWorkflowLockedRef.current) setState('idle') - setBalanceState('loading') - setBalanceError(undefined) - setBalances(undefined) - try { - const loaded = await loadLiveBalances(configuredClient(configuration), selected, account, configuration.router) - if (!balanceRequests.isCurrent(request)) return - setBalances(loaded) - setBalanceState('ready') - setMessage(undefined) - } catch (error) { - if (!balanceRequests.isCurrent(request)) return - setBalanceState('error') - setBalanceError(publicErrorMessage(error, 'Balance refresh failed')) - } - } - - async function retryPortfolioBalances() { - if (account === undefined) { - await connect() - return - } - if (discoveryState === 'error') { - await refresh(configuration, 0n) - return - } - setPortfolioRefreshNonce(value => value + 1) - } - - async function connect() { - if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) return - if (accountRef.current !== undefined || walletClient !== undefined) invalidateWalletIdentity('Reconnecting wallet…') - const request = connectionRequests.begin() - const expectedRenderContextKey = walletRenderContextKey - try { - const provider = getInjectedEthereum() - if (provider === undefined) throw new Error('No injected wallet was found') - const requireCurrentConnection = () => { - if (!walletComponentMounted.current || !connectionRequests.isCurrent(request)) return false - if (getInjectedEthereum() !== provider) throw new Error('Wallet provider changed; reconnect before continuing') - if (walletRenderContextKeyRef.current !== expectedRenderContextKey) { - walletConnectHandler.current() - return false - } - return true - } - if (configuration === undefined) throw new Error('Deployment configuration is unavailable') - let chainId = await walletChainId(provider) - if (!requireCurrentConnection()) return - if (chainId !== configuration.chainId) { - await switchWalletChain(provider, configuration.chainId) - if (!requireCurrentConnection()) return - chainId = await walletChainId(provider) - if (!requireCurrentConnection()) return - } - if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const connected = await connectWallet(provider) - if (!requireCurrentConnection()) return - walletSubscriptionCleanup.current?.() - walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, (eventName: WalletContextChangeEvent) => { - walletContextChangeHandler.current(provider, eventName, false) - }) - const confirmedChainId = await walletChainId(provider) - if (!requireCurrentConnection()) return - if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const confirmedAccount = await connectWallet(provider) - if (!requireCurrentConnection()) return - if (confirmedAccount !== connected) throw new Error('Wallet account changed while connecting; reconnect to continue') - balanceRequests.invalidate() - walletSummaryRequests.invalidate() - simulationRequests.invalidate() - accountRef.current = connected - setWalletEthAttoEth(undefined) - setWalletRepAttoRep(undefined) - setWalletSummaryError(undefined) - setWalletSummaryErrorLabel(undefined) - setWalletSummaryUniverseId(selectedUniverseId) - setWalletSummaryStatus('loading') - onWalletSummaryChange(walletSummaryRefreshState(connected, selectedUniverseId)) - setBalances(undefined) - setBalanceState('loading') - setBalanceError(undefined) - setWalletContextInvalidated(false) - walletContextRevision.current++ - setAccount(connected) - setWalletClient(createTradingWalletClient(provider, connected)) - setWalletProvider(provider) - setMessage(undefined) - await refresh(configuration) - } catch (error) { - if (!connectionRequests.isCurrent(request)) return - invalidateWalletIdentity(publicErrorMessage(error, 'Wallet connection failed')) - } - } - walletConnectHandler.current = () => { - void connect() - } - - async function refreshWalletContextAfterEvent(provider: InjectedEthereum, eventName: WalletContextChangeEvent, allowDisconnectedRefresh: boolean) { - const contextLabel = eventName === 'accountsChanged' ? 'Wallet account changed' : 'Wallet network changed' - if ((!allowDisconnectedRefresh && accountRef.current === undefined) || positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) { - invalidateWalletIdentity(`${contextLabel}. Reconnect before simulating or submitting.`) - if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) setState('error') - return - } - invalidateWalletIdentity(`${contextLabel}. Refreshing wallet context…`) - const request = connectionRequests.begin() - const expectedRenderContextKey = walletRenderContextKey - try { - const requireCurrentConnection = () => { - if (!walletComponentMounted.current || !connectionRequests.isCurrent(request)) return false - if (getInjectedEthereum() !== provider) throw new Error('Wallet provider changed; reconnect before continuing') - if (walletRenderContextKeyRef.current !== expectedRenderContextKey) { - walletContextChangeHandler.current(provider, eventName, true) - return false - } - return true - } - if (configuration === undefined) throw new Error('Deployment configuration is unavailable') - const chainId = await walletChainId(provider) - if (!requireCurrentConnection()) return - if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const connected = await connectWallet(provider) - if (!requireCurrentConnection()) return - walletSubscriptionCleanup.current?.() - walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, changedEventName => { - walletContextChangeHandler.current(provider, changedEventName, false) - }) - const confirmedChainId = await walletChainId(provider) - if (!requireCurrentConnection()) return - if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) - const confirmedAccount = await connectWallet(provider) - if (!requireCurrentConnection()) return - if (confirmedAccount !== connected) throw new Error('Wallet account changed while refreshing; reconnect to continue') - balanceRequests.invalidate() - walletSummaryRequests.invalidate() - simulationRequests.invalidate() - accountRef.current = connected - setWalletEthAttoEth(undefined) - setWalletRepAttoRep(undefined) - setWalletSummaryError(undefined) - setWalletSummaryErrorLabel(undefined) - setWalletSummaryUniverseId(selectedUniverseId) - setWalletSummaryStatus('loading') - onWalletSummaryChange(walletSummaryRefreshState(connected, selectedUniverseId)) - setBalances(undefined) - setBalanceState('loading') - setBalanceError(undefined) - setWalletContextInvalidated(false) - walletContextRevision.current++ - setAccount(connected) - setWalletClient(createTradingWalletClient(provider, connected)) - setWalletProvider(provider) - setMessage(undefined) - setState('idle') - await refresh(configuration) - } catch (error) { - if (!connectionRequests.isCurrent(request)) return - invalidateWalletIdentity(`${contextLabel}: ${publicErrorMessage(error, 'wallet refresh failed')}`) - setState('error') - } - } - walletContextChangeHandler.current = (provider, eventName, allowDisconnectedRefresh) => { - void refreshWalletContextAfterEvent(provider, eventName, allowDisconnectedRefresh) - } - - async function refreshBalancesAfterApproval(label: string, expectedMarket: LiveMarket, expectedAccount: Address, request = balanceRequests.begin()): Promise<'ready' | 'refresh-error' | 'context-changed'> { - if (configuration === undefined || accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' - setBalances(undefined) - setBalanceState('loading') - setBalanceError(undefined) - try { - const loaded = await loadLiveBalances(configuredClient(configuration), expectedMarket, expectedAccount, configuration.router) - if (accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' - setBalances(loaded) - setBalanceState('ready') - setBalanceError(undefined) - return 'ready' - } catch (error) { - if (accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' - const detail = publicErrorMessage(error, 'Balance refresh failed') - setBalanceState('error') - setBalanceError(`${label} confirmed, but balances could not be refreshed: ${detail}`) - return 'refresh-error' - } - } - - async function simulate() { - const slippageBps = parseSlippageBps(slippage) - const validityMinutes = parseTransactionValidityMinutes(transactionValidityMinutes) - if (configuration === undefined || selected === undefined || account === undefined || walletClient === undefined || parsedAmount.value === undefined || parsedAmount.value === 0n || slippageBps === undefined || validityMinutes === undefined) return - const request = simulationRequests.begin() - try { - setState('simulating') - setPositionHash(undefined) - setMessage(undefined) - const context = { account, configuration, walletClient } - const nextQuote: Quote = - mode === 'entry' - ? { ...context, kind: 'entry', value: await simulateEntry(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } - : { ...context, kind: 'exit', value: await simulateExit(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } - if (!simulationRequests.isCurrent(request)) return - setQuote(nextQuote) - setState('ready') - } catch (error) { - if (!simulationRequests.isCurrent(request)) return - setQuote(undefined) - setState('error') - setMessage(publicErrorMessage(error, 'Router simulation failed')) - } - } - - async function approve() { - if (configuration === undefined || selected === undefined || account === undefined || walletClient === undefined) return - if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) return - if (!positionWorkflow.begin()) return - updatePositionWorkflowLock(true) - setState('preparing') - setMessage(undefined) - setPositionReceiptWarning(undefined) - setPositionHash(undefined) - const balanceRequest = balanceRequests.begin() - let broadcastHash: Hash | undefined - let receiptKnown = false - let keepLocked = false - try { - broadcastHash = await createGuardedWalletWrite( - account, - 'Wallet network changed; switch back before approving', - 'Wallet account changed; reconnect before approving', - )(async () => { - setState('approval') - return await approveRouter(walletClient, selected, configuration, account) - }) - setPositionHash(broadcastHash) - setState('approval-pending') - const receipt = await observeKnownReceipt(walletClient.waitForTransactionReceipt({ hash: broadcastHash }), refreshWalletSummaryAfterReceipt) - receiptKnown = true - if (receipt.status === 'reverted') { - if (!balanceRequests.isCurrent(balanceRequest)) { - setState('error') - setMessage(current => `${current ?? 'Wallet context changed.'} Approval transaction reverted.`) - return - } - throw new Error('Approval transaction reverted') - } - setState('approval-confirmed') - if (!balanceRequests.isCurrent(balanceRequest)) return - const refreshResult = await refreshBalancesAfterApproval('Share-token approval', selected, account, balanceRequest) - if (refreshResult !== 'ready') { - return - } - setPositionReceiptWarning(undefined) - setMessage(undefined) - } catch (error) { - if (!balanceRequests.isCurrent(balanceRequest)) { - if (broadcastHash !== undefined && !receiptKnown) { - keepLocked = true - setState('approval-pending') - setPositionReceiptWarning(broadcastUncertainMessage('Share-token approval', broadcastHash)) - } else setState('error') - return - } - const failure = approvalFailureTransition('Share-token approval', broadcastHash, receiptKnown, error, 'Approval failed') - keepLocked = failure.keepLocked - setState(failure.state === 'pending' ? 'approval-pending' : failure.state) - setMessage(failure.message) - setPositionReceiptWarning(failure.warning) - } finally { - positionWorkflow.finish() - if (!keepLocked) { - updatePositionWorkflowLock(false) - } - } - } - - async function submit() { - if (configuration === undefined || account === undefined || walletClient === undefined || quote === undefined) return - if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) return - if (!positionWorkflow.begin()) return - updatePositionWorkflowLock(true) - setState('preparing') - setPositionReceiptWarning(undefined) - let broadcastHash: Hash | undefined - let receiptKnown = false - let keepLocked = false - try { - const quotedAmount = quote.kind === 'entry' ? quote.value.amount : quote.value.completeSets - if ( - selected === undefined || - quote.account !== account || - quote.walletClient !== walletClient || - quote.configuration.chainId !== configuration.chainId || - quote.configuration.router !== configuration.router || - quote.value.market.pool !== selected.pool || - quote.value.side !== side || - quote.kind !== mode || - parsedAmount.value !== quotedAmount - ) - throw new Error('Trade inputs changed; simulate the current selection again') - simulationRequests.invalidate() - await executeWithCurrentWalletContext(account, 'Wallet network changed; switch back before submitting', 'Wallet account changed; reconnect and simulate again', async () => undefined) - const guardedPositionWrite = createGuardedWalletWrite(account, 'Wallet network changed during transaction revalidation; reconnect and simulate again', 'Wallet account changed during transaction revalidation; reconnect and simulate again') - const guardedWrite: GuardedWalletWrite = async write => - await guardedPositionWrite(async () => { - setState('submitting') - return await write() - }) - broadcastHash = quote.kind === 'entry' ? await submitFreshEntry(walletClient, configuration, account, quote.value, guardedWrite) : await submitFreshExit(walletClient, configuration, account, quote.value, guardedWrite) - setPositionHash(broadcastHash) - setState('pending') - const receipt = await observeKnownReceipt(walletClient.waitForTransactionReceipt({ hash: broadcastHash }), refreshWalletSummaryAfterReceipt) - receiptKnown = true - if (receipt.status === 'reverted') throw new Error('Transaction reverted') - setQuote(undefined) - setPositionReceiptWarning(undefined) - setState('confirmed') - await refresh(configuration, marketPage.start, 'position') - } catch (error) { - if (broadcastHash !== undefined && !receiptKnown) { - keepLocked = true - setState('pending') - setMessage(undefined) - setPositionReceiptWarning(broadcastUncertainMessage('Transaction', broadcastHash)) - } else { - const failure = failedSubmissionTransition(error, 'Transaction failed') - setQuote(failure.quote) - setState(failure.state) - setMessage(failure.message) - setPositionReceiptWarning(undefined) - } - } finally { - positionWorkflow.finish() - if (!keepLocked) { - updatePositionWorkflowLock(false) - } - } - } - + const { wallet, balances, discovery, position, workflow } = useLiveTradingController({ + route, + configuration, + configurationError, + selectedUniverseId, + onUniversesChange, + onWorkflowLockChange, + onWalletSummaryChange, + walletSummaryRetryNonce, + defaultSlippage: DEFAULT_SLIPPAGE_PERCENT, + defaultValidityMinutes: DEFAULT_TRANSACTION_VALIDITY_MINUTES, + }) + const { account, walletClient, connect, refreshWalletSummaryAfterReceipt, walletContextIsCurrent, executeWithCurrentWalletContext, createGuardedWalletWrite } = wallet + const { balanceError, portfolioBalanceState, portfolioBalanceError, visiblePortfolioEntries, selectedBalances, selectedBalanceState, retryBalances, retryPortfolioBalances, refreshBalancesAfterApproval } = balances + const { visibleMarkets, selected, selectedPairInitialized, routePool, discoveryState, discoveryError, marketPage, marketListRef, marketDetailRef, nowSeconds, refresh, refreshFromControl, loadMarketPage, focusSection, selectMarket } = discovery + const { parsedAmount, mode, setMode, side, setSide, amount, setAmount, slippage, setSlippage, transactionValidityMinutes, setTransactionValidityMinutes, quote, state, positionHash, message, positionReceiptWarning, simulate, approve, submit } = position + const { workflowLocked, updateLiquidityWorkflowLock } = workflow if (configuration === undefined) return (
@@ -1411,26 +506,7 @@ export function LiveTrading({ else if (visibleMarkets.length === 0) discoveryContent =

No SecurityPools are deployed in the selected universe.

else { const marketButtons = visibleMarkets.map(market => ( - @@ -1620,7 +696,7 @@ export function LiveTrading({ refresh={() => refresh(configuration, marketPage.start, 'liquidity')} refreshBalancesAfterApproval={refreshBalancesAfterApproval} onKnownReceipt={refreshWalletSummaryAfterReceipt} - walletContextIsCurrent={expectedAccount => accountRef.current === expectedAccount} + walletContextIsCurrent={walletContextIsCurrent} executeWithCurrentWalletContext={executeWithCurrentWalletContext} createGuardedWalletWrite={createGuardedWalletWrite} retryBalances={retryBalances} @@ -1641,7 +717,7 @@ export function LiveTrading({ refresh={() => refresh(configuration, marketPage.start, 'liquidity')} refreshBalancesAfterApproval={refreshBalancesAfterApproval} onKnownReceipt={refreshWalletSummaryAfterReceipt} - walletContextIsCurrent={expectedAccount => accountRef.current === expectedAccount} + walletContextIsCurrent={walletContextIsCurrent} executeWithCurrentWalletContext={executeWithCurrentWalletContext} createGuardedWalletWrite={createGuardedWalletWrite} retryBalances={retryBalances} @@ -1665,46 +741,11 @@ export function LiveTrading({ transactionHash={positionHash} externallyLocked={workflowLocked} nowSeconds={nowSeconds} - setMode={value => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setMode(value) - setQuote(undefined) - setPositionHash(undefined) - setState('idle') - }} - setSide={value => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setSide(value) - setQuote(undefined) - setPositionHash(undefined) - setState('idle') - }} - setAmount={value => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setAmount(value) - setQuote(undefined) - setPositionHash(undefined) - setState('idle') - }} - setSlippage={value => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setSlippage(value) - setQuote(undefined) - setPositionHash(undefined) - setState('idle') - }} - setTransactionValidityMinutes={value => { - if (positionWorkflowLockedRef.current) return - simulationRequests.invalidate() - setTransactionValidityMinutes(value) - setQuote(undefined) - setPositionHash(undefined) - setState('idle') - }} + setMode={setMode} + setSide={setSide} + setAmount={setAmount} + setSlippage={setSlippage} + setTransactionValidityMinutes={setTransactionValidityMinutes} simulate={simulate} approve={approve} submit={submit} diff --git a/trading/ui/ts/features/liveTradingController.ts b/trading/ui/ts/features/liveTradingController.ts new file mode 100644 index 000000000..980228f06 --- /dev/null +++ b/trading/ui/ts/features/liveTradingController.ts @@ -0,0 +1,1207 @@ +import type { Address, Hash, WalletClient } from '@zoltar/shared/ethereum' +import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks' +import { bigintToSafeNumber, parseUnits, parseUnitsOrUndefined } from '../app/format.ts' +import { createExclusiveWorkflowGuard, createLatestRequestGuard } from '../app/latestRequest.ts' +import { getInjectedEthereum, subscribeToWalletContextChanges, type InjectedEthereum, type WalletContextChangeEvent } from '../protocol/injected.ts' +import { + approveRouter, + connectWallet, + createSecurityPoolDeploymentIndex, + createTradingPublicClient, + createTradingWalletClient, + discoverAllLiveMarketsInUniverse, + discoverLiveUniverseMarketPage, + loadLiveBalances, + loadWalletHeaderBalances, + liveBalancesForMarket, + mapWithConcurrency, + marketAcceptsNewRisk, + publicErrorMessage, + simulateEntry, + simulateExit, + submitFreshEntry, + submitFreshExit, + switchWalletChain, + validateLiveDeployment, + walletChainId, + type LiveBalances, + type LiveMarket, + type SecurityPoolDeployment, +} from '../protocol/live.ts' +import type { DeploymentConfiguration } from '../protocol/config.ts' + +type EntryQuote = Awaited> +type ExitQuote = Awaited> +export type QuoteContext = Readonly<{ account: Address; configuration: DeploymentConfiguration; walletClient: WalletClient }> + +export type Quote = (Readonly<{ kind: 'entry'; value: EntryQuote }> | Readonly<{ kind: 'exit'; value: ExitQuote }>) & QuoteContext +export type TransactionState = 'idle' | 'simulating' | 'ready' | 'preparing' | 'approval' | 'approval-pending' | 'approval-confirmed' | 'submitting' | 'pending' | 'confirmed' | 'error' +export type BalanceState = 'disconnected' | 'loading' | 'ready' | 'error' +export type PortfolioBalanceEntry = Readonly<{ market: LiveMarket; balances: LiveBalances | undefined; error: string | undefined }> +export type WalletSummaryState = Readonly<{ + account: Address | undefined + ethAttoEth: bigint | undefined + repAttoRep: bigint | undefined + status: 'disconnected' | 'loading' | 'ready' | 'error' + error: string | undefined + errorLabel: string | undefined + universeId: string | undefined +}> + +export type GuardedWalletWrite = (write: () => Promise) => Promise +type WorkflowOwner = 'position' | 'liquidity' + +export function walletSummaryRefreshState(account: Address | undefined, universeId: string | undefined): WalletSummaryState { + return { account, ethAttoEth: undefined, repAttoRep: undefined, status: account === undefined ? 'disconnected' : 'loading', error: undefined, errorLabel: undefined, universeId } +} + +export async function observeKnownReceipt(receipt: Promise, onKnownReceipt: () => void): Promise { + const knownReceipt = await receipt + onKnownReceipt() + return knownReceipt +} + +export function walletSummaryDiscoveryRetryStart(discoveryState: 'loading' | 'ready' | 'error', selectedPoolAvailable: boolean, selectedPoolLoadError: string | undefined, currentPageStart: bigint) { + return discoveryState === 'error' || !selectedPoolAvailable || selectedPoolLoadError !== undefined ? currentPageStart : undefined +} + +export function walletSummaryAvailability(configurationAvailable: boolean, configurationError: string | undefined, discoveryState: 'loading' | 'ready' | 'error', discoveryError: string | undefined, selectedPoolAvailable: boolean) { + if (!configurationAvailable) return configurationError === undefined ? { status: 'loading' as const, error: undefined, errorLabel: undefined } : { status: 'error' as const, error: configurationError, errorLabel: 'Deployment unavailable' } + if (discoveryState === 'loading') return { status: 'loading' as const, error: undefined, errorLabel: undefined } + if (discoveryState === 'error') return { status: 'error' as const, error: `SecurityPool discovery failed: ${discoveryError ?? 'unknown discovery error'}`, errorLabel: 'SecurityPool discovery failed' } + if (selectedPoolAvailable) return undefined + return { status: 'error' as const, error: 'No SecurityPool is available in the selected universe', errorLabel: 'No SecurityPool in this universe' } +} + +export function parseSlippageBps(value: string) { + const parsed = parseUnitsOrUndefined(value, 2) + return parsed !== undefined && parsed >= 0n && parsed <= 500n ? parsed : undefined +} + +export function parseTransactionValidityMinutes(value: string) { + if (!/^\d+$/.test(value)) return undefined + const parsed = BigInt(value) + return parsed >= 1n && parsed <= 1_440n ? parsed : undefined +} + +export function failedSubmissionTransition(caught: unknown, fallback: string) { + return { quote: undefined, state: 'error' as const, message: publicErrorMessage(caught, fallback) } +} + +export function broadcastUncertainMessage(label: string, hash: Hash) { + return `${label} ${hash} was broadcast, but its receipt could not be confirmed. Do not resubmit. Check this hash in your wallet or configured block explorer, then reload only after its final status is known.` +} + +export function approvalFailureTransition(label: string, broadcastHash: Hash | undefined, receiptKnown: boolean, caught: unknown, fallback: string) { + if (broadcastHash !== undefined && !receiptKnown) return { keepLocked: true, state: 'pending' as const, message: undefined, warning: broadcastUncertainMessage(label, broadcastHash) } + return { keepLocked: false, state: 'error' as const, message: publicErrorMessage(caught, fallback), warning: undefined } +} + +export function positionControlsWorkflowLocked(state: TransactionState, receiptWarning: string | undefined) { + return state === 'preparing' || state === 'approval' || state === 'approval-pending' || state === 'submitting' || state === 'pending' || receiptWarning !== undefined +} + +export function discoveryCommitAllowed(owner: WorkflowOwner | undefined, positionLocked: boolean, liquidityLocked: boolean) { + if (owner === 'position') return !liquidityLocked + if (owner === 'liquidity') return !positionLocked + return !positionLocked && !liquidityLocked +} + +export function securityPoolAddressFromRoute(route: string) { + const match = /^security-pool\/(0x[0-9a-fA-F]{40})$/.exec(route) + return match?.[1]?.toLowerCase() +} + +export function livePairInitialized(market: Pick) { + return market.pair !== undefined && market.lpTotalSupply > 0n && market.yesReserve > 0n && market.noReserve > 0n && market.tradingStatus !== 6 +} + +export function marketSelectionAfterDiscovery(markets: readonly Pick[], currentPool: Address | undefined, preserveCurrentPage: boolean) { + if (preserveCurrentPage && markets.some(market => market.pool === currentPool)) return currentPool + return markets[0]?.pool +} + +export function filterMarketsByUniverse(markets: readonly LiveMarket[], selectedUniverseId: string | undefined) { + if (selectedUniverseId === undefined) return [] + return markets.filter(market => market.universeId.toString() === selectedUniverseId) +} + +function parsedUniverseId(selectedUniverseId: string | undefined) { + if (selectedUniverseId === undefined) return undefined + try { + return BigInt(selectedUniverseId) + } catch (error) { + if (error instanceof SyntaxError) return undefined + throw error + } +} + +function useWalletState() { + const [account, setAccount] = useState
() + const accountRef = useRef(account) + accountRef.current = account + const [walletClient, setWalletClient] = useState() + const [walletProvider, setWalletProvider] = useState() + const [walletContextInvalidated, setWalletContextInvalidated] = useState(false) + const [walletSummaryStatus, setWalletSummaryStatus] = useState('disconnected') + const [walletEthAttoEth, setWalletEthAttoEth] = useState() + const [walletRepAttoRep, setWalletRepAttoRep] = useState() + const [walletSummaryError, setWalletSummaryError] = useState() + const [walletSummaryErrorLabel, setWalletSummaryErrorLabel] = useState() + const [walletSummaryUniverseId, setWalletSummaryUniverseId] = useState() + const [walletSummaryReceiptNonce, setWalletSummaryReceiptNonce] = useState(0) + + return { + account, + setAccount, + accountRef, + walletClient, + setWalletClient, + walletProvider, + setWalletProvider, + walletContextInvalidated, + setWalletContextInvalidated, + walletSummaryStatus, + setWalletSummaryStatus, + walletEthAttoEth, + setWalletEthAttoEth, + walletRepAttoRep, + setWalletRepAttoRep, + walletSummaryError, + setWalletSummaryError, + walletSummaryErrorLabel, + setWalletSummaryErrorLabel, + walletSummaryUniverseId, + setWalletSummaryUniverseId, + walletSummaryReceiptNonce, + setWalletSummaryReceiptNonce, + } +} + +function useBalanceState() { + const [balances, setBalances] = useState() + const [balanceState, setBalanceState] = useState('disconnected') + const [balanceError, setBalanceError] = useState() + const [portfolioEntries, setPortfolioEntries] = useState([]) + const [portfolioBalanceState, setPortfolioBalanceState] = useState('disconnected') + const [portfolioBalanceError, setPortfolioBalanceError] = useState() + const [portfolioRefreshNonce, setPortfolioRefreshNonce] = useState(0) + + return { balances, setBalances, balanceState, setBalanceState, balanceError, setBalanceError, portfolioEntries, setPortfolioEntries, portfolioBalanceState, setPortfolioBalanceState, portfolioBalanceError, setPortfolioBalanceError, portfolioRefreshNonce, setPortfolioRefreshNonce } +} + +function useDiscoveryState() { + const [markets, setMarkets] = useState([]) + const [selectedPool, setSelectedPool] = useState
() + const [discoveryState, setDiscoveryState] = useState<'loading' | 'ready' | 'error'>('loading') + const [discoveryError, setDiscoveryError] = useState() + const [marketPage, setMarketPage] = useState({ start: 0n, total: 0n, previousStart: undefined as bigint | undefined, nextStart: undefined as bigint | undefined }) + const deploymentIndex = useRef(createSecurityPoolDeploymentIndex()).current + + return { markets, setMarkets, selectedPool, setSelectedPool, discoveryState, setDiscoveryState, discoveryError, setDiscoveryError, marketPage, setMarketPage, deploymentIndex } +} + +function usePositionWorkflowState(onWorkflowLockChange: (locked: boolean) => void, defaultSlippage: string, defaultValidityMinutes: string) { + const [mode, setMode] = useState<'entry' | 'exit'>('entry') + const [side, setSide] = useState<'YES' | 'NO'>('YES') + const [amount, setAmount] = useState('0.01') + const [slippage, setSlippage] = useState(defaultSlippage) + const [transactionValidityMinutes, setTransactionValidityMinutes] = useState(defaultValidityMinutes) + const [quote, setQuote] = useState() + const [state, setState] = useState('idle') + const [positionHash, setPositionHash] = useState() + const [message, setMessage] = useState() + const [positionReceiptWarning, setPositionReceiptWarning] = useState() + const positionWorkflow = useRef(createExclusiveWorkflowGuard()).current + const positionWorkflowLockedRef = useRef(false) + const liquidityWorkflowLockedRef = useRef(false) + const [positionWorkflowLocked, setPositionWorkflowLocked] = useState(false) + const [liquidityWorkflowLocked, setLiquidityWorkflowLocked] = useState(false) + const workflowLocked = positionWorkflowLocked || liquidityWorkflowLocked + const updatePositionWorkflowLock = useCallback( + (locked: boolean) => { + positionWorkflowLockedRef.current = locked + setPositionWorkflowLocked(locked) + onWorkflowLockChange(positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) + }, + [onWorkflowLockChange], + ) + const updateLiquidityWorkflowLock = useCallback( + (locked: boolean) => { + liquidityWorkflowLockedRef.current = locked + setLiquidityWorkflowLocked(locked) + onWorkflowLockChange(positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) + }, + [onWorkflowLockChange], + ) + + useEffect( + () => () => { + if (positionWorkflow.isActive()) positionWorkflow.finish() + onWorkflowLockChange(false) + }, + [onWorkflowLockChange, positionWorkflow], + ) + + return { + mode, + setMode, + side, + setSide, + amount, + setAmount, + slippage, + setSlippage, + transactionValidityMinutes, + setTransactionValidityMinutes, + quote, + setQuote, + state, + setState, + positionHash, + setPositionHash, + message, + setMessage, + positionReceiptWarning, + setPositionReceiptWarning, + positionWorkflow, + positionWorkflowLockedRef, + liquidityWorkflowLockedRef, + workflowLocked, + updatePositionWorkflowLock, + updateLiquidityWorkflowLock, + } +} + +export function useQuestionClock(endTime: bigint | undefined) { + const [nowSeconds, setNowSeconds] = useState(() => BigInt(Math.floor(Date.now() / 1_000))) + + useEffect(() => { + let timeout: number | undefined + let active = true + const updateAtBoundary = () => { + if (!active) return + const current = BigInt(Math.floor(Date.now() / 1_000)) + setNowSeconds(current) + if (endTime === undefined || current >= endTime) return + const remainingSeconds = endTime - current + const maximumDelay = 2_147_000_000 + const delay = remainingSeconds > BigInt(Math.floor(maximumDelay / 1_000)) ? maximumDelay : bigintToSafeNumber(remainingSeconds, 'Question-end delay') * 1_000 + 50 + timeout = window.setTimeout(updateAtBoundary, delay) + } + updateAtBoundary() + return () => { + active = false + if (timeout !== undefined) window.clearTimeout(timeout) + } + }, [endTime]) + + return nowSeconds +} + +export function useLiveTradingController({ + route, + configuration, + configurationError, + selectedUniverseId, + onUniversesChange, + onWorkflowLockChange, + onWalletSummaryChange, + walletSummaryRetryNonce, + defaultSlippage, + defaultValidityMinutes, +}: { + route: string + configuration: DeploymentConfiguration | undefined + configurationError: string | undefined + selectedUniverseId: string | undefined + onUniversesChange(universeIds: readonly bigint[], selectedUniverseId: bigint | undefined): void + onWorkflowLockChange(locked: boolean): void + onWalletSummaryChange(summary: WalletSummaryState): void + walletSummaryRetryNonce: number + defaultSlippage: string + defaultValidityMinutes: string +}) { + const { markets, setMarkets, selectedPool, setSelectedPool, discoveryState, setDiscoveryState, discoveryError, setDiscoveryError, marketPage, setMarketPage, deploymentIndex } = useDiscoveryState() + const { + account, + setAccount, + accountRef, + walletClient, + setWalletClient, + walletProvider, + setWalletProvider, + walletContextInvalidated, + setWalletContextInvalidated, + walletSummaryStatus, + setWalletSummaryStatus, + walletEthAttoEth, + setWalletEthAttoEth, + walletRepAttoRep, + setWalletRepAttoRep, + walletSummaryError, + setWalletSummaryError, + walletSummaryErrorLabel, + setWalletSummaryErrorLabel, + walletSummaryUniverseId, + setWalletSummaryUniverseId, + walletSummaryReceiptNonce, + setWalletSummaryReceiptNonce, + } = useWalletState() + const { balances, setBalances, balanceState, setBalanceState, balanceError, setBalanceError, portfolioEntries, setPortfolioEntries, portfolioBalanceState, setPortfolioBalanceState, portfolioBalanceError, setPortfolioBalanceError, portfolioRefreshNonce, setPortfolioRefreshNonce } = useBalanceState() + const { + mode, + setMode, + side, + setSide, + amount, + setAmount, + slippage, + setSlippage, + transactionValidityMinutes, + setTransactionValidityMinutes, + quote, + setQuote, + state, + setState, + positionHash, + setPositionHash, + message, + setMessage, + positionReceiptWarning, + setPositionReceiptWarning, + positionWorkflow, + positionWorkflowLockedRef, + liquidityWorkflowLockedRef, + workflowLocked, + updatePositionWorkflowLock, + updateLiquidityWorkflowLock, + } = usePositionWorkflowState(onWorkflowLockChange, defaultSlippage, defaultValidityMinutes) + const marketListRef = useRef(null) + const marketDetailRef = useRef(null) + const portfolioBalanceRequests = useRef(createLatestRequestGuard()).current + const previousRoute = useRef(route) + const discoveryRequests = useRef(createLatestRequestGuard()).current + const balanceRequests = useRef(createLatestRequestGuard()).current + const walletSummaryRequests = useRef(createLatestRequestGuard()).current + const connectionRequests = useRef(createLatestRequestGuard()).current + const simulationRequests = useRef(createLatestRequestGuard()).current + const walletContextRevision = useRef(0) + const walletSubscriptionCleanup = useRef<(() => void) | undefined>() + const walletContextChangeHandler = useRef<(provider: InjectedEthereum, eventName: WalletContextChangeEvent, allowDisconnectedRefresh: boolean) => void>(() => undefined) + const walletConnectHandler = useRef<() => void>(() => undefined) + const walletComponentMounted = useRef(true) + const walletRenderContextKey = `${route}\u0000${selectedUniverseId ?? ''}\u0000${configuration?.chainId.toString() ?? ''}\u0000${configuration?.router ?? ''}` + const walletRenderContextKeyRef = useRef(walletRenderContextKey) + walletRenderContextKeyRef.current = walletRenderContextKey + const previousWalletSummaryRetryNonce = useRef(walletSummaryRetryNonce) + + const invalidateWalletIdentity = useCallback( + (detail: string) => { + walletContextRevision.current++ + walletSubscriptionCleanup.current?.() + walletSubscriptionCleanup.current = undefined + connectionRequests.invalidate() + balanceRequests.invalidate() + portfolioBalanceRequests.invalidate() + walletSummaryRequests.invalidate() + simulationRequests.invalidate() + accountRef.current = undefined + setWalletEthAttoEth(undefined) + setWalletRepAttoRep(undefined) + setWalletSummaryError(undefined) + setWalletSummaryErrorLabel(undefined) + setWalletSummaryUniverseId(selectedUniverseId) + setWalletSummaryStatus('disconnected') + onWalletSummaryChange(walletSummaryRefreshState(undefined, selectedUniverseId)) + setWalletClient(undefined) + setWalletProvider(undefined) + setAccount(undefined) + setBalances(undefined) + setBalanceState('error') + setBalanceError('Wallet context changed; reconnect to refresh balances and approvals') + setPortfolioBalanceError('Wallet context changed; reconnect before loading portfolio positions') + setWalletContextInvalidated(true) + setQuote(undefined) + if (!positionWorkflowLockedRef.current) { + setPositionHash(undefined) + setPositionReceiptWarning(undefined) + } + setMessage(detail) + }, + [balanceRequests, connectionRequests, onWalletSummaryChange, portfolioBalanceRequests, selectedUniverseId, simulationRequests, walletSummaryRequests], + ) + + const executeWithCurrentWalletContext = useCallback( + async (expectedAccount: Address, networkFailure: string, accountFailure: string, action: () => Promise): Promise => { + const expectedRevision = walletContextRevision.current + const provider = getInjectedEthereum() + if (provider === undefined) { + const detail = 'No injected wallet was found; reconnect before continuing' + invalidateWalletIdentity(detail) + throw new Error(detail) + } + if (provider !== walletProvider) { + const detail = 'Wallet provider changed; reconnect before continuing' + invalidateWalletIdentity(detail) + throw new Error(detail) + } + const requireUnchangedProvider = () => { + if (walletContextRevision.current !== expectedRevision || getInjectedEthereum() !== provider || accountRef.current !== expectedAccount) { + const detail = 'Wallet context changed; reconnect before continuing' + invalidateWalletIdentity(detail) + throw new Error(detail) + } + } + let chainId: number + try { + chainId = await walletChainId(provider) + } catch (error) { + invalidateWalletIdentity(networkFailure) + throw new Error(networkFailure, { cause: error }) + } + requireUnchangedProvider() + if (configuration === undefined || chainId !== configuration.chainId) { + invalidateWalletIdentity(networkFailure) + throw new Error(networkFailure) + } + let connectedAccount: Address + try { + connectedAccount = await connectWallet(provider) + } catch (error) { + invalidateWalletIdentity(accountFailure) + throw new Error(accountFailure, { cause: error }) + } + requireUnchangedProvider() + if (connectedAccount !== expectedAccount) { + invalidateWalletIdentity(accountFailure) + throw new Error(accountFailure) + } + requireUnchangedProvider() + return action() + }, + [configuration, invalidateWalletIdentity, walletProvider], + ) + + const createGuardedWalletWrite = useCallback( + (expectedAccount: Address, networkFailure: string, accountFailure: string) => { + const expectedRevision = walletContextRevision.current + const guardedWrite: GuardedWalletWrite = async write => { + if (walletContextRevision.current !== expectedRevision) throw new Error('Wallet context changed during transaction revalidation; reconnect and simulate again') + return await executeWithCurrentWalletContext(expectedAccount, networkFailure, accountFailure, async () => { + if (walletContextRevision.current !== expectedRevision) throw new Error('Wallet context changed during transaction revalidation; reconnect and simulate again') + return await write() + }) + } + return guardedWrite + }, + [executeWithCurrentWalletContext], + ) + + const refreshWalletSummaryAfterReceipt = useCallback(() => { + walletSummaryRequests.invalidate() + const currentAccount = accountRef.current + const nextSummary = walletSummaryRefreshState(currentAccount, selectedUniverseId) + setWalletEthAttoEth(undefined) + setWalletRepAttoRep(undefined) + setWalletSummaryError(undefined) + setWalletSummaryErrorLabel(undefined) + setWalletSummaryUniverseId(selectedUniverseId) + setWalletSummaryStatus(currentAccount === undefined ? 'disconnected' : 'loading') + onWalletSummaryChange(nextSummary) + setWalletSummaryReceiptNonce(current => current + 1) + }, [onWalletSummaryChange, selectedUniverseId, walletSummaryRequests]) + + const visibleMarkets = filterMarketsByUniverse(markets, selectedUniverseId) + const visiblePortfolioEntries = portfolioEntries.filter(entry => entry.market.universeId.toString() === selectedUniverseId) + const routePool = securityPoolAddressFromRoute(route) + const routeSelected = routePool === undefined ? undefined : visibleMarkets.find(market => market.pool.toLowerCase() === routePool) + const selected = routePool === undefined ? (visibleMarkets.find(market => market.pool.toLowerCase() === selectedPool?.toLowerCase()) ?? visibleMarkets[0]) : routeSelected + const selectedBalances = balanceState === 'ready' ? liveBalancesForMarket(balances, selected) : undefined + let selectedBalanceState = balanceState + if (balanceState !== 'error' && balances !== undefined && selectedBalances === undefined) selectedBalanceState = account === undefined ? 'disconnected' : 'loading' + const selectedPairInitialized = selected === undefined ? false : livePairInitialized(selected) + const nowSeconds = useQuestionClock(selected?.endTime) + const parsedAmount = useMemo(() => { + try { + return { value: parseUnits(amount), error: undefined } + } catch (error) { + return { value: undefined, error: error instanceof Error ? error.message : 'Invalid amount' } + } + }, [amount]) + + useEffect(() => { + onWalletSummaryChange({ account, ethAttoEth: walletEthAttoEth, repAttoRep: walletRepAttoRep, status: walletSummaryStatus, error: walletSummaryError, errorLabel: walletSummaryErrorLabel, universeId: walletSummaryUniverseId }) + }, [account, onWalletSummaryChange, walletEthAttoEth, walletRepAttoRep, walletSummaryError, walletSummaryErrorLabel, walletSummaryStatus, walletSummaryUniverseId]) + + useEffect(() => { + const request = walletSummaryRequests.begin() + setWalletEthAttoEth(undefined) + setWalletRepAttoRep(undefined) + setWalletSummaryError(undefined) + setWalletSummaryErrorLabel(undefined) + setWalletSummaryUniverseId(selectedUniverseId) + if (account === undefined) { + setWalletSummaryStatus('disconnected') + return + } + const availability = walletSummaryAvailability(configuration !== undefined, configurationError, discoveryState, discoveryError, selected !== undefined) + if (availability !== undefined) { + setWalletSummaryStatus(availability.status) + setWalletSummaryError(availability.error) + setWalletSummaryErrorLabel(availability.errorLabel) + return + } + if (configuration === undefined || selected === undefined) throw new Error('Wallet summary availability was resolved without a SecurityPool configuration') + if (selected.loadError !== undefined) { + setWalletSummaryStatus('error') + setWalletSummaryError(`Wallet balances could not be loaded because the selected SecurityPool is unavailable: ${selected.loadError}`) + setWalletSummaryErrorLabel('SecurityPool unavailable') + return + } + setWalletSummaryStatus('loading') + void loadWalletHeaderBalances(createTradingPublicClient(configuration), selected, account).then( + loaded => { + if (!walletSummaryRequests.isCurrent(request) || accountRef.current !== account) return + setWalletEthAttoEth(loaded.ethAttoEth) + setWalletRepAttoRep(loaded.repAttoRep) + setWalletSummaryStatus('ready') + }, + error => { + if (!walletSummaryRequests.isCurrent(request) || accountRef.current !== account) return + setWalletSummaryStatus('error') + setWalletSummaryError(publicErrorMessage(error, 'Wallet ETH and REP balances could not be loaded')) + setWalletSummaryErrorLabel('Wallet balance read failed') + }, + ) + return () => walletSummaryRequests.invalidate() + }, [account, configuration, configurationError, discoveryError, discoveryState, selected, walletSummaryReceiptNonce, walletSummaryRequests, walletSummaryRetryNonce]) + + async function refresh(nextConfiguration = configuration, requestedStart = marketPage.start, owner: WorkflowOwner | undefined = undefined) { + if (nextConfiguration === undefined) return + const request = discoveryRequests.begin() + simulationRequests.invalidate() + setQuote(undefined) + if (!positionWorkflowLockedRef.current) { + setState('idle') + if (owner !== 'position') { + setPositionHash(undefined) + setPositionReceiptWarning(undefined) + } + } + if (accountRef.current !== undefined) { + setBalanceState('loading') + setBalanceError(undefined) + setBalances(undefined) + } + if (route === 'portfolio') { + portfolioBalanceRequests.invalidate() + setPortfolioEntries([]) + setPortfolioBalanceState(accountRef.current === undefined ? 'disconnected' : 'loading') + setPortfolioBalanceError(undefined) + } + setDiscoveryState('loading') + setDiscoveryError(undefined) + try { + const client = createTradingPublicClient(nextConfiguration) + await validateLiveDeployment(client, nextConfiguration) + if (!discoveryRequests.isCurrent(request)) return + const requestedUniverseId = parsedUniverseId(selectedUniverseId) + const discovered = route === 'portfolio' || routePool !== undefined ? await discoverAllLiveMarketsInUniverse(client, nextConfiguration, requestedUniverseId, 25n, deploymentIndex) : await discoverLiveUniverseMarketPage(client, nextConfiguration, requestedUniverseId, requestedStart, 25n, deploymentIndex) + if (!discoveryRequests.isCurrent(request)) return + if (!discoveryCommitAllowed(owner, positionWorkflowLockedRef.current, liquidityWorkflowLockedRef.current)) { + setDiscoveryState('ready') + return + } + setMarkets(discovered.markets) + onUniversesChange(discovered.universeIds, discovered.selectedUniverseId) + setMarketPage({ start: discovered.start, total: discovered.total, previousStart: discovered.previousStart, nextStart: discovered.nextStart }) + setSelectedPool(currentPool => marketSelectionAfterDiscovery(discovered.markets, currentPool, requestedStart === marketPage.start)) + setDiscoveryState('ready') + } catch (error) { + if (!discoveryRequests.isCurrent(request)) return + if (!discoveryCommitAllowed(owner, positionWorkflowLockedRef.current, liquidityWorkflowLockedRef.current)) { + setDiscoveryState('ready') + return + } + const detail = publicErrorMessage(error, 'SecurityPool discovery failed') + setDiscoveryError(detail) + setDiscoveryState('error') + if (route === 'portfolio') { + setPortfolioBalanceState('error') + setPortfolioBalanceError(`SecurityPool discovery failed: ${detail}`) + } + if (accountRef.current !== undefined) { + setBalanceState('error') + setBalanceError('Market refresh failed before wallet balances could be revalidated') + } + } + } + + function refreshFromControl() { + if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) void refresh() + } + + function loadMarketPage(start: bigint | undefined) { + if (start !== undefined && !workflowLocked) void refresh(configuration, start) + } + + function focusSection(section: Readonly<{ current: HTMLElement | null }>) { + requestAnimationFrame(() => { + section.current?.focus({ preventScroll: true }) + section.current?.scrollIntoView({ block: 'start' }) + }) + } + + useEffect(() => { + if (configuration === undefined) { + discoveryRequests.invalidate() + balanceRequests.invalidate() + simulationRequests.invalidate() + setMessage(configurationError) + return + } + void refresh(configuration, 0n) + }, [configuration, configurationError, selectedUniverseId]) + + useEffect(() => { + if (previousWalletSummaryRetryNonce.current === walletSummaryRetryNonce) return + previousWalletSummaryRetryNonce.current = walletSummaryRetryNonce + const retryStart = walletSummaryDiscoveryRetryStart(discoveryState, selected !== undefined, selected?.loadError, marketPage.start) + if (configuration !== undefined && retryStart !== undefined) void refresh(configuration, retryStart) + }, [configuration, discoveryState, marketPage.start, selected, walletSummaryRetryNonce]) + + useEffect(() => { + if (positionWorkflowLockedRef.current) return + simulationRequests.invalidate() + setQuote(undefined) + setPositionHash(undefined) + setPositionReceiptWarning(undefined) + setState('idle') + if (previousRoute.current !== route) void refresh(configuration, 0n) + previousRoute.current = route + }, [route]) + + useEffect(() => { + if (selected === undefined || marketAcceptsNewRisk(selected, nowSeconds)) return + simulationRequests.invalidate() + setQuote(undefined) + if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) setState('idle') + }, [nowSeconds, selected]) + + useEffect( + () => () => { + walletComponentMounted.current = false + connectionRequests.invalidate() + walletContextRevision.current++ + walletSubscriptionCleanup.current?.() + walletSubscriptionCleanup.current = undefined + }, + [], + ) + + useEffect(() => { + const request = portfolioBalanceRequests.begin() + if (route !== 'portfolio') { + setPortfolioEntries([]) + setPortfolioBalanceState('disconnected') + setPortfolioBalanceError(undefined) + return + } + const emptyEntries = visibleMarkets.map(market => ({ market, balances: undefined, error: market.loadError })) + setPortfolioEntries(emptyEntries) + if (configuration === undefined || account === undefined) { + setPortfolioBalanceState(walletContextInvalidated ? 'error' : 'disconnected') + setPortfolioBalanceError(walletContextInvalidated ? 'Wallet context changed; reconnect before loading portfolio positions' : undefined) + return + } + setPortfolioBalanceState('loading') + setPortfolioBalanceError(undefined) + const client = createTradingPublicClient(configuration) + void mapWithConcurrency(visibleMarkets, 6, async (market, index) => { + if (market.loadError !== undefined) return { market, balances: undefined, error: market.loadError } + let entry: PortfolioBalanceEntry + try { + const loaded = await loadLiveBalances(client, market, account, configuration.router) + entry = { market, balances: liveBalancesForMarket(loaded, market), error: undefined } + } catch (error) { + entry = { market, balances: undefined, error: publicErrorMessage(error, 'Balance refresh failed') } + } + if (portfolioBalanceRequests.isCurrent(request) && accountRef.current === account) setPortfolioEntries(current => current.map((currentEntry, currentIndex) => (currentIndex === index ? entry : currentEntry))) + return entry + }) + .then(entries => { + if (!portfolioBalanceRequests.isCurrent(request) || accountRef.current !== account) return + setPortfolioEntries(entries) + setPortfolioBalanceState('ready') + setPortfolioBalanceError(undefined) + }) + .catch(error => { + if (!portfolioBalanceRequests.isCurrent(request) || accountRef.current !== account) return + setPortfolioBalanceState('error') + setPortfolioBalanceError(publicErrorMessage(error, 'Portfolio balance refresh failed')) + }) + return () => portfolioBalanceRequests.invalidate() + }, [account, configuration, markets, portfolioBalanceRequests, portfolioRefreshNonce, route, selectedUniverseId, walletContextInvalidated]) + + useEffect(() => { + const request = balanceRequests.begin() + if (route === 'portfolio') { + setBalances(undefined) + setBalanceState('disconnected') + setBalanceError(undefined) + return + } + if (configuration === undefined || account === undefined || selected === undefined || selected.loadError !== undefined) { + setBalances(undefined) + setBalanceState(walletContextInvalidated || selected?.loadError !== undefined ? 'error' : 'disconnected') + setBalanceError(selected?.loadError) + return + } + setBalanceState('loading') + setBalanceError(undefined) + setBalances(undefined) + void loadLiveBalances(createTradingPublicClient(configuration), selected, account, configuration.router).then( + loaded => { + if (balanceRequests.isCurrent(request)) { + setBalances(loaded) + setBalanceState('ready') + setBalanceError(undefined) + } + }, + error => { + if (balanceRequests.isCurrent(request)) { + setBalanceState('error') + setBalanceError(publicErrorMessage(error, 'Balance refresh failed')) + } + }, + ) + return () => balanceRequests.invalidate() + }, [account, configuration, route, selected, walletContextInvalidated]) + + async function retryBalances() { + if (configuration === undefined || selected === undefined) return + if (account === undefined) { + await connect() + return + } + const request = balanceRequests.begin() + simulationRequests.invalidate() + setQuote(undefined) + if (!positionWorkflowLockedRef.current) setState('idle') + setBalanceState('loading') + setBalanceError(undefined) + setBalances(undefined) + try { + const loaded = await loadLiveBalances(createTradingPublicClient(configuration), selected, account, configuration.router) + if (!balanceRequests.isCurrent(request)) return + setBalances(loaded) + setBalanceState('ready') + setMessage(undefined) + } catch (error) { + if (!balanceRequests.isCurrent(request)) return + setBalanceState('error') + setBalanceError(publicErrorMessage(error, 'Balance refresh failed')) + } + } + + async function retryPortfolioBalances() { + if (account === undefined) { + await connect() + return + } + if (discoveryState === 'error') { + await refresh(configuration, 0n) + return + } + setPortfolioRefreshNonce(value => value + 1) + } + + async function connect() { + if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) return + if (accountRef.current !== undefined || walletClient !== undefined) invalidateWalletIdentity('Reconnecting wallet…') + const request = connectionRequests.begin() + const expectedRenderContextKey = walletRenderContextKey + try { + const provider = getInjectedEthereum() + if (provider === undefined) throw new Error('No injected wallet was found') + const requireCurrentConnection = () => { + if (!walletComponentMounted.current || !connectionRequests.isCurrent(request)) return false + if (getInjectedEthereum() !== provider) throw new Error('Wallet provider changed; reconnect before continuing') + if (walletRenderContextKeyRef.current !== expectedRenderContextKey) { + walletConnectHandler.current() + return false + } + return true + } + if (configuration === undefined) throw new Error('Deployment configuration is unavailable') + let chainId = await walletChainId(provider) + if (!requireCurrentConnection()) return + if (chainId !== configuration.chainId) { + await switchWalletChain(provider, configuration.chainId) + if (!requireCurrentConnection()) return + chainId = await walletChainId(provider) + if (!requireCurrentConnection()) return + } + if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) + const connected = await connectWallet(provider) + if (!requireCurrentConnection()) return + walletSubscriptionCleanup.current?.() + walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, (eventName: WalletContextChangeEvent) => { + walletContextChangeHandler.current(provider, eventName, false) + }) + const confirmedChainId = await walletChainId(provider) + if (!requireCurrentConnection()) return + if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) + const confirmedAccount = await connectWallet(provider) + if (!requireCurrentConnection()) return + if (confirmedAccount !== connected) throw new Error('Wallet account changed while connecting; reconnect to continue') + balanceRequests.invalidate() + walletSummaryRequests.invalidate() + simulationRequests.invalidate() + accountRef.current = connected + setWalletEthAttoEth(undefined) + setWalletRepAttoRep(undefined) + setWalletSummaryError(undefined) + setWalletSummaryErrorLabel(undefined) + setWalletSummaryUniverseId(selectedUniverseId) + setWalletSummaryStatus('loading') + onWalletSummaryChange(walletSummaryRefreshState(connected, selectedUniverseId)) + setBalances(undefined) + setBalanceState('loading') + setBalanceError(undefined) + setWalletContextInvalidated(false) + walletContextRevision.current++ + setAccount(connected) + setWalletClient(createTradingWalletClient(provider, connected)) + setWalletProvider(provider) + setMessage(undefined) + await refresh(configuration) + } catch (error) { + if (!connectionRequests.isCurrent(request)) return + invalidateWalletIdentity(publicErrorMessage(error, 'Wallet connection failed')) + } + } + walletConnectHandler.current = () => void connect() + + async function refreshWalletContextAfterEvent(provider: InjectedEthereum, eventName: WalletContextChangeEvent, allowDisconnectedRefresh: boolean) { + const contextLabel = eventName === 'accountsChanged' ? 'Wallet account changed' : 'Wallet network changed' + if ((!allowDisconnectedRefresh && accountRef.current === undefined) || positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) { + invalidateWalletIdentity(`${contextLabel}. Reconnect before simulating or submitting.`) + if (!positionWorkflowLockedRef.current && !liquidityWorkflowLockedRef.current) setState('error') + return + } + invalidateWalletIdentity(`${contextLabel}. Refreshing wallet context…`) + const request = connectionRequests.begin() + const expectedRenderContextKey = walletRenderContextKey + try { + const requireCurrentConnection = () => { + if (!walletComponentMounted.current || !connectionRequests.isCurrent(request)) return false + if (getInjectedEthereum() !== provider) throw new Error('Wallet provider changed; reconnect before continuing') + if (walletRenderContextKeyRef.current !== expectedRenderContextKey) { + walletContextChangeHandler.current(provider, eventName, true) + return false + } + return true + } + if (configuration === undefined) throw new Error('Deployment configuration is unavailable') + const chainId = await walletChainId(provider) + if (!requireCurrentConnection()) return + if (chainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) + const connected = await connectWallet(provider) + if (!requireCurrentConnection()) return + walletSubscriptionCleanup.current?.() + walletSubscriptionCleanup.current = subscribeToWalletContextChanges(provider, changedEventName => { + walletContextChangeHandler.current(provider, changedEventName, false) + }) + const confirmedChainId = await walletChainId(provider) + if (!requireCurrentConnection()) return + if (confirmedChainId !== configuration.chainId) throw new Error(`Wallet must use ${configuration.chainName}`) + const confirmedAccount = await connectWallet(provider) + if (!requireCurrentConnection()) return + if (confirmedAccount !== connected) throw new Error('Wallet account changed while refreshing; reconnect to continue') + balanceRequests.invalidate() + walletSummaryRequests.invalidate() + simulationRequests.invalidate() + accountRef.current = connected + setWalletEthAttoEth(undefined) + setWalletRepAttoRep(undefined) + setWalletSummaryError(undefined) + setWalletSummaryErrorLabel(undefined) + setWalletSummaryUniverseId(selectedUniverseId) + setWalletSummaryStatus('loading') + onWalletSummaryChange(walletSummaryRefreshState(connected, selectedUniverseId)) + setBalances(undefined) + setBalanceState('loading') + setBalanceError(undefined) + setWalletContextInvalidated(false) + walletContextRevision.current++ + setAccount(connected) + setWalletClient(createTradingWalletClient(provider, connected)) + setWalletProvider(provider) + setMessage(undefined) + setState('idle') + await refresh(configuration) + } catch (error) { + if (!connectionRequests.isCurrent(request)) return + invalidateWalletIdentity(`${contextLabel}: ${publicErrorMessage(error, 'wallet refresh failed')}`) + setState('error') + } + } + walletContextChangeHandler.current = (provider, eventName, allowDisconnectedRefresh) => void refreshWalletContextAfterEvent(provider, eventName, allowDisconnectedRefresh) + + async function refreshBalancesAfterApproval(label: string, expectedMarket: LiveMarket, expectedAccount: Address, request = balanceRequests.begin()): Promise<'ready' | 'refresh-error' | 'context-changed'> { + if (configuration === undefined || accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' + setBalances(undefined) + setBalanceState('loading') + setBalanceError(undefined) + try { + const loaded = await loadLiveBalances(createTradingPublicClient(configuration), expectedMarket, expectedAccount, configuration.router) + if (accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' + setBalances(loaded) + setBalanceState('ready') + setBalanceError(undefined) + return 'ready' + } catch (error) { + if (accountRef.current !== expectedAccount || !balanceRequests.isCurrent(request)) return 'context-changed' + const detail = publicErrorMessage(error, 'Balance refresh failed') + setBalanceState('error') + setBalanceError(`${label} confirmed, but balances could not be refreshed: ${detail}`) + return 'refresh-error' + } + } + + async function simulate() { + const slippageBps = parseSlippageBps(slippage) + const validityMinutes = parseTransactionValidityMinutes(transactionValidityMinutes) + if (configuration === undefined || selected === undefined || account === undefined || walletClient === undefined || parsedAmount.value === undefined || parsedAmount.value === 0n || slippageBps === undefined || validityMinutes === undefined) return + const request = simulationRequests.begin() + try { + setState('simulating') + setPositionHash(undefined) + setMessage(undefined) + const context = { account, configuration, walletClient } + const nextQuote: Quote = + mode === 'entry' + ? { ...context, kind: 'entry', value: await simulateEntry(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } + : { ...context, kind: 'exit', value: await simulateExit(walletClient, configuration, selected, account, side, parsedAmount.value, validityMinutes, slippageBps) } + if (!simulationRequests.isCurrent(request)) return + setQuote(nextQuote) + setState('ready') + } catch (error) { + if (!simulationRequests.isCurrent(request)) return + setQuote(undefined) + setState('error') + setMessage(publicErrorMessage(error, 'Router simulation failed')) + } + } + + async function approve() { + if (configuration === undefined || selected === undefined || account === undefined || walletClient === undefined) return + if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current || !positionWorkflow.begin()) return + updatePositionWorkflowLock(true) + setState('preparing') + setMessage(undefined) + setPositionReceiptWarning(undefined) + setPositionHash(undefined) + const balanceRequest = balanceRequests.begin() + let broadcastHash: Hash | undefined + let receiptKnown = false + let keepLocked = false + try { + broadcastHash = await createGuardedWalletWrite( + account, + 'Wallet network changed; switch back before approving', + 'Wallet account changed; reconnect before approving', + )(async () => { + setState('approval') + return await approveRouter(walletClient, selected, configuration, account) + }) + setPositionHash(broadcastHash) + setState('approval-pending') + const receipt = await observeKnownReceipt(walletClient.waitForTransactionReceipt({ hash: broadcastHash }), refreshWalletSummaryAfterReceipt) + receiptKnown = true + if (receipt.status === 'reverted') { + if (!balanceRequests.isCurrent(balanceRequest)) { + setState('error') + setMessage(current => `${current ?? 'Wallet context changed.'} Approval transaction reverted.`) + return + } + throw new Error('Approval transaction reverted') + } + setState('approval-confirmed') + if (!balanceRequests.isCurrent(balanceRequest)) return + const refreshResult = await refreshBalancesAfterApproval('Share-token approval', selected, account, balanceRequest) + if (refreshResult !== 'ready') return + setPositionReceiptWarning(undefined) + setMessage(undefined) + } catch (error) { + if (!balanceRequests.isCurrent(balanceRequest)) { + if (broadcastHash !== undefined && !receiptKnown) { + keepLocked = true + setState('approval-pending') + setPositionReceiptWarning(broadcastUncertainMessage('Share-token approval', broadcastHash)) + } else setState('error') + return + } + const failure = approvalFailureTransition('Share-token approval', broadcastHash, receiptKnown, error, 'Approval failed') + keepLocked = failure.keepLocked + setState(failure.state === 'pending' ? 'approval-pending' : failure.state) + setMessage(failure.message) + setPositionReceiptWarning(failure.warning) + } finally { + positionWorkflow.finish() + if (!keepLocked) updatePositionWorkflowLock(false) + } + } + + async function submit() { + if (configuration === undefined || account === undefined || walletClient === undefined || quote === undefined) return + if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current || !positionWorkflow.begin()) return + updatePositionWorkflowLock(true) + setState('preparing') + setPositionReceiptWarning(undefined) + let broadcastHash: Hash | undefined + let receiptKnown = false + let keepLocked = false + try { + const quotedAmount = quote.kind === 'entry' ? quote.value.amount : quote.value.completeSets + if ( + selected === undefined || + quote.account !== account || + quote.walletClient !== walletClient || + quote.configuration.chainId !== configuration.chainId || + quote.configuration.router !== configuration.router || + quote.value.market.pool !== selected.pool || + quote.value.side !== side || + quote.kind !== mode || + parsedAmount.value !== quotedAmount + ) { + throw new Error('Trade inputs changed; simulate the current selection again') + } + simulationRequests.invalidate() + await executeWithCurrentWalletContext(account, 'Wallet network changed; switch back before submitting', 'Wallet account changed; reconnect and simulate again', async () => undefined) + const guardedPositionWrite = createGuardedWalletWrite(account, 'Wallet network changed during transaction revalidation; reconnect and simulate again', 'Wallet account changed during transaction revalidation; reconnect and simulate again') + const guardedWrite: GuardedWalletWrite = async write => + await guardedPositionWrite(async () => { + setState('submitting') + return await write() + }) + broadcastHash = quote.kind === 'entry' ? await submitFreshEntry(walletClient, configuration, account, quote.value, guardedWrite) : await submitFreshExit(walletClient, configuration, account, quote.value, guardedWrite) + setPositionHash(broadcastHash) + setState('pending') + const receipt = await observeKnownReceipt(walletClient.waitForTransactionReceipt({ hash: broadcastHash }), refreshWalletSummaryAfterReceipt) + receiptKnown = true + if (receipt.status === 'reverted') throw new Error('Transaction reverted') + setQuote(undefined) + setPositionReceiptWarning(undefined) + setState('confirmed') + await refresh(configuration, marketPage.start, 'position') + } catch (error) { + if (broadcastHash !== undefined && !receiptKnown) { + keepLocked = true + setState('pending') + setMessage(undefined) + setPositionReceiptWarning(broadcastUncertainMessage('Transaction', broadcastHash)) + } else { + const failure = failedSubmissionTransition(error, 'Transaction failed') + setQuote(failure.quote) + setState(failure.state) + setMessage(failure.message) + setPositionReceiptWarning(undefined) + } + } finally { + positionWorkflow.finish() + if (!keepLocked) updatePositionWorkflowLock(false) + } + } + + function resetPositionInput(update: () => void) { + if (positionWorkflowLockedRef.current) return + simulationRequests.invalidate() + update() + setQuote(undefined) + setPositionHash(undefined) + setState('idle') + } + + function selectMarket(market: LiveMarket) { + if (positionWorkflowLockedRef.current || liquidityWorkflowLockedRef.current) return + balanceRequests.invalidate() + simulationRequests.invalidate() + setBalances(undefined) + setBalanceState(account === undefined ? 'disconnected' : 'loading') + setBalanceError(undefined) + setSelectedPool(market.pool) + setQuote(undefined) + setState('idle') + setPositionHash(undefined) + setPositionReceiptWarning(undefined) + focusSection(marketDetailRef) + } + + return { + wallet: { + account, + walletClient, + connect, + refreshWalletSummaryAfterReceipt, + walletContextIsCurrent: (expectedAccount: Address) => accountRef.current === expectedAccount, + executeWithCurrentWalletContext, + createGuardedWalletWrite, + }, + balances: { + balanceError, + portfolioBalanceState, + portfolioBalanceError, + visiblePortfolioEntries, + selectedBalances, + selectedBalanceState, + retryBalances, + retryPortfolioBalances, + refreshBalancesAfterApproval, + }, + discovery: { + visibleMarkets, + selected, + selectedPairInitialized, + routePool, + discoveryState, + discoveryError, + marketPage, + marketListRef, + marketDetailRef, + nowSeconds, + refresh, + refreshFromControl, + loadMarketPage, + focusSection, + selectMarket, + }, + position: { + parsedAmount, + mode, + side, + amount, + slippage, + transactionValidityMinutes, + quote, + state, + positionHash, + message, + positionReceiptWarning, + simulate, + approve, + submit, + setMode: (value: 'entry' | 'exit') => resetPositionInput(() => setMode(value)), + setSide: (value: 'YES' | 'NO') => resetPositionInput(() => setSide(value)), + setAmount: (value: string) => resetPositionInput(() => setAmount(value)), + setSlippage: (value: string) => resetPositionInput(() => setSlippage(value)), + setTransactionValidityMinutes: (value: string) => resetPositionInput(() => setTransactionValidityMinutes(value)), + }, + workflow: { + workflowLocked, + updateLiquidityWorkflowLock, + }, + } +} From ed144c5874cb3996700f34616920503f126f518e Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:08:09 +0000 Subject: [PATCH 2/2] Fix integration regressions after main merge --- augurScan/src/indexer-runtime.ts | 8 +++--- augurScan/src/indexer.ts | 22 ++++++++------- augurScan/src/time.ts | 8 +++++- augurScan/tests/indexer-lifecycle.test.ts | 3 +- augurScan/tests/time.test.ts | 11 +++++++- bots/liquidator/README.md | 7 +++-- bots/liquidator/bun.lock | 10 ++----- bots/liquidator/src/dashboard/dashboard.ts | 5 +++- bots/liquidator/src/execution/recovery.ts | 4 +-- .../tests/core/execution-safety.test.ts | 6 +--- bots/open-oracle-arbitrager/README.md | 4 ++- bots/open-oracle-arbitrager/bun.lock | 4 ++- .../contracts/OpenOracleArbitrageExecutor.sol | 8 ++---- .../src/contracts/artifacts.generated.ts | 28 +++++++++---------- .../src/execution/recovery-support.ts | 2 +- .../coordinator-report-discovery.test.ts | 22 +++++++++------ .../tests/execution/create2-executor.test.ts | 2 +- bots/shared/tests/shared-primitives.test.ts | 4 +++ shared/package.json | 1 + trading/ui/ts/features/LiveTrading.tsx | 1 + 20 files changed, 93 insertions(+), 67 deletions(-) diff --git a/augurScan/src/indexer-runtime.ts b/augurScan/src/indexer-runtime.ts index 433b85889..5a0d8adc4 100644 --- a/augurScan/src/indexer-runtime.ts +++ b/augurScan/src/indexer-runtime.ts @@ -2,6 +2,7 @@ import { type AddressActivity, DatabaseConsistencyError, databaseConsistencyDiag import { errorChainIncludes } from './error-chain.ts' import { type Address, type Hash, type Log, type PublicClient, type TransactionReceipt, zeroAddress } from './ethereum.ts' import { RpcRequestMethodError, rpcQueueSaturationFrom } from './rpc-request-queue.ts' +import { bigintToSafeNumber } from './time.ts' import type { ContractMetadata, StoredLog } from './types.ts' export const waitForIndexerDelay = (milliseconds: number, signal: AbortSignal): Promise => @@ -216,9 +217,8 @@ export const requireLogPosition = (log: Log): { transactionHash: Hash; transacti ) { throw new Error('RPC returned a pending log while indexing a confirmed block') } - const transactionIndex = Number(log.transactionIndex) - const logIndex = Number(log.logIndex) - if (!Number.isSafeInteger(transactionIndex) || !Number.isSafeInteger(logIndex)) throw new Error('RPC returned a log position outside the safe integer range') + const transactionIndex = bigintToSafeNumber(log.transactionIndex, 'RPC log transaction index') + const logIndex = bigintToSafeNumber(log.logIndex, 'RPC log index') return { transactionHash: log.transactionHash, transactionIndex, @@ -359,7 +359,7 @@ export const indexerProgressMessage = ( const progress = state === 'live' ? 'caught up' - : `${completion.remainingBlocks} blocks behind; ${blocksPerSecond === undefined ? 'estimating ETA' : `ETA ${compactIndexerDuration(Number(completion.remainingBlocks) / blocksPerSecond)}`}` + : `${completion.remainingBlocks} blocks behind; ${blocksPerSecond === undefined ? 'estimating ETA' : `ETA ${compactIndexerDuration(bigintToSafeNumber(completion.remainingBlocks, 'Remaining block count') / blocksPerSecond)}`}` return `[${networkId}] indexer state: ${state}; ${indexed}; observed head #${observedHead}; ${completion.percentage}% complete; ${progress}` } diff --git a/augurScan/src/indexer.ts b/augurScan/src/indexer.ts index 992d360d8..a896758b7 100644 --- a/augurScan/src/indexer.ts +++ b/augurScan/src/indexer.ts @@ -107,7 +107,7 @@ import { zeroAddress, } from './ethereum.ts' import { decodeAction, decodeLogRecord, discoveriesFrom, tokenAddressesFrom } from './metadata.ts' -import { unixSecondsToDate } from './time.ts' +import { bigintToSafeNumber, unixSecondsToDate } from './time.ts' import type { ContractMetadata, ManifestContract, NetworkConfig, StoredLog, TokenMetadata } from './types.ts' import { uniswapV4PoolConfigurations, uniswapV4PoolId } from './uniswap.ts' @@ -407,7 +407,7 @@ export const queryAdaptiveLogRange = async ( if (!Number.isSafeInteger(maximumBlockCount) || maximumBlockCount <= 0) throw new Error('The maximum log range must be a positive safe integer') if (fromBlock > maximumToBlock) throw new Error('The log range start must not exceed its end') const remaining = maximumToBlock - fromBlock + 1n - let blockCount = remaining < BigInt(maximumBlockCount) ? Number(remaining) : maximumBlockCount + let blockCount = remaining < BigInt(maximumBlockCount) ? bigintToSafeNumber(remaining, 'Remaining log range') : maximumBlockCount while (true) { const toBlock = fromBlock + BigInt(blockCount - 1) try { @@ -647,7 +647,7 @@ class NetworkIndexer { const previousSample = this.#progressSample let blocksPerSecond = previousSample?.blocksPerSecond if (previousSample !== undefined && endBlock > previousSample.block && now - previousSample.sampledAt >= 1_000) { - const observedRate = Number(endBlock - previousSample.block) / ((now - previousSample.sampledAt) / 1_000) + const observedRate = bigintToSafeNumber(endBlock - previousSample.block, 'Indexer progress block count') / ((now - previousSample.sampledAt) / 1_000) blocksPerSecond = blocksPerSecond === undefined ? observedRate : blocksPerSecond * 0.7 + observedRate * 0.3 this.#progressSample = { block: endBlock, sampledAt: now, blocksPerSecond } } else if (previousSample === undefined || endBlock < previousSample.block) { @@ -773,7 +773,7 @@ class NetworkIndexer { ? undefined : { ...deployment, - timestamp: new Date(Number((await readWithinBudget(() => this.#getBlockHeader(deployment.block))).timestamp) * 1_000), + timestamp: unixSecondsToDate((await readWithinBudget(() => this.#getBlockHeader(deployment.block))).timestamp, 'Deployment block timestamp'), } } catch (error) { if (rpcQueueSaturationFrom(error) !== undefined) throw error @@ -841,7 +841,10 @@ class NetworkIndexer { let headers: readonly RpcBlockHeader[] try { segment = await this.#getNextLogSegment(nextBlock, observedHead, initialContracts) - const blockNumbers = Array.from({ length: Number(segment.toBlock - nextBlock + 1n) }, (_, index) => nextBlock + BigInt(index)) + const blockNumbers = Array.from( + { length: bigintToSafeNumber(segment.toBlock - nextBlock + 1n, 'Log segment block count') }, + (_, index) => nextBlock + BigInt(index), + ) headers = await mapLimit(blockNumbers, 20, (blockNumber) => this.#getBlockHeader(blockNumber)) const endHeader = headers.at(-1) if (endHeader === undefined) throw new Error(`RPC did not return block ${segment.toBlock}`) @@ -876,7 +879,7 @@ class NetworkIndexer { expectedParentHash = (await this.#getBlockHeader(nextBlock - 1n)).hash } while (nextBlock <= end && !this.#signal.aborted) { - const header = headers[Number(nextBlock - batchStart)] + const header = headers[bigintToSafeNumber(nextBlock - batchStart, 'Block header offset')] if (header === undefined) throw new Error(`RPC did not return block ${nextBlock}`) const contractsBeforeBlock = new Set(contracts.keys()) let indexed: { block: IndexedBlock; contracts: Map; tokenMetadata: Map } @@ -906,7 +909,7 @@ class NetworkIndexer { this.#mergeLogs( logsByBlock, await this.#getAllLogs(nextBlock + 1n, end, additionalAddresses, indexed.contracts, (blockNumber) => { - const expected = headers[Number(blockNumber - batchStart)] + const expected = headers[bigintToSafeNumber(blockNumber - batchStart, 'Log block header offset')] if (expected === undefined) throw new Error(`RPC did not return block ${blockNumber}`) return expected.hash }), @@ -1044,7 +1047,7 @@ class NetworkIndexer { rangeEnd, this.#network.startBlock, (address, blockNumber) => this.#client.getBytecode({ address, blockNumber }), - async (blockNumber) => new Date(Number((await this.#getBlockHeader(blockNumber)).timestamp) * 1_000), + async (blockNumber) => unixSecondsToDate((await this.#getBlockHeader(blockNumber)).timestamp, 'Deployment scan block timestamp'), (contract, error) => console.warn( `[${this.#network.id}] deployment-aware log scan fell back for ${contract.label} (${contract.address}): ${this.#rpcFailureReason(error)}`, @@ -1171,8 +1174,7 @@ class NetworkIndexer { if (receipt.status !== 'success') throw new ChainContinuityError(`Log-selected transaction ${transaction.hash} did not succeed`) if (transaction.blockHash !== block.hash || transaction.blockNumber !== number || transaction.transactionIndex === undefined) throw new ChainContinuityError(`Transaction ${transaction.hash} no longer belongs to block ${number}`) - const transactionIndex = Number(transaction.transactionIndex) - if (!Number.isSafeInteger(transactionIndex)) throw new Error(`Transaction ${transaction.hash} index exceeds the safe integer range`) + const transactionIndex = bigintToSafeNumber(transaction.transactionIndex, `Transaction ${transaction.hash} index`) receipts.push(receipt) receiptByHash.set(receipt.transactionHash, receipt) transactionByHash.set(transaction.hash, { transaction, index: transactionIndex }) diff --git a/augurScan/src/time.ts b/augurScan/src/time.ts index 28fc11e69..ea3b3a3ca 100644 --- a/augurScan/src/time.ts +++ b/augurScan/src/time.ts @@ -1,10 +1,16 @@ const minimumDateMilliseconds = -8_640_000_000_000_000n const maximumDateMilliseconds = 8_640_000_000_000_000n +export const bigintToSafeNumber = (value: bigint, name: string): number => { + const result = Number.parseInt(value.toString(), 10) + if (!Number.isSafeInteger(result)) throw new Error(`${name} is outside the safe integer range`) + return result +} + export const unixSecondsToDate = (seconds: bigint, name = 'Timestamp'): Date => { const milliseconds = seconds * 1000n if (milliseconds < minimumDateMilliseconds || milliseconds > maximumDateMilliseconds) { throw new Error(`${name} is outside the supported timestamp range`) } - return new Date(Number.parseInt(milliseconds.toString(), 10)) + return new Date(bigintToSafeNumber(milliseconds, name)) } diff --git a/augurScan/tests/indexer-lifecycle.test.ts b/augurScan/tests/indexer-lifecycle.test.ts index 35aba45ea..39753277f 100644 --- a/augurScan/tests/indexer-lifecycle.test.ts +++ b/augurScan/tests/indexer-lifecycle.test.ts @@ -61,6 +61,7 @@ import { withRpcRequestQueue, withVerifiedProvider, } from '../src/indexer.ts' +import { unixSecondsToDate } from '../src/time.ts' import type { ContractMetadata, StoredLog, TokenMetadata } from '../src/types.ts' import { isSupportedUniswapV4Market, uniswapV4PoolId } from '../src/uniswap.ts' @@ -850,7 +851,7 @@ describe('network indexer lifecycle', () => { checkedBlocks.push(block) return candidate === address && block >= 75n ? '0x01' : undefined }, - async (block) => new Date(Number(block) * 1_000), + async (block) => unixSecondsToDate(block), ) expect(plan.inputs).toEqual([{ address, fromBlock: 75n, startBlock: 75n }]) expect(plan.observations).toEqual([ diff --git a/augurScan/tests/time.test.ts b/augurScan/tests/time.test.ts index ffe47e015..08f0b03c4 100644 --- a/augurScan/tests/time.test.ts +++ b/augurScan/tests/time.test.ts @@ -1,5 +1,14 @@ import { describe, expect, test } from 'bun:test' -import { unixSecondsToDate } from '../src/time.ts' +import { bigintToSafeNumber, unixSecondsToDate } from '../src/time.ts' + +describe('bigintToSafeNumber', () => { + test('preserves safe integer bounds and rejects values outside them', () => { + expect(bigintToSafeNumber(BigInt(Number.MIN_SAFE_INTEGER), 'Value')).toBe(Number.MIN_SAFE_INTEGER) + expect(bigintToSafeNumber(BigInt(Number.MAX_SAFE_INTEGER), 'Value')).toBe(Number.MAX_SAFE_INTEGER) + expect(() => bigintToSafeNumber(BigInt(Number.MIN_SAFE_INTEGER) - 1n, 'Block count')).toThrow('Block count is outside the safe integer range') + expect(() => bigintToSafeNumber(BigInt(Number.MAX_SAFE_INTEGER) + 1n, 'Block count')).toThrow('Block count is outside the safe integer range') + }) +}) describe('unixSecondsToDate', () => { test('preserves exact whole-second timestamps', () => { diff --git a/bots/liquidator/README.md b/bots/liquidator/README.md index c0ecce57a..8a3da7060 100644 --- a/bots/liquidator/README.md +++ b/bots/liquidator/README.md @@ -57,12 +57,15 @@ elsewhere. ### Bun -From `bots/liquidator`: +From the monorepo root, install the root package before entering the liquidator +project. The root package provides the shared Ethereum runtime used by the bot: ```bash +bun install --frozen-lockfile +cd bots/liquidator +bun install --frozen-lockfile install -d -m 700 .state install -m 600 config/operator.example.json .state/operator.json -bun install --frozen-lockfile bun run run ``` diff --git a/bots/liquidator/bun.lock b/bots/liquidator/bun.lock index 1d8e3e26c..493e51635 100644 --- a/bots/liquidator/bun.lock +++ b/bots/liquidator/bun.lock @@ -48,7 +48,7 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@zoltar/bot-shared": ["@zoltar/bot-shared@file:../shared", { "dependencies": { "@noble/hashes": "2.2.0", "ccxt": "4.5.70", "micro-eth-signer": "0.18.1" }, "devDependencies": { "@biomejs/biome": "2.3.11", "bun-types": "1.3.11", "typescript": "5.8.2" } }], + "@zoltar/bot-shared": ["@zoltar/bot-shared@file:../shared", { "dependencies": { "@zoltar/shared": "file:../../shared", "ccxt": "4.5.70" }, "devDependencies": { "@biomejs/biome": "2.3.11", "bun-types": "1.3.11", "typescript": "5.8.2" } }], "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], @@ -64,10 +64,6 @@ "happy-dom": ["happy-dom@20.10.6", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw=="], - "micro-eth-signer": ["micro-eth-signer@0.18.1", "", { "dependencies": { "@noble/curves": "^2.0.0", "@noble/hashes": "^2.0.0", "micro-packed": "^0.8.0" } }, "sha512-vXKhCZxrytpl+dXR9JeaE41ZVFndi7wCKc1Jd22smOMAeDErvRcXaTxhYUf1yQxis4n2kIdv/pH7iuf+5/cj+Q=="], - - "micro-packed": ["micro-packed@0.8.0", "", { "dependencies": { "@scure/base": "2.0.0" } }, "sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A=="], - "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], @@ -80,8 +76,8 @@ "ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], - "ccxt/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + "@zoltar/bot-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", {}], - "micro-packed/@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="], + "ccxt/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], } } diff --git a/bots/liquidator/src/dashboard/dashboard.ts b/bots/liquidator/src/dashboard/dashboard.ts index 42f8dbd1e..bd4200d41 100644 --- a/bots/liquidator/src/dashboard/dashboard.ts +++ b/bots/liquidator/src/dashboard/dashboard.ts @@ -947,7 +947,10 @@ function render(snapshot: Snapshot) { recoveryGuidance.hidden = snapshot.paused lastScan.textContent = snapshot.lastScanAt === undefined ? (snapshot.scanning ? 'Scanning factory registry…' : 'Waiting for first scan') : `Last scan ${new Date(snapshot.lastScanAt).toLocaleString()}` walletAddress.textContent = snapshot.wallet ?? 'No active signer' - setGlobalError(snapshot.error === undefined ? undefined : snapshot.status === 'connectivity-degraded' ? 'RPC connectivity is degraded. Execution is blocked and the bot will retry automatically.' : `${scanFailureDetail(snapshot.error)} Automatic retry is active. Check the bot logs if the next cycle also fails.`, 'Scan failed') + setGlobalError( + snapshot.error === undefined ? undefined : snapshot.status === 'connectivity-degraded' ? 'RPC connectivity is degraded. Execution is blocked and the bot will retry automatically.' : `${scanFailureDetail(snapshot.error)} Automatic retry is active. Check the bot logs if the next cycle also fails.`, + 'Scan failed', + ) renderMetrics(snapshot) renderAlerts(snapshot) renderCentralizedMarket(snapshot) diff --git a/bots/liquidator/src/execution/recovery.ts b/bots/liquidator/src/execution/recovery.ts index a2319f44f..4fa37bef0 100644 --- a/bots/liquidator/src/execution/recovery.ts +++ b/bots/liquidator/src/execution/recovery.ts @@ -34,9 +34,7 @@ function recoveryReaders(settings: OperatorSettings, wallet: WalletClient, hash: Hex, pool = createRpcEndpointPool([settings.connectivity.readRpcUrl, ...settings.connectivity.quorumRpcUrls])) { const readers = recoveryReaders(settings, wallet, pool) const result = await settledQuorumValue<{ - evidence: - | { blockHash: Hex; blockNumber: bigint; hash: Hex; logs: { address: `0x${string}`; data: Hex; topics: readonly Hex[] }[]; status: 'reverted' | 'success' } - | undefined + evidence: { blockHash: Hex; blockNumber: bigint; hash: Hex; logs: { address: `0x${string}`; data: Hex; topics: readonly Hex[] }[]; status: 'reverted' | 'success' } | undefined receipt: TransactionReceipt | undefined }>( `receipt ${hash}`, diff --git a/bots/liquidator/tests/core/execution-safety.test.ts b/bots/liquidator/tests/core/execution-safety.test.ts index 33045b5c0..27ab400ff 100644 --- a/bots/liquidator/tests/core/execution-safety.test.ts +++ b/bots/liquidator/tests/core/execution-safety.test.ts @@ -142,11 +142,7 @@ describe('liquidator execution safety', () => { } }) test('fails closed when an available execution reader disagrees on wallet REP balance', async () => { - const observations = await Promise.allSettled([ - Promise.resolve({ endpoint: 'rpc-a', walletRepByToken: [['0xrep', 10n]] }), - Promise.resolve({ endpoint: 'rpc-b', walletRepByToken: [['0xrep', 10n]] }), - Promise.resolve({ endpoint: 'rpc-c', walletRepByToken: [['0xrep', 11n]] }), - ]) + const observations = await Promise.allSettled([Promise.resolve({ endpoint: 'rpc-a', walletRepByToken: [['0xrep', 10n]] }), Promise.resolve({ endpoint: 'rpc-b', walletRepByToken: [['0xrep', 10n]] }), Promise.resolve({ endpoint: 'rpc-c', walletRepByToken: [['0xrep', 11n]] })]) expect(() => availableExecutionObservations('liquidation execution snapshot', observations, observation => ({ endpoint: observation.endpoint, value: observation.walletRepByToken }))).toThrow('RPC disagreement') }) test('chunks staged-operation recovery across bounded inclusive log ranges', () => { diff --git a/bots/open-oracle-arbitrager/README.md b/bots/open-oracle-arbitrager/README.md index 16ebfa4d7..d919f2571 100644 --- a/bots/open-oracle-arbitrager/README.md +++ b/bots/open-oracle-arbitrager/README.md @@ -179,9 +179,11 @@ end-user release. The commands below remain experimental operator references: ## Install -From the monorepo root, enter the arbitrager project: +From the monorepo root, install the root package before entering the arbitrager +project. The root package provides the shared Ethereum runtime used by the bot: ```bash +bun install --frozen-lockfile cd bots/open-oracle-arbitrager bun install --frozen-lockfile ``` diff --git a/bots/open-oracle-arbitrager/bun.lock b/bots/open-oracle-arbitrager/bun.lock index 6551d0711..09a1407c4 100644 --- a/bots/open-oracle-arbitrager/bun.lock +++ b/bots/open-oracle-arbitrager/bun.lock @@ -63,7 +63,7 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@zoltar/bot-shared": ["@zoltar/bot-shared@file:../shared", { "dependencies": { "@noble/hashes": "2.2.0", "ccxt": "4.5.70", "micro-eth-signer": "0.18.1" }, "devDependencies": { "@biomejs/biome": "2.3.11", "bun-types": "1.3.11", "typescript": "5.8.2" } }], + "@zoltar/bot-shared": ["@zoltar/bot-shared@file:../shared", { "dependencies": { "@zoltar/shared": "file:../../shared", "ccxt": "4.5.70" }, "devDependencies": { "@biomejs/biome": "2.3.11", "bun-types": "1.3.11", "typescript": "5.8.2" } }], "@zoltar/shared": ["@zoltar/shared@file:../../shared", { "dependencies": { "@noble/hashes": "2.2.0", "micro-eth-signer": "0.18.1" } }], @@ -117,6 +117,8 @@ "ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="], + "@zoltar/bot-shared/@zoltar/shared": ["@zoltar/shared@file:../../shared", {}], + "ccxt/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "micro-packed/@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="], diff --git a/bots/open-oracle-arbitrager/contracts/OpenOracleArbitrageExecutor.sol b/bots/open-oracle-arbitrager/contracts/OpenOracleArbitrageExecutor.sol index 9b81c5c49..996abbda8 100644 --- a/bots/open-oracle-arbitrager/contracts/OpenOracleArbitrageExecutor.sol +++ b/bots/open-oracle-arbitrager/contracts/OpenOracleArbitrageExecutor.sol @@ -615,10 +615,7 @@ contract OpenOracleArbitrageExecutor { } else { require(request.venue == 2, 'Unsupported hedge venue'); } - require( - result.hedgeAmountAttoWeth <= request.hedgeWethLimitAttoEth, - 'Uniswap buy hedge exceeded maximum WETH' - ); + require(result.hedgeAmountAttoWeth <= request.hedgeWethLimitAttoEth, 'Uniswap buy hedge exceeded maximum WETH'); if (request.venue != 2) _approveExact(token1, request.router, 0); return result; } @@ -710,8 +707,7 @@ contract OpenOracleArbitrageExecutor { 'Uniswap V4 callback was not consumed' ); hedgeAmountAttoWeth = abi.decode(encodedResult, (uint256)); - uint256 expectedAttoEth = - buyToken ? request.hedgeWethLimitAttoEth - hedgeAmountAttoWeth : hedgeAmountAttoWeth; + uint256 expectedAttoEth = buyToken ? request.hedgeWethLimitAttoEth - hedgeAmountAttoWeth : hedgeAmountAttoWeth; require( address(this).balance == ethBalanceAttoEth + expectedAttoEth, 'Uniswap V4 native balance delta was not exact' diff --git a/bots/open-oracle-arbitrager/src/contracts/artifacts.generated.ts b/bots/open-oracle-arbitrager/src/contracts/artifacts.generated.ts index f7e5b747f..4bc6e25d4 100644 --- a/bots/open-oracle-arbitrager/src/contracts/artifacts.generated.ts +++ b/bots/open-oracle-arbitrager/src/contracts/artifacts.generated.ts @@ -938,11 +938,11 @@ export const executorArtifact = { evm: { bytecode: { object: - '60808060405234601557614ca7908161001a8239f35b5f80fdfe60806040526004361015610087575b3615610018575f80fd5b60ff5f54161561002457005b60405162461bcd60e51b815260206004820152603560248201527f4f70656e4f7261636c6520617262697472616765206578656375746f722072656044820152740d4cac6e8e640eadce6ded8d2c6d2e8cac8408aa89605b1b6064820152608490fd5b5f3560e01c806329d76f5f14611df05780633b0b89cc14611dcf5780637f8d61f414611c1357806391dd734614611b9c578063a7b06edf1461102b578063cc5eed78146108a25763f6a2b58e0361000e57346106065736600319016104a0811261060657610120136106065761028036610123190112610606576080366103a319011261060657608036610423190112610606575f5461012a60ff82161561230d565b610424359061013c610104358361253a565b61014f61014761236a565b3b1515612409565b610157612380565b3b15610843576001600160a01b0361016d6123db565b16151580610825575b61017f90612f0f565b6101ae61018a6123db565b6001600160a01b0361019a6123f2565b6001600160a01b0390921691161415612f77565b610164356001600160a01b038116808203610606576101d09150331415613253565b60e43542116107cc5760ff19166001175f556001600160a01b036101f26123db565b166001600160a01b036102036123f2565b16906040516370a0823160e01b8152306004820152602081602481855afa908115610612575f9161079a575b506040516370a0823160e01b815230600482015292602084602481845afa938415610612575f94610766575b5061026461236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292602084602481845afa938415610612575f94610732575b506102a261236a565b6040516370a0823160e01b81526001600160a01b03909116600482015290602082602481865afa918215610612575f926106fe575b50604051936080850185811067ffffffffffffffff8211176106ea5760405284526020840195865260408401948552606084019182526103168461381b565b9461031f61236a565b966103316020880198895190856136f8565b61033961236a565b9561034b6040890197885190886136f8565b6001600160a01b0361035b61236a565b166103a4359a610369612ff2565b9160a435906001600160801b038216918281036106065750803b15610606575f928e926001600160801b03604051968795638c64ed9560e01b8752600487015216602485015260448401523360648401528360848401528360a48401526103d560c4840161012461301b565b6103e561034484016103a461321f565b6103c483015261044480356103e4840152610464356104048401526104843561042484015290829084905af18015610612576106da575b5061042e61042861236a565b85613644565b61043f61043961236a565b87613644565b8751156106d057610456608089015160c435612520565b806106bf575b506040516370a0823160e01b815230600482015290602082602481885afa908115610612575f91610689575b610494925051146132c5565b6040516370a0823160e01b815230600482015290602082602481895afa908115610612575f91610653575b6104cb9250511461331d565b60206104d561236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f9261061d575b509061051c610522925188519061252d565b14613375565b602061052c61236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f926105d8575b5090610573610579925184519061252d565b146133d1565b815115159260806060840151930151905191519260405194855260208501526040840152606083015260808201527fb87b87bb3eacf9e5cf9ed834f7142300b841a66f3e9695eb6d3f6e7e89257ea560a03392a35f805460ff19169055005b91506020823d60201161060a575b816105f3602093836124cb565b8101031261060657905190610573610561565b5f80fd5b3d91506105e6565b6040513d5f823e3d90fd5b91506020823d60201161064b575b81610638602093836124cb565b810103126106065790519061051c61050a565b3d915061062b565b90506020823d602011610681575b8161066e602093836124cb565b81010312610606576104cb9151906104bf565b3d9150610661565b90506020823d6020116106b7575b816106a4602093836124cb565b8101031261060657610494915190610488565b3d9150610697565b6106ca9033866134d0565b5f61045c565b6080880151610456565b5f6106e4916124cb565b5f61041c565b634e487b7160e01b5f52604160045260245ffd5b9091506020813d60201161072a575b8161071a602093836124cb565b810103126106065751905f6102d7565b3d915061070d565b9093506020813d60201161075e575b8161074e602093836124cb565b810103126106065751925f610299565b3d9150610741565b9093506020813d602011610792575b81610782602093836124cb565b810103126106065751925f61025b565b3d9150610775565b90506020813d6020116107c4575b816107b5602093836124cb565b8101031261060657515f61022f565b3d91506107a8565b60405162461bcd60e51b815260206004820152602b60248201527f4f70656e4f7261636c652061726269747261676520686564676520646561646c60448201526a1a5b9948195e1c1a5c995960aa1b6064820152608490fd5b5061017f6001600160a01b036108396123f2565b1615159050610176565b60405162461bcd60e51b815260206004820152603160248201527f556e697377617020726f757465722061646472657373206d75737420636f6e7460448201527061696e20636f6e747261637420636f646560781b6064820152608490fd5b34610606576103e0366003190112610606576004356001600160a01b038116808203610606576024356001600160801b0381169283820361060657604435906001600160801b03821680830361060657610280366063190112610606576080366102e319011261060657608036610363190112610606575f5461092860ff82161561230d565b610934833b1515612409565b6001600160a01b036109446123c4565b1615158061100d575b61095690612f0f565b6109716109616123c4565b6001600160a01b0361019a6123db565b60a4356001600160a01b0381169190828103610606576001926109979150331415613253565b60ff1916175f55606435926001600160801b03841693848114159586610606576109c1868561246b565b95608435976001600160801b03891694858a14159889610606576109e58d8861246b565b1015610f7c575061060657891115610f6c576001600160801b0391610a099161342d565b169361060657610a19818361252d565b610224359062ffffff821680830361060657610a3e6298968091610a4594508561246b565b049061252d565b6102a435955062ffffff86169182870361060657610a3e610a6e9362989680926024995061246b565b955b60206001600160a01b03610a826123c4565b16604051968780926370a0823160e01b82523060048301525afa8015610612575f90610f39575b6024955060206001600160a01b03610abf6123db565b16604051978880926370a0823160e01b82523060048301525afa938415610612575f94610f04575b6024965060206001600160a01b03610afd6123c4565b16604051988980926370a0823160e01b82528c60048301525afa938415610612575f94610ecf575b6024975060206001600160a01b03610b3b6123db565b16604051998a80926370a0823160e01b82528d60048301525afa978815610612575f98610e9b575b50610b7f83886001600160a01b03610b796123c4565b1661351a565b610b94868b6001600160a01b03610b796123db565b610baf87836001600160a01b03610ba96123c4565b166136f8565b610bc48a836001600160a01b03610ba96123db565b883b156106065760405193638c64ed9560e01b85526102e4356004860152602485015260448401523360648401525f60848401525f60a4840152610c0c60c48401606461301b565b610c1c61034484016102e461321f565b610364356103c484810191909152610384356103e48501526103a435610404850152356104248401525f8361044481838c5af190811561061257602493610c8d92610e8b575b50610c7d816001600160a01b03610c776123c4565b16613644565b6001600160a01b03610c776123db565b60206001600160a01b03610c9f6123c4565b16604051938480926370a0823160e01b82523060048301525afa8015610612575f90610e57575b610cd19250146132c5565b602460206001600160a01b03610ce56123db565b16604051928380926370a0823160e01b82523060048301525afa908115610612575f91610e24575b50602492610d1b911461331d565b60206001600160a01b03610d2d6123c4565b16604051938480926370a0823160e01b82528960048301525afa918215610612575f92610dee575b50610d639261051c9161252d565b60206001600160a01b03610d756123db565b16926024604051809581936370a0823160e01b835260048301525afa918215610612575f92610db8575b50610dad926105739161252d565b5f805460ff19169055005b9091506020813d602011610de6575b81610dd4602093836124cb565b81010312610606575190610dad610d9f565b3d9150610dc7565b9091506020813d602011610e1c575b81610e0a602093836124cb565b81010312610606575190610d63610d55565b3d9150610dfd565b90506020813d602011610e4f575b81610e3f602093836124cb565b8101031261060657516024610d0d565b3d9150610e32565b506020823d602011610e83575b81610e71602093836124cb565b8101031261060657610cd19151610cc6565b3d9150610e64565b5f610e95916124cb565b8a610c62565b9097506020813d602011610ec7575b81610eb7602093836124cb565b810103126106065751968a610b63565b3d9150610eaa565b93506020873d602011610efc575b81610eea602093836124cb565b81010312610606576024965193610b25565b3d9150610edd565b93506020863d602011610f31575b81610f1f602093836124cb565b81010312610606576024955193610ae7565b3d9150610f12565b506020853d602011610f64575b81610f53602093836124cb565b810103126106065760249451610aa9565b3d9150610f46565b50506001600160801b035f610a09565b9398949750509050610f8e818a61252d565b610224359062ffffff821680830361060657610a3e6298968091610fb394508561246b565b6102a435975062ffffff88169182890361060657610a3e610fdc93629896809260249b5061246b565b94831115610ffd576001600160801b0391610ff69161342d565b1695610a70565b50506001600160801b035f610ff6565b506109566001600160a01b036110216123db565b161515905061094d565b346106065736600319016103a081126106065760a013610606576102803660a319011261060657608036610323190112610606575f5461106e60ff82161561230d565b61107c60443560243561253a565b61108761014761236a565b6001600160a01b03611097612396565b16151580611b7e575b6110a990612f0f565b6110c46110b4612396565b6001600160a01b0361019a6123ad565b6001600160801b036110d4612fdc565b16151580611b65575b15611b0a5760ff19166001175f556001600160a01b036110fb61236a565b5f911660e4356001600160a01b038116908181036106065733821480611aef575b61187e575b506024905060206001600160a01b03611138612396565b16604051928380926370a0823160e01b82523360048301525afa8015610612575f9061184b575b6024915060206001600160a01b036111756123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611817575b506111a6612396565b6111ae612fdc565b90843b1561060657604051637d656cf760e01b8152915f91839182916111da91903033600486016124ed565b038183885af1801561061257611807575b506111f46123ad565b6111fc612ff2565b90843b1561060657604051637d656cf760e01b8152915f918391829161122891903033600486016124ed565b038183885af18015610612576117f7575b5061128c6020611247612396565b61124f612fdc565b60405163627160f360e11b81526001600160a01b0390921660048301526001600160801b0316602482015233604482015291829081906064820190565b03815f885af1908115610612575f916117c5575b506001600160801b036112b1612fdc565b1603611763576112cc60206112c46123ad565b61124f612ff2565b03815f885af1908115610612575f91611731575b506001600160801b036112f1612ff2565b16036116cf5760249060206001600160a01b0361130c612396565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611699575b50611350906001600160801b03611349612fdc565b169061252d565b0361163a5760249060206001600160a01b0361136a6123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611604575b506113a7906001600160801b03611349612ff2565b036115a5578161143c575b506113bb612396565b6113c3612fdc565b916001600160801b036113d46123ad565b6113dc612ff2565b90826040519616865260018060a01b03166020860152166040840152606083015260018060a01b03169061032435907f8e60feda1ea0461bfc923501c0e2478e40a145bb3c95a75eb987a1fd4afc85e260803392a45f805460ff19169055005b60405163627160f360e11b81525f600482018190526024820184905233604483018190523192602091839160649183915af180156106125783915f91611570575b50036115055761148f8233319261252d565b0361149a57816113b2565b60405162461bcd60e51b815260206004820152603960248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207265636569707420776173206e6f74206578616374000000000000006064820152608490fd5b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207769746864726177616c20776173206e6f74206578616374000000006064820152608490fd5b9150506020813d60201161159d575b8161158c602093836124cb565b81010312610606578290518461147d565b3d915061157f565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3220726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d602011611632575b81611620602093836124cb565b810103126106065751906113a7611392565b3d9150611613565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3120726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d6020116116c7575b816116b5602093836124cb565b81010312610606575190611350611334565b3d91506116a8565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e322077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d60201161175b575b8161174c602093836124cb565b810103126106065751856112e0565b3d915061173f565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e312077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d6020116117ef575b816117e0602093836124cb565b810103126106065751856112a0565b3d91506117d3565b5f611801916124cb565b84611239565b5f611811916124cb565b846111eb565b9091506020813d602011611843575b81611833602093836124cb565b810103126106065751908461119d565b3d9150611826565b506020813d602011611876575b81611865602093836124cb565b81010312610606576024905161115f565b3d9150611858565b909192506103243591833b15610606576040519163cad5e7a960e01b835283600484015260a4356001600160801b03811680910361060657602484015260c4356001600160801b0381168091036106065760448401525060648201526101043565ffffffffffff81168091036106065760848201526101243565ffffffffffff81168091036106065760a4820152610144356001600160a01b038116908190036106065760c48201526101643565ffffffffffff81168091036106065760e48201526101843565ffffffffffff8116809103610606576101048201526101a4356001600160801b038116809103610606576101248201526101c4356001600160a01b03811690819003610606576101448201526101e435916bffffffffffffffffffffffff8316809303610606576101648201839052610204356001600160a01b03811690819003610606576101848301526102243562ffffff8116809103610606576101a48301526102443562ffffff8116809103610606576101c48301526102643562ffffff8116809103610606576101e48301526102843561ffff8116809103610606576102048301526102a4356001600160a01b03811690819003610606576102248301526102c43563ffffffff8116809103610606576102448301526102e43562ffffff8116809103610606576102648301526103043560ff8116809103610606576102848301526102a4820152610344356001600160a01b03811690819003610606576102c4820152610364356102e4820152610384356103048201525f816103248183875af1801561061257611adf575b50908280611121565b5f611ae9916124cb565b82611ad6565b506101243565ffffffffffff8116809103610606571561111c565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c65206c6966656379636c6520616d6f756e7473206d757360448201526c7420626520706f73697469766560981b6064820152608490fd5b506001600160801b03611b76612ff2565b1615156110dd565b506110a96001600160a01b03611b926123ad565b16151590506110a0565b346106065760203660031901126106065760043567ffffffffffffffff811161060657366023820112156106065780600401359067ffffffffffffffff821161060657366024838301011161060657611c0f916024611bfb920161266d565b6040519182916020835260208301906122d5565b0390f35b346106065736600319016102c08112610606576102801361060657610284356001600160801b038116808203610606576102a4356001600160801b038116808203610606576004356001600160801b0381169485821415958661060657611c7a818561246b565b95602435976001600160801b03891696878a1415988961060657611c9e848a61246b565b1015611d3b5750610606571115611d2b576001600160801b0391611cc19161342d565b16926106065781611cd19161252d565b6101c4359062ffffff821680830361060657610a3e6298968091611cf694508561246b565b61024435935062ffffff84169182850361060657610a3e611d1f9362989680926040975061246b565b82519182526020820152f35b50506001600160801b035f611cc1565b9597505081925090611d52915f989694985061252d565b6101c4359062ffffff821680830361060657610a3e6298968091611d7794508561246b565b61024435965062ffffff87169182880361060657610a3e611da093629896809260409a5061246b565b931115611dbf576001600160801b0391611db99161342d565b16611d1f565b50506001600160801b035f611db9565b3461060657604036600319011261060657611dee60243560043561253a565b005b3461060657366003190160c081126106065760a013610606575f54611e1860ff82161561230d565b611e2660643560443561253a565b611e3161014761236a565b611e39612380565b3b1561226057608435908115612208577003fffffffffffffffffffffffffffffffc82116121ab5760ff19166001175f55602460206001600160a01b03611e7e612380565b16604051928380926370a0823160e01b82523360048301525afa908115610612575f91612179575b506001600160a01b03611eb761236a565b1682805b6120e057506020611eca612380565b60405163627160f360e11b81526001600160a01b0390911660048201526024810185905233604482015291829060649082905f905af180156106125783915f916120ab575b500361204757602460206001600160a01b03611f29612380565b16604051928380926370a0823160e01b82523360048301525afa9081156106125783905f92612011575b50611f5e919261252d565b03611fb0576001600160a01b03611f73612380565b169060405190815260a435907f158110513241d1756a66549dae81bce1d1cbc3db022d0fe8a62af9206771abb160203392a45f805460ff19169055005b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c65207265706c6163656d656e742063726564697420726560448201527218d95a5c1d081dd85cc81b9bdd08195e1858dd606a1b6064820152608490fd5b9150506020813d60201161203f575b8161202d602093836124cb565b81010312610606575182611f5e611f53565b3d9150612020565b60405162461bcd60e51b815260206004820152603660248201527f4f70656e4f7261636c65207265706c6163656d656e74206372656469742077696044820152751d1a191c985dd85b081dd85cc81b9bdd08195e1858dd60521b6064820152608490fd5b9150506020813d6020116120d8575b816120c7602093836124cb565b810103126106065782905184611f0f565b3d91506120ba565b6001600160801b03811115612169576001600160801b03905b612101612380565b91833b15610606575f8161212c946040519586928392637d656cf760e01b84523033600486016124ed565b038183885af190811561061257612153936001600160801b0392612159575b501690612520565b80611ebb565b5f612163916124cb565b8761214b565b6001600160801b038116906120f9565b90506020813d6020116121a3575b81612194602093836124cb565b81010312610606575182611ea6565b3d9150612187565b60405162461bcd60e51b815260206004820152602f60248201527f5265706c6163656d656e742063726564697420616d6f756e742065786365656460448201526e73207265706f727420626f756e647360881b6064820152608490fd5b60405162461bcd60e51b815260206004820152602a60248201527f5265706c6163656d656e742063726564697420616d6f756e74206d75737420626044820152696520706f73697469766560b01b6064820152608490fd5b60405162461bcd60e51b815260206004820152603360248201527f5265706c6163656d656e742063726564697420746f6b656e206d75737420636f6044820152726e7461696e20636f6e747261637420636f646560681b6064820152608490fd5b35906001600160801b038216820361060657565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b35906001600160a01b038216820361060657565b1561231457565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c6520617262697472616765206578656375746f72207265604482015267656e7472616e637960c01b6064820152608490fd5b6004356001600160a01b03811681036106065790565b6024356001600160a01b03811681036106065790565b610144356001600160a01b03811681036106065790565b610204356001600160a01b03811681036106065790565b610104356001600160a01b03811681036106065790565b6101c4356001600160a01b03811681036106065790565b610284356001600160a01b03811681036106065790565b1561241057565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c652061646472657373206d75737420636f6e7461696e2060448201526c636f6e747261637420636f646560981b6064820152608490fd5b8181029291811591840414171561247e57565b634e487b7160e01b5f52601160045260245ffd5b60a0810190811067ffffffffffffffff8211176106ea57604052565b610100810190811067ffffffffffffffff8211176106ea57604052565b90601f8019910116810190811067ffffffffffffffff8211176106ea57604052565b6001600160a01b039182168152918116602083015290911660408201526001600160801b03909116606082015260800190565b9190820391821161247e57565b9190820180921161247e57565b904315158061260e575b156125bd5780151591826125b2575b50501561255c57565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2063616e6f6e6963616c20706172656e7420626c6f636b6044820152670818da185b99d95960c21b6064820152608490fd5b401490505f80612553565b60405162461bcd60e51b8152602060048201526024808201527f457865637574696f6e206d7573742074617267657420746865206e65787420626044820152636c6f636b60e01b6064820152608490fd5b505f19430143811161247e578214612544565b67ffffffffffffffff81116106ea57601f01601f191660200190565b359062ffffff8216820361060657565b600f0b6f7fffffffffffffffffffffffffffffff19811461247e575f0390565b906060905f905f5460ff811680612ef9575b15612ea45761268d82612621565b61269a60405191826124cb565b828152602081019083870193368511610606576020815f928a86378301015251902060015403612e4e57610100600160a81b0319165f90815560015560a09084900312610606576040516126ed81612492565b6126f6846122f9565b908181526127066020860161263d565b918260208301526040860135908115158203610606576040830191825262ffffff868401948789013586526080808601990135895216906127468261344d565b9060405161275381612492565b5f8152602081019160018060a01b03168252604081019384528881019260020b835260808101925f84528551151590815f14612e3a5788515b875115612e1f576401000276a4905b6040519c8d01938d851067ffffffffffffffff8611176106ea57612864946040528d5260208d0190815260408d019160018060a01b0316825260209c8d97604051946127e78a876124cb565b5f8652604051633cf3645360e21b815297516001600160a01b0390811660048a0152985189166024890152995162ffffff166044880152985160020b60648701529751861660848601529651151560a4850152955160c4840152945190921660e482015261012061010482015292839182916101248301906122d5565b03815f335af1908115610612575f91612df2575b508060801d916001600160801b0383600f0b9216600f0b9051612b72576128a28551600f0b61264d565b600f0b03612b2157841215612ad1576001600160801b031694518510612a755780516001600160a01b0316333b15612a715760405190632961046560e21b82526004820152838160248183335af18015612a6657908491612a4d575b50505181516129179133906001600160a01b03166134d0565b604051630476982d60e21b815290838260048186335af1918215612a42578392612a13575b5051036129bc57333b156129ae57604051630b0d9c0960e01b81526004810182905230602482015260448101849052818160648183335af180156129b157612999575b5050604051918183015281526129966040826124cb565b90565b6129a48280926124cb565b6129ae578061297f565b80fd5b6040513d84823e3d90fd5b60405162461bcd60e51b815260048101839052602960248201527f556e697377617020563420746f6b656e20736574746c656d656e7420776173206044820152681b9bdd08195e1858dd60ba1b6064820152608490fd5b9091508381813d8311612a3b575b612a2b81836124cb565b810103126106065751905f61293c565b503d612a21565b6040513d85823e3d90fd5b81612a57916124cb565b612a6257825f6128fe565b8280fd5b6040513d86823e3d90fd5b8380fd5b60405162461bcd60e51b815260048101859052602e60248201527f556e69737761702056342073656c6c206865646765207265636569766564207460448201526d0dede40d8d2e8e8d8ca40ae8aa8960931b6064820152608490fd5b60405162461bcd60e51b815260048101869052602260248201527f556e69737761702056342073656c6c206f75747075742077617320696e76616c6044820152611a5960f21b6064820152608490fd5b60405162461bcd60e51b815260048101879052602360248201527f556e69737761702056342073656c6c20696e70757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b9091508351600f0b03612da1575f811215612d5e57612b986001600160801b039161264d565b1694518511612d0657333b1561060657604051632961046560e21b81525f600482018190528160248183335af1801561061257612cf1575b50604051630476982d60e21b8152848160048189335af1908115612a66579086918591612cc0575b5003612c6857519051906001600160a01b0316333b15612a6257604051630b0d9c0960e01b81526001600160a01b039190911660048201523060248201526044810191909152818160648183335af180156129b157612999575050604051918183015281526129966040826124cb565b60405162461bcd60e51b815260048101859052602a60248201527f556e6973776170205634206e617469766520736574746c656d656e7420776173604482015269081b9bdd08195e1858dd60b21b6064820152608490fd5b809250868092503d8311612cea575b612cd981836124cb565b81010312610606578590515f612bf8565b503d612ccf565b612cfe9193505f906124cb565b5f915f612bd0565b60405162461bcd60e51b815260048101859052602a60248201527f556e697377617020563420627579206865646765206578636565646564206d616044820152690f0d2daeada40ae8aa8960b31b6064820152608490fd5b6064856040519062461bcd60e51b825280600483015260248201527f556e69737761702056342062757920696e7075742077617320696e76616c69646044820152fd5b60405162461bcd60e51b815260048101869052602360248201527f556e697377617020563420627579206f757470757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b90508581813d8311612e18575b612e0981836124cb565b8101031261060657515f612878565b503d612dff565b73fffd8963efd1fc6a506488495d951d5263988d259061279b565b8851600160ff1b811461247e575f0361278c565b60405162461bcd60e51b815260206004820152602860248201527f556e617574686f72697a656420556e69737761702056342063616c6c6261636b604482015267081c185e5b1bd85960c21b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f556e617574686f72697a656420556e697377617020563420756e6c6f636b2063604482015266616c6c6261636b60c81b6064820152608490fd5b5033600882901c6001600160a01b03161461267f565b15612f1657565b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220726560448201527271756972657320455243323020746f6b656e7360681b6064820152608490fd5b15612f7e57565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220746f60448201526f35b2b7399036bab9ba103234b33332b960811b6064820152608490fd5b6064356001600160801b03811681036106065790565b6084356001600160801b03811681036106065790565b359065ffffffffffff8216820361060657565b6001600160801b0361302c826122c1565b1682526001600160801b03613043602083016122c1565b1660208301526001600160a01b0361305d604083016122f9565b16604083015265ffffffffffff61307660608301613008565b16606083015265ffffffffffff61308f60808301613008565b1660808301526001600160a01b036130a960a083016122f9565b1660a083015265ffffffffffff6130c260c08301613008565b1660c083015265ffffffffffff6130db60e08301613008565b1660e08301526001600160801b036130f661010083016122c1565b166101008301526001600160a01b0361311261012083016122f9565b166101208301526101408101356bffffffffffffffffffffffff8116809103610606576101408301526001600160a01b0361315061016083016122f9565b1661016083015262ffffff613168610180830161263d565b1661018083015262ffffff6131806101a0830161263d565b166101a083015262ffffff6131986101c0830161263d565b166101c08301526101e081013561ffff8116809103610606576101e08301526001600160a01b036131cc61020083016122f9565b166102008301526102208101359063ffffffff8216809203610606576102609161022084015262ffffff613203610240830161263d565b1661024084015201359060ff8216809203610606576102600152565b8035825260609081906001600160a01b0361323c602083016122f9565b166020850152604081013560408501520135910152565b1561325a57565b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220646f60448201527f6573206e6f7420737570706f72742073656c662d6469737075746573000000006064820152608490fd5b156132cc57565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e31207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561332457565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e32207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561337c57565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e31207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b156133d857565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e32207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b906001600160801b03809116911603906001600160801b03821161247e57565b62ffffff16606481146134ca576101f481146134c457610bb881146134be57612710146134b95760405162461bcd60e51b815260206004820152601f60248201527f556e737570706f7274656420556e697377617020563420706f6f6c20666565006044820152606490fd5b60c890565b50603c90565b50600a90565b50600190565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526135189161351382606481015b03601f1981018452836124cb565b614920565b565b90801561363f576040516323b872dd60e01b60208281019190915233602483810191909152306044840152606483018490529390916135709061356a81608481015b03601f1981018352826124cb565b82614920565b6040516370a0823160e01b815230600482015293849182906001600160a01b03165afa918215610612575f92613609575b506135ac919261252d565b036135b357565b60405162461bcd60e51b815260206004820152602860248201527f546f6b656e207472616e7366657220746f206578656375746f7220776173206e6044820152671bdd08195e1858dd60c21b6064820152608490fd5b91506020823d602011613637575b81613624602093836124cb565b81010312610606576135ac9151916135a1565b3d9150613617565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529192916020908290604490829087165afa908115610612575f916136c6575b5061368f575050565b60405163095ea7b360e01b60208201526001600160a01b0390911660248201525f6044820152613518916135138260648101613505565b90506020813d6020116136f0575b816136e1602093836124cb565b8101031261060657515f613686565b3d91506136d4565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529293926020908290604490829086165afa908115610612575f916137bf575b50613784575b8161374a57505050565b60405163095ea7b360e01b60208201526001600160a01b039093166024840152604483019190915261351891906135138260648101613505565b60405163095ea7b360e01b60208201526001600160a01b03841660248201525f60448201526137ba9061356a816064810161355c565b613740565b90506020813d6020116137e9575b816137da602093836124cb565b8101031261060657515f61373a565b3d91506137cd565b604051906137fe82612492565b5f6080838281528260208201528260408201528260608201520152565b6138236137f1565b90608435906001600160801b038216918281036106065760a435916001600160801b0383169384840361060657610124356001600160801b0381168103610606576138776001600160801b0382168761246b565b9461014435956001600160801b0387168703610606576138a0846001600160801b03891661246b565b1015614820576001600160801b0382168203610606576001600160801b038216831115614812576001600160801b0382168203610606576001600160801b036138e9838761342d565b16916001600160801b03871687036106065761390e6001600160801b0388168961252d565b6001600160801b0388168803610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846139509550166001600160801b038c1661246b565b6001600160801b038816880361060657610364359062ffffff821682036106065762989680610a3e62ffffff846139929550166001600160801b038c1661246b565b915b60208a019360408b019384528452505f976001600160801b03821690818303610606576139c1925061246b565b6001600160801b03871695868814159485610606576139e191508761246b565b108089526141245750506139f36137f1565b506101c4356001600160a01b03811680821415949193918561412057610284356001600160a01b03811697888214159591949186613d9c57613d945790613a69915060608b01928352613a4d60208c01518251908661351a565b6020613a5f60408d015185519061252d565b910151908961351a565b6044359460ff861694858703613d9457600286149788156140fa5750505f5f94835160643562ffffff8116810361060657613aa38161344d565b5060016001607f1b038211614099574791602435906001600160a01b0382168203610606578e54610100600160a81b031916600883901b610100600160a81b0316178f5562ffffff83168303610606578e8e916080604051613b0481612492565b84815262ffffff60208201971687526040810193845260608101928352019160c435835262ffffff60405196602088019586525116604087015251151560608601525160808501525160a084015260a08352613b6160c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918e918391829084908290613ba59060248301906122d5565b03926001600160a01b03165af1908115613f56578d91614017575b508c5460081c6001600160a01b0316158061400d575b15613fbc5760208151918180820193849201010312610606575190613bfc82479261252d565b03613f615780613f0b575b60808d01525b5f9680613da457506024356001600160a01b0381169690878103613da05750613d9c5750613d9857506064359062ffffff821691828103613d94575051604051602093613cac936101049391613c62846124ae565b8b845286840152604083015230606083015260e435608083015260a082015260c43560c08201528960e082015289604051958694859363414bf38960e01b85526004850190614c11565b5af1908115613d89578691613d57575b5060808701525b608086015160c43511613cfe57612a71575015613cdf57505090565b602435916001600160a01b03831683036129ae57509061299691613644565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761702073656c6c20686564676520726563656976656420746f6f2060448201526a0d8d2e8e8d8ca40ae8aa8960ab1b6064820152608490fd5b90506020813d602011613d81575b81613d72602093836124cb565b8101031261060657515f613cbc565b3d9150613d65565b6040513d88823e3d90fd5b8980fd5b8880fd5b8a80fd5b8c80fd5b5f975093959493600103613ef55760405194613dc16060876124cb565b600286526040366020880137613d9c575088613ddc85614af2565b52613d985750613deb82614b13565b526024356001600160a01b03811690818103613d9857918891613e339493508351836040518097819582946338ed173960e01b845260e43591309160c4359060048701614ba9565b03925af1918215613eea578792613ec6575b5060028251149081613eb1575b5015613e6c57613e6190614b13565b516080870152613cc3565b60405162461bcd60e51b815260206004820152601f60248201527f556e69737761702056322073656c6c20616d6f756e747320696e76616c6964006044820152606490fd5b9050613ebc82614af2565b519051145f613e52565b613ee39192503d8089833e613edb81836124cb565b810190614b23565b905f613e45565b6040513d89823e3d90fd5b505050505050505f613f0683614aa6565b613cc3565b853b15613f5257604051630d0e30db60e41b81528c81600481858b5af18015613f5657908d91613f3d575b5050613c07565b81613f47916124cb565b613f52578b5f613f36565b8b80fd5b6040513d8f823e3d90fd5b60405162461bcd60e51b815260206004820152602d60248201527f556e6973776170205634206e61746976652062616c616e63652064656c74612060448201526c1dd85cc81b9bdd08195e1858dd609a1b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f556e69737761702056342063616c6c6261636b20776173206e6f7420636f6e736044820152631d5b595960e21b6064820152608490fd5b5060015415613bd6565b90503d808e833e61402881836124cb565b8101906020818303126140955780519067ffffffffffffffff8211614091570181601f820112156140955780519061405f82612621565b9261406d60405194856124cb565b8284526020838301011161409157818f9260208093018386015e830101525f613bc0565b8e80fd5b8d80fd5b60405162461bcd60e51b815260206004820152603360248201527f556e697377617020563420686564676520616d6f756e742065786365656473206044820152727369676e65642064656c746120626f756e647360681b6064820152608490fd5b90946024356001600160a01b0381168103613f5257845161411b918c6136f8565b613c0d565b8780fd5b909291506141339493946137f1565b506101c4356001600160a01b0381169586821415938461060657610284359560018060a01b03871692838814159889610606576102e43562ffffff81168082036106065761418a629896809161419193508561246b565b048361252d565b9050610364359162ffffff831692838103610606576141b9936298968092610a3e925061246b565b9160608c01928084528651106147b45760206141f66142009351976141ed8d6141e560c435809c61252d565b86519161351a565b51855190612520565b910151908461351a565b6044359560ff871695868803610606576002871498891561478e5750505f935f9083516064359062ffffff821682036106065761423c8261344d565b5060016001607f1b0381116140995747918d3b15610606575f808f60248d6040519485938492632e1a7d4d60e01b845260048401525af1801561061257614779575b50602435906001600160a01b0382168203610606578f8054610100600160a81b038460081b1690610100600160a81b03191617905562ffffff8116810361060657604051926142cc84612492565b89845262ffffff602085019216825260806040850194600186526060810192835201908c82526040519462ffffff60208701948d86525116604087015251151560608601525160808501525160a084015260a0835261432c60c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918f9183918290849082906143709060248301906122d5565b03926001600160a01b03165af19081156146e4578e916146fb575b508d5460081c6001600160a01b031615806146f1575b15613fbc57602081519181808201938492010103126106065751906143c6828a612520565b906143d282479261252d565b03613f61578e81614697575b6080915001525b5f978061453957506024356001600160a01b03811696908781036140955750613f525750613d9457506064359062ffffff821691828103613d9c57505160405160209361448293610104939161443a846124ae565b8c845286840152604083015230606083015260e435608083015260a08201528560c08201528a60e08201528a6040519586948593631b67c43360e31b85526004850190614c11565b5af1908115613eea578791614507575b5060808801525b6080870151116144b257612a71575015613cdf57505090565b60405162461bcd60e51b815260206004820152602760248201527f556e697377617020627579206865646765206578636565646564206d6178696d6044820152660eada40ae8aa8960cb1b6064820152608490fd5b90506020813d602011614531575b81614522602093836124cb565b8101031261060657515f614492565b3d9150614515565b5f98509395949360010361467f57604051946145566060876124cb565b600286526040366020880137613f5257508961457185614af2565b52613d94575061458082614b13565b526024356001600160a01b03811690818103613d94579189916145c5949350835183604051809781958294634401edf760e11b845260e435918c309260048701614ba9565b03925af1918215614674578892614658575b5060028251149081614643575b50156145fe576145f390614af2565b516080880152614499565b60405162461bcd60e51b815260206004820152601e60248201527f556e69737761702056322062757920616d6f756e747320696e76616c696400006044820152606490fd5b905061464e82614b13565b519051145f6145e4565b61466d9192503d808a833e613edb81836124cb565b905f6145d7565b6040513d8a823e3d90fd5b50505050505090505f9061469284614aa6565b614499565b508c3b15614095578d8d600460405180948193630d0e30db60e41b83525af180156146e4578f90918f926146cc575b506143de565b6146d8915082906124cb565b613da0578c8e5f6146c6565b8e604051903d90823e3d90fd5b50600154156143a1565b90503d808f833e61470c81836124cb565b8101906020818303126140915780519067ffffffffffffffff8211614775570181601f820112156140915780519061474382612621565b9261475160405194856124cb565b82845260208383010111614775578f918060208093018386015e830101525f61438b565b8f80fd5b614786919f505f906124cb565b5f9d5f61427e565b90946024356001600160a01b0381168103610606576147af9088908d6136f8565b6143e5565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520627579206865646765206578636565647320746f6b60448201526f32b7191031b7b73a3934b13aba34b7b760811b6064820152608490fd5b6001600160801b035f6138e9565b6001600160801b0382168203610606576148436001600160801b0383168461252d565b6001600160801b0383168303610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846148859550166001600160801b03871661246b565b6001600160801b038316830361060657610364359062ffffff821682036106065762989680610a3e62ffffff846148c79550166001600160801b03871661246b565b916001600160801b0387168703610606576001600160801b038716881115614912576001600160801b0387168703610606576001600160801b0361490b888461342d565b1691613994565b6001600160801b035f61490b565b6001600160a01b0316803b15614a4357815f92918360208194519301915af13d15614a3b573d9061495082612621565b9161495e60405193846124cb565b82523d5f602084013e5b156149f757805180614978575050565b816020918101031261060657602001518015908115036106065761499857565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090614968565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b15614aad57565b60405162461bcd60e51b815260206004820152601760248201527f556e737570706f727465642068656467652076656e75650000000000000000006044820152606490fd5b805115614aff5760200190565b634e487b7160e01b5f52603260045260245ffd5b805160011015614aff5760400190565b6020818303126106065780519067ffffffffffffffff821161060657019080601f830112156106065781519167ffffffffffffffff83116106ea578260051b906020820193614b7560405195866124cb565b845260208085019282010192831161060657602001905b828210614b995750505090565b8151815260209182019101614b8c565b92919594939560a08401918452602084015260a060408401528151809152602060c084019201905f5b818110614bf2575050506001600160a01b03909416606082015260800152565b82516001600160a01b0316845260209384019390920191600101614bd2565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff16908401526060808301518216908401526080808301519084015260a0808301519084015260c0808301519084015260e0918201511691015256fea26469706673582212201c45d9ea582d95758797a1cf6f2b0692a3340fa87cb0e4df152b08fde09cadfb64736f6c63430008230033', + '60808060405234601557614ca7908161001a8239f35b5f80fdfe60806040526004361015610087575b3615610018575f80fd5b60ff5f54161561002457005b60405162461bcd60e51b815260206004820152603560248201527f4f70656e4f7261636c6520617262697472616765206578656375746f722072656044820152740d4cac6e8e640eadce6ded8d2c6d2e8cac8408aa89605b1b6064820152608490fd5b5f3560e01c806329d76f5f14611df05780633b0b89cc14611dcf5780637f8d61f414611c1357806391dd734614611b9c578063a7b06edf1461102b578063cc5eed78146108a25763f6a2b58e0361000e57346106065736600319016104a0811261060657610120136106065761028036610123190112610606576080366103a319011261060657608036610423190112610606575f5461012a60ff82161561230d565b610424359061013c610104358361253a565b61014f61014761236a565b3b1515612409565b610157612380565b3b15610843576001600160a01b0361016d6123db565b16151580610825575b61017f90612f0f565b6101ae61018a6123db565b6001600160a01b0361019a6123f2565b6001600160a01b0390921691161415612f77565b610164356001600160a01b038116808203610606576101d09150331415613253565b60e43542116107cc5760ff19166001175f556001600160a01b036101f26123db565b166001600160a01b036102036123f2565b16906040516370a0823160e01b8152306004820152602081602481855afa908115610612575f9161079a575b506040516370a0823160e01b815230600482015292602084602481845afa938415610612575f94610766575b5061026461236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292602084602481845afa938415610612575f94610732575b506102a261236a565b6040516370a0823160e01b81526001600160a01b03909116600482015290602082602481865afa918215610612575f926106fe575b50604051936080850185811067ffffffffffffffff8211176106ea5760405284526020840195865260408401948552606084019182526103168461381b565b9461031f61236a565b966103316020880198895190856136f8565b61033961236a565b9561034b6040890197885190886136f8565b6001600160a01b0361035b61236a565b166103a4359a610369612ff2565b9160a435906001600160801b038216918281036106065750803b15610606575f928e926001600160801b03604051968795638c64ed9560e01b8752600487015216602485015260448401523360648401528360848401528360a48401526103d560c4840161012461301b565b6103e561034484016103a461321f565b6103c483015261044480356103e4840152610464356104048401526104843561042484015290829084905af18015610612576106da575b5061042e61042861236a565b85613644565b61043f61043961236a565b87613644565b8751156106d057610456608089015160c435612520565b806106bf575b506040516370a0823160e01b815230600482015290602082602481885afa908115610612575f91610689575b610494925051146132c5565b6040516370a0823160e01b815230600482015290602082602481895afa908115610612575f91610653575b6104cb9250511461331d565b60206104d561236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f9261061d575b509061051c610522925188519061252d565b14613375565b602061052c61236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f926105d8575b5090610573610579925184519061252d565b146133d1565b815115159260806060840151930151905191519260405194855260208501526040840152606083015260808201527fb87b87bb3eacf9e5cf9ed834f7142300b841a66f3e9695eb6d3f6e7e89257ea560a03392a35f805460ff19169055005b91506020823d60201161060a575b816105f3602093836124cb565b8101031261060657905190610573610561565b5f80fd5b3d91506105e6565b6040513d5f823e3d90fd5b91506020823d60201161064b575b81610638602093836124cb565b810103126106065790519061051c61050a565b3d915061062b565b90506020823d602011610681575b8161066e602093836124cb565b81010312610606576104cb9151906104bf565b3d9150610661565b90506020823d6020116106b7575b816106a4602093836124cb565b8101031261060657610494915190610488565b3d9150610697565b6106ca9033866134d0565b5f61045c565b6080880151610456565b5f6106e4916124cb565b5f61041c565b634e487b7160e01b5f52604160045260245ffd5b9091506020813d60201161072a575b8161071a602093836124cb565b810103126106065751905f6102d7565b3d915061070d565b9093506020813d60201161075e575b8161074e602093836124cb565b810103126106065751925f610299565b3d9150610741565b9093506020813d602011610792575b81610782602093836124cb565b810103126106065751925f61025b565b3d9150610775565b90506020813d6020116107c4575b816107b5602093836124cb565b8101031261060657515f61022f565b3d91506107a8565b60405162461bcd60e51b815260206004820152602b60248201527f4f70656e4f7261636c652061726269747261676520686564676520646561646c60448201526a1a5b9948195e1c1a5c995960aa1b6064820152608490fd5b5061017f6001600160a01b036108396123f2565b1615159050610176565b60405162461bcd60e51b815260206004820152603160248201527f556e697377617020726f757465722061646472657373206d75737420636f6e7460448201527061696e20636f6e747261637420636f646560781b6064820152608490fd5b34610606576103e0366003190112610606576004356001600160a01b038116808203610606576024356001600160801b0381169283820361060657604435906001600160801b03821680830361060657610280366063190112610606576080366102e319011261060657608036610363190112610606575f5461092860ff82161561230d565b610934833b1515612409565b6001600160a01b036109446123c4565b1615158061100d575b61095690612f0f565b6109716109616123c4565b6001600160a01b0361019a6123db565b60a4356001600160a01b0381169190828103610606576001926109979150331415613253565b60ff1916175f55606435926001600160801b03841693848114159586610606576109c1868561246b565b95608435976001600160801b03891694858a14159889610606576109e58d8861246b565b1015610f7c575061060657891115610f6c576001600160801b0391610a099161342d565b169361060657610a19818361252d565b610224359062ffffff821680830361060657610a3e6298968091610a4594508561246b565b049061252d565b6102a435955062ffffff86169182870361060657610a3e610a6e9362989680926024995061246b565b955b60206001600160a01b03610a826123c4565b16604051968780926370a0823160e01b82523060048301525afa8015610612575f90610f39575b6024955060206001600160a01b03610abf6123db565b16604051978880926370a0823160e01b82523060048301525afa938415610612575f94610f04575b6024965060206001600160a01b03610afd6123c4565b16604051988980926370a0823160e01b82528c60048301525afa938415610612575f94610ecf575b6024975060206001600160a01b03610b3b6123db565b16604051998a80926370a0823160e01b82528d60048301525afa978815610612575f98610e9b575b50610b7f83886001600160a01b03610b796123c4565b1661351a565b610b94868b6001600160a01b03610b796123db565b610baf87836001600160a01b03610ba96123c4565b166136f8565b610bc48a836001600160a01b03610ba96123db565b883b156106065760405193638c64ed9560e01b85526102e4356004860152602485015260448401523360648401525f60848401525f60a4840152610c0c60c48401606461301b565b610c1c61034484016102e461321f565b610364356103c484810191909152610384356103e48501526103a435610404850152356104248401525f8361044481838c5af190811561061257602493610c8d92610e8b575b50610c7d816001600160a01b03610c776123c4565b16613644565b6001600160a01b03610c776123db565b60206001600160a01b03610c9f6123c4565b16604051938480926370a0823160e01b82523060048301525afa8015610612575f90610e57575b610cd19250146132c5565b602460206001600160a01b03610ce56123db565b16604051928380926370a0823160e01b82523060048301525afa908115610612575f91610e24575b50602492610d1b911461331d565b60206001600160a01b03610d2d6123c4565b16604051938480926370a0823160e01b82528960048301525afa918215610612575f92610dee575b50610d639261051c9161252d565b60206001600160a01b03610d756123db565b16926024604051809581936370a0823160e01b835260048301525afa918215610612575f92610db8575b50610dad926105739161252d565b5f805460ff19169055005b9091506020813d602011610de6575b81610dd4602093836124cb565b81010312610606575190610dad610d9f565b3d9150610dc7565b9091506020813d602011610e1c575b81610e0a602093836124cb565b81010312610606575190610d63610d55565b3d9150610dfd565b90506020813d602011610e4f575b81610e3f602093836124cb565b8101031261060657516024610d0d565b3d9150610e32565b506020823d602011610e83575b81610e71602093836124cb565b8101031261060657610cd19151610cc6565b3d9150610e64565b5f610e95916124cb565b8a610c62565b9097506020813d602011610ec7575b81610eb7602093836124cb565b810103126106065751968a610b63565b3d9150610eaa565b93506020873d602011610efc575b81610eea602093836124cb565b81010312610606576024965193610b25565b3d9150610edd565b93506020863d602011610f31575b81610f1f602093836124cb565b81010312610606576024955193610ae7565b3d9150610f12565b506020853d602011610f64575b81610f53602093836124cb565b810103126106065760249451610aa9565b3d9150610f46565b50506001600160801b035f610a09565b9398949750509050610f8e818a61252d565b610224359062ffffff821680830361060657610a3e6298968091610fb394508561246b565b6102a435975062ffffff88169182890361060657610a3e610fdc93629896809260249b5061246b565b94831115610ffd576001600160801b0391610ff69161342d565b1695610a70565b50506001600160801b035f610ff6565b506109566001600160a01b036110216123db565b161515905061094d565b346106065736600319016103a081126106065760a013610606576102803660a319011261060657608036610323190112610606575f5461106e60ff82161561230d565b61107c60443560243561253a565b61108761014761236a565b6001600160a01b03611097612396565b16151580611b7e575b6110a990612f0f565b6110c46110b4612396565b6001600160a01b0361019a6123ad565b6001600160801b036110d4612fdc565b16151580611b65575b15611b0a5760ff19166001175f556001600160a01b036110fb61236a565b5f911660e4356001600160a01b038116908181036106065733821480611aef575b61187e575b506024905060206001600160a01b03611138612396565b16604051928380926370a0823160e01b82523360048301525afa8015610612575f9061184b575b6024915060206001600160a01b036111756123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611817575b506111a6612396565b6111ae612fdc565b90843b1561060657604051637d656cf760e01b8152915f91839182916111da91903033600486016124ed565b038183885af1801561061257611807575b506111f46123ad565b6111fc612ff2565b90843b1561060657604051637d656cf760e01b8152915f918391829161122891903033600486016124ed565b038183885af18015610612576117f7575b5061128c6020611247612396565b61124f612fdc565b60405163627160f360e11b81526001600160a01b0390921660048301526001600160801b0316602482015233604482015291829081906064820190565b03815f885af1908115610612575f916117c5575b506001600160801b036112b1612fdc565b1603611763576112cc60206112c46123ad565b61124f612ff2565b03815f885af1908115610612575f91611731575b506001600160801b036112f1612ff2565b16036116cf5760249060206001600160a01b0361130c612396565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611699575b50611350906001600160801b03611349612fdc565b169061252d565b0361163a5760249060206001600160a01b0361136a6123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611604575b506113a7906001600160801b03611349612ff2565b036115a5578161143c575b506113bb612396565b6113c3612fdc565b916001600160801b036113d46123ad565b6113dc612ff2565b90826040519616865260018060a01b03166020860152166040840152606083015260018060a01b03169061032435907f8e60feda1ea0461bfc923501c0e2478e40a145bb3c95a75eb987a1fd4afc85e260803392a45f805460ff19169055005b60405163627160f360e11b81525f600482018190526024820184905233604483018190523192602091839160649183915af180156106125783915f91611570575b50036115055761148f8233319261252d565b0361149a57816113b2565b60405162461bcd60e51b815260206004820152603960248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207265636569707420776173206e6f74206578616374000000000000006064820152608490fd5b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207769746864726177616c20776173206e6f74206578616374000000006064820152608490fd5b9150506020813d60201161159d575b8161158c602093836124cb565b81010312610606578290518461147d565b3d915061157f565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3220726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d602011611632575b81611620602093836124cb565b810103126106065751906113a7611392565b3d9150611613565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3120726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d6020116116c7575b816116b5602093836124cb565b81010312610606575190611350611334565b3d91506116a8565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e322077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d60201161175b575b8161174c602093836124cb565b810103126106065751856112e0565b3d915061173f565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e312077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d6020116117ef575b816117e0602093836124cb565b810103126106065751856112a0565b3d91506117d3565b5f611801916124cb565b84611239565b5f611811916124cb565b846111eb565b9091506020813d602011611843575b81611833602093836124cb565b810103126106065751908461119d565b3d9150611826565b506020813d602011611876575b81611865602093836124cb565b81010312610606576024905161115f565b3d9150611858565b909192506103243591833b15610606576040519163cad5e7a960e01b835283600484015260a4356001600160801b03811680910361060657602484015260c4356001600160801b0381168091036106065760448401525060648201526101043565ffffffffffff81168091036106065760848201526101243565ffffffffffff81168091036106065760a4820152610144356001600160a01b038116908190036106065760c48201526101643565ffffffffffff81168091036106065760e48201526101843565ffffffffffff8116809103610606576101048201526101a4356001600160801b038116809103610606576101248201526101c4356001600160a01b03811690819003610606576101448201526101e435916bffffffffffffffffffffffff8316809303610606576101648201839052610204356001600160a01b03811690819003610606576101848301526102243562ffffff8116809103610606576101a48301526102443562ffffff8116809103610606576101c48301526102643562ffffff8116809103610606576101e48301526102843561ffff8116809103610606576102048301526102a4356001600160a01b03811690819003610606576102248301526102c43563ffffffff8116809103610606576102448301526102e43562ffffff8116809103610606576102648301526103043560ff8116809103610606576102848301526102a4820152610344356001600160a01b03811690819003610606576102c4820152610364356102e4820152610384356103048201525f816103248183875af1801561061257611adf575b50908280611121565b5f611ae9916124cb565b82611ad6565b506101243565ffffffffffff8116809103610606571561111c565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c65206c6966656379636c6520616d6f756e7473206d757360448201526c7420626520706f73697469766560981b6064820152608490fd5b506001600160801b03611b76612ff2565b1615156110dd565b506110a96001600160a01b03611b926123ad565b16151590506110a0565b346106065760203660031901126106065760043567ffffffffffffffff811161060657366023820112156106065780600401359067ffffffffffffffff821161060657366024838301011161060657611c0f916024611bfb920161266d565b6040519182916020835260208301906122d5565b0390f35b346106065736600319016102c08112610606576102801361060657610284356001600160801b038116808203610606576102a4356001600160801b038116808203610606576004356001600160801b0381169485821415958661060657611c7a818561246b565b95602435976001600160801b03891696878a1415988961060657611c9e848a61246b565b1015611d3b5750610606571115611d2b576001600160801b0391611cc19161342d565b16926106065781611cd19161252d565b6101c4359062ffffff821680830361060657610a3e6298968091611cf694508561246b565b61024435935062ffffff84169182850361060657610a3e611d1f9362989680926040975061246b565b82519182526020820152f35b50506001600160801b035f611cc1565b9597505081925090611d52915f989694985061252d565b6101c4359062ffffff821680830361060657610a3e6298968091611d7794508561246b565b61024435965062ffffff87169182880361060657610a3e611da093629896809260409a5061246b565b931115611dbf576001600160801b0391611db99161342d565b16611d1f565b50506001600160801b035f611db9565b3461060657604036600319011261060657611dee60243560043561253a565b005b3461060657366003190160c081126106065760a013610606575f54611e1860ff82161561230d565b611e2660643560443561253a565b611e3161014761236a565b611e39612380565b3b1561226057608435908115612208577003fffffffffffffffffffffffffffffffc82116121ab5760ff19166001175f55602460206001600160a01b03611e7e612380565b16604051928380926370a0823160e01b82523360048301525afa908115610612575f91612179575b506001600160a01b03611eb761236a565b1682805b6120e057506020611eca612380565b60405163627160f360e11b81526001600160a01b0390911660048201526024810185905233604482015291829060649082905f905af180156106125783915f916120ab575b500361204757602460206001600160a01b03611f29612380565b16604051928380926370a0823160e01b82523360048301525afa9081156106125783905f92612011575b50611f5e919261252d565b03611fb0576001600160a01b03611f73612380565b169060405190815260a435907f158110513241d1756a66549dae81bce1d1cbc3db022d0fe8a62af9206771abb160203392a45f805460ff19169055005b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c65207265706c6163656d656e742063726564697420726560448201527218d95a5c1d081dd85cc81b9bdd08195e1858dd606a1b6064820152608490fd5b9150506020813d60201161203f575b8161202d602093836124cb565b81010312610606575182611f5e611f53565b3d9150612020565b60405162461bcd60e51b815260206004820152603660248201527f4f70656e4f7261636c65207265706c6163656d656e74206372656469742077696044820152751d1a191c985dd85b081dd85cc81b9bdd08195e1858dd60521b6064820152608490fd5b9150506020813d6020116120d8575b816120c7602093836124cb565b810103126106065782905184611f0f565b3d91506120ba565b6001600160801b03811115612169576001600160801b03905b612101612380565b91833b15610606575f8161212c946040519586928392637d656cf760e01b84523033600486016124ed565b038183885af190811561061257612153936001600160801b0392612159575b501690612520565b80611ebb565b5f612163916124cb565b8761214b565b6001600160801b038116906120f9565b90506020813d6020116121a3575b81612194602093836124cb565b81010312610606575182611ea6565b3d9150612187565b60405162461bcd60e51b815260206004820152602f60248201527f5265706c6163656d656e742063726564697420616d6f756e742065786365656460448201526e73207265706f727420626f756e647360881b6064820152608490fd5b60405162461bcd60e51b815260206004820152602a60248201527f5265706c6163656d656e742063726564697420616d6f756e74206d75737420626044820152696520706f73697469766560b01b6064820152608490fd5b60405162461bcd60e51b815260206004820152603360248201527f5265706c6163656d656e742063726564697420746f6b656e206d75737420636f6044820152726e7461696e20636f6e747261637420636f646560681b6064820152608490fd5b35906001600160801b038216820361060657565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b35906001600160a01b038216820361060657565b1561231457565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c6520617262697472616765206578656375746f72207265604482015267656e7472616e637960c01b6064820152608490fd5b6004356001600160a01b03811681036106065790565b6024356001600160a01b03811681036106065790565b610144356001600160a01b03811681036106065790565b610204356001600160a01b03811681036106065790565b610104356001600160a01b03811681036106065790565b6101c4356001600160a01b03811681036106065790565b610284356001600160a01b03811681036106065790565b1561241057565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c652061646472657373206d75737420636f6e7461696e2060448201526c636f6e747261637420636f646560981b6064820152608490fd5b8181029291811591840414171561247e57565b634e487b7160e01b5f52601160045260245ffd5b60a0810190811067ffffffffffffffff8211176106ea57604052565b610100810190811067ffffffffffffffff8211176106ea57604052565b90601f8019910116810190811067ffffffffffffffff8211176106ea57604052565b6001600160a01b039182168152918116602083015290911660408201526001600160801b03909116606082015260800190565b9190820391821161247e57565b9190820180921161247e57565b904315158061260e575b156125bd5780151591826125b2575b50501561255c57565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2063616e6f6e6963616c20706172656e7420626c6f636b6044820152670818da185b99d95960c21b6064820152608490fd5b401490505f80612553565b60405162461bcd60e51b8152602060048201526024808201527f457865637574696f6e206d7573742074617267657420746865206e65787420626044820152636c6f636b60e01b6064820152608490fd5b505f19430143811161247e578214612544565b67ffffffffffffffff81116106ea57601f01601f191660200190565b359062ffffff8216820361060657565b600f0b6f7fffffffffffffffffffffffffffffff19811461247e575f0390565b906060905f905f5460ff811680612ef9575b15612ea45761268d82612621565b61269a60405191826124cb565b828152602081019083870193368511610606576020815f928a86378301015251902060015403612e4e57610100600160a81b0319165f90815560015560a09084900312610606576040516126ed81612492565b6126f6846122f9565b908181526127066020860161263d565b918260208301526040860135908115158203610606576040830191825262ffffff868401948789013586526080808601990135895216906127468261344d565b9060405161275381612492565b5f8152602081019160018060a01b03168252604081019384528881019260020b835260808101925f84528551151590815f14612e3a5788515b875115612e1f576401000276a4905b6040519c8d01938d851067ffffffffffffffff8611176106ea57612864946040528d5260208d0190815260408d019160018060a01b0316825260209c8d97604051946127e78a876124cb565b5f8652604051633cf3645360e21b815297516001600160a01b0390811660048a0152985189166024890152995162ffffff166044880152985160020b60648701529751861660848601529651151560a4850152955160c4840152945190921660e482015261012061010482015292839182916101248301906122d5565b03815f335af1908115610612575f91612df2575b508060801d916001600160801b0383600f0b9216600f0b9051612b72576128a28551600f0b61264d565b600f0b03612b2157841215612ad1576001600160801b031694518510612a755780516001600160a01b0316333b15612a715760405190632961046560e21b82526004820152838160248183335af18015612a6657908491612a4d575b50505181516129179133906001600160a01b03166134d0565b604051630476982d60e21b815290838260048186335af1918215612a42578392612a13575b5051036129bc57333b156129ae57604051630b0d9c0960e01b81526004810182905230602482015260448101849052818160648183335af180156129b157612999575b5050604051918183015281526129966040826124cb565b90565b6129a48280926124cb565b6129ae578061297f565b80fd5b6040513d84823e3d90fd5b60405162461bcd60e51b815260048101839052602960248201527f556e697377617020563420746f6b656e20736574746c656d656e7420776173206044820152681b9bdd08195e1858dd60ba1b6064820152608490fd5b9091508381813d8311612a3b575b612a2b81836124cb565b810103126106065751905f61293c565b503d612a21565b6040513d85823e3d90fd5b81612a57916124cb565b612a6257825f6128fe565b8280fd5b6040513d86823e3d90fd5b8380fd5b60405162461bcd60e51b815260048101859052602e60248201527f556e69737761702056342073656c6c206865646765207265636569766564207460448201526d0dede40d8d2e8e8d8ca40ae8aa8960931b6064820152608490fd5b60405162461bcd60e51b815260048101869052602260248201527f556e69737761702056342073656c6c206f75747075742077617320696e76616c6044820152611a5960f21b6064820152608490fd5b60405162461bcd60e51b815260048101879052602360248201527f556e69737761702056342073656c6c20696e70757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b9091508351600f0b03612da1575f811215612d5e57612b986001600160801b039161264d565b1694518511612d0657333b1561060657604051632961046560e21b81525f600482018190528160248183335af1801561061257612cf1575b50604051630476982d60e21b8152848160048189335af1908115612a66579086918591612cc0575b5003612c6857519051906001600160a01b0316333b15612a6257604051630b0d9c0960e01b81526001600160a01b039190911660048201523060248201526044810191909152818160648183335af180156129b157612999575050604051918183015281526129966040826124cb565b60405162461bcd60e51b815260048101859052602a60248201527f556e6973776170205634206e617469766520736574746c656d656e7420776173604482015269081b9bdd08195e1858dd60b21b6064820152608490fd5b809250868092503d8311612cea575b612cd981836124cb565b81010312610606578590515f612bf8565b503d612ccf565b612cfe9193505f906124cb565b5f915f612bd0565b60405162461bcd60e51b815260048101859052602a60248201527f556e697377617020563420627579206865646765206578636565646564206d616044820152690f0d2daeada40ae8aa8960b31b6064820152608490fd5b6064856040519062461bcd60e51b825280600483015260248201527f556e69737761702056342062757920696e7075742077617320696e76616c69646044820152fd5b60405162461bcd60e51b815260048101869052602360248201527f556e697377617020563420627579206f757470757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b90508581813d8311612e18575b612e0981836124cb565b8101031261060657515f612878565b503d612dff565b73fffd8963efd1fc6a506488495d951d5263988d259061279b565b8851600160ff1b811461247e575f0361278c565b60405162461bcd60e51b815260206004820152602860248201527f556e617574686f72697a656420556e69737761702056342063616c6c6261636b604482015267081c185e5b1bd85960c21b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f556e617574686f72697a656420556e697377617020563420756e6c6f636b2063604482015266616c6c6261636b60c81b6064820152608490fd5b5033600882901c6001600160a01b03161461267f565b15612f1657565b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220726560448201527271756972657320455243323020746f6b656e7360681b6064820152608490fd5b15612f7e57565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220746f60448201526f35b2b7399036bab9ba103234b33332b960811b6064820152608490fd5b6064356001600160801b03811681036106065790565b6084356001600160801b03811681036106065790565b359065ffffffffffff8216820361060657565b6001600160801b0361302c826122c1565b1682526001600160801b03613043602083016122c1565b1660208301526001600160a01b0361305d604083016122f9565b16604083015265ffffffffffff61307660608301613008565b16606083015265ffffffffffff61308f60808301613008565b1660808301526001600160a01b036130a960a083016122f9565b1660a083015265ffffffffffff6130c260c08301613008565b1660c083015265ffffffffffff6130db60e08301613008565b1660e08301526001600160801b036130f661010083016122c1565b166101008301526001600160a01b0361311261012083016122f9565b166101208301526101408101356bffffffffffffffffffffffff8116809103610606576101408301526001600160a01b0361315061016083016122f9565b1661016083015262ffffff613168610180830161263d565b1661018083015262ffffff6131806101a0830161263d565b166101a083015262ffffff6131986101c0830161263d565b166101c08301526101e081013561ffff8116809103610606576101e08301526001600160a01b036131cc61020083016122f9565b166102008301526102208101359063ffffffff8216809203610606576102609161022084015262ffffff613203610240830161263d565b1661024084015201359060ff8216809203610606576102600152565b8035825260609081906001600160a01b0361323c602083016122f9565b166020850152604081013560408501520135910152565b1561325a57565b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220646f60448201527f6573206e6f7420737570706f72742073656c662d6469737075746573000000006064820152608490fd5b156132cc57565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e31207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561332457565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e32207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561337c57565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e31207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b156133d857565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e32207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b906001600160801b03809116911603906001600160801b03821161247e57565b62ffffff16606481146134ca576101f481146134c457610bb881146134be57612710146134b95760405162461bcd60e51b815260206004820152601f60248201527f556e737570706f7274656420556e697377617020563420706f6f6c20666565006044820152606490fd5b60c890565b50603c90565b50600a90565b50600190565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526135189161351382606481015b03601f1981018452836124cb565b614920565b565b90801561363f576040516323b872dd60e01b60208281019190915233602483810191909152306044840152606483018490529390916135709061356a81608481015b03601f1981018352826124cb565b82614920565b6040516370a0823160e01b815230600482015293849182906001600160a01b03165afa918215610612575f92613609575b506135ac919261252d565b036135b357565b60405162461bcd60e51b815260206004820152602860248201527f546f6b656e207472616e7366657220746f206578656375746f7220776173206e6044820152671bdd08195e1858dd60c21b6064820152608490fd5b91506020823d602011613637575b81613624602093836124cb565b81010312610606576135ac9151916135a1565b3d9150613617565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529192916020908290604490829087165afa908115610612575f916136c6575b5061368f575050565b60405163095ea7b360e01b60208201526001600160a01b0390911660248201525f6044820152613518916135138260648101613505565b90506020813d6020116136f0575b816136e1602093836124cb565b8101031261060657515f613686565b3d91506136d4565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529293926020908290604490829086165afa908115610612575f916137bf575b50613784575b8161374a57505050565b60405163095ea7b360e01b60208201526001600160a01b039093166024840152604483019190915261351891906135138260648101613505565b60405163095ea7b360e01b60208201526001600160a01b03841660248201525f60448201526137ba9061356a816064810161355c565b613740565b90506020813d6020116137e9575b816137da602093836124cb565b8101031261060657515f61373a565b3d91506137cd565b604051906137fe82612492565b5f6080838281528260208201528260408201528260608201520152565b6138236137f1565b90608435906001600160801b038216918281036106065760a435916001600160801b0383169384840361060657610124356001600160801b0381168103610606576138776001600160801b0382168761246b565b9461014435956001600160801b0387168703610606576138a0846001600160801b03891661246b565b1015614820576001600160801b0382168203610606576001600160801b038216831115614812576001600160801b0382168203610606576001600160801b036138e9838761342d565b16916001600160801b03871687036106065761390e6001600160801b0388168961252d565b6001600160801b0388168803610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846139509550166001600160801b038c1661246b565b6001600160801b038816880361060657610364359062ffffff821682036106065762989680610a3e62ffffff846139929550166001600160801b038c1661246b565b915b60208a019360408b019384528452505f976001600160801b03821690818303610606576139c1925061246b565b6001600160801b03871695868814159485610606576139e191508761246b565b108089526141245750506139f36137f1565b506101c4356001600160a01b03811680821415949193918561412057610284356001600160a01b03811697888214159591949186613d9c57613d945790613a69915060608b01928352613a4d60208c01518251908661351a565b6020613a5f60408d015185519061252d565b910151908961351a565b6044359460ff861694858703613d9457600286149788156140fa5750505f5f94835160643562ffffff8116810361060657613aa38161344d565b5060016001607f1b038211614099574791602435906001600160a01b0382168203610606578e54610100600160a81b031916600883901b610100600160a81b0316178f5562ffffff83168303610606578e8e916080604051613b0481612492565b84815262ffffff60208201971687526040810193845260608101928352019160c435835262ffffff60405196602088019586525116604087015251151560608601525160808501525160a084015260a08352613b6160c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918e918391829084908290613ba59060248301906122d5565b03926001600160a01b03165af1908115613f56578d91614017575b508c5460081c6001600160a01b0316158061400d575b15613fbc5760208151918180820193849201010312610606575190613bfc82479261252d565b03613f615780613f0b575b60808d01525b5f9680613da457506024356001600160a01b0381169690878103613da05750613d9c5750613d9857506064359062ffffff821691828103613d94575051604051602093613cac936101049391613c62846124ae565b8b845286840152604083015230606083015260e435608083015260a082015260c43560c08201528960e082015289604051958694859363414bf38960e01b85526004850190614c11565b5af1908115613d89578691613d57575b5060808701525b608086015160c43511613cfe57612a71575015613cdf57505090565b602435916001600160a01b03831683036129ae57509061299691613644565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761702073656c6c20686564676520726563656976656420746f6f2060448201526a0d8d2e8e8d8ca40ae8aa8960ab1b6064820152608490fd5b90506020813d602011613d81575b81613d72602093836124cb565b8101031261060657515f613cbc565b3d9150613d65565b6040513d88823e3d90fd5b8980fd5b8880fd5b8a80fd5b8c80fd5b5f975093959493600103613ef55760405194613dc16060876124cb565b600286526040366020880137613d9c575088613ddc85614af2565b52613d985750613deb82614b13565b526024356001600160a01b03811690818103613d9857918891613e339493508351836040518097819582946338ed173960e01b845260e43591309160c4359060048701614ba9565b03925af1918215613eea578792613ec6575b5060028251149081613eb1575b5015613e6c57613e6190614b13565b516080870152613cc3565b60405162461bcd60e51b815260206004820152601f60248201527f556e69737761702056322073656c6c20616d6f756e747320696e76616c6964006044820152606490fd5b9050613ebc82614af2565b519051145f613e52565b613ee39192503d8089833e613edb81836124cb565b810190614b23565b905f613e45565b6040513d89823e3d90fd5b505050505050505f613f0683614aa6565b613cc3565b853b15613f5257604051630d0e30db60e41b81528c81600481858b5af18015613f5657908d91613f3d575b5050613c07565b81613f47916124cb565b613f52578b5f613f36565b8b80fd5b6040513d8f823e3d90fd5b60405162461bcd60e51b815260206004820152602d60248201527f556e6973776170205634206e61746976652062616c616e63652064656c74612060448201526c1dd85cc81b9bdd08195e1858dd609a1b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f556e69737761702056342063616c6c6261636b20776173206e6f7420636f6e736044820152631d5b595960e21b6064820152608490fd5b5060015415613bd6565b90503d808e833e61402881836124cb565b8101906020818303126140955780519067ffffffffffffffff8211614091570181601f820112156140955780519061405f82612621565b9261406d60405194856124cb565b8284526020838301011161409157818f9260208093018386015e830101525f613bc0565b8e80fd5b8d80fd5b60405162461bcd60e51b815260206004820152603360248201527f556e697377617020563420686564676520616d6f756e742065786365656473206044820152727369676e65642064656c746120626f756e647360681b6064820152608490fd5b90946024356001600160a01b0381168103613f5257845161411b918c6136f8565b613c0d565b8780fd5b909291506141339493946137f1565b506101c4356001600160a01b0381169586821415938461060657610284359560018060a01b03871692838814159889610606576102e43562ffffff81168082036106065761418a629896809161419193508561246b565b048361252d565b9050610364359162ffffff831692838103610606576141b9936298968092610a3e925061246b565b9160608c01928084528651106147b45760206141f66142009351976141ed8d6141e560c435809c61252d565b86519161351a565b51855190612520565b910151908461351a565b6044359560ff871695868803610606576002871498891561478e5750505f935f9083516064359062ffffff821682036106065761423c8261344d565b5060016001607f1b0381116140995747918d3b15610606575f808f60248d6040519485938492632e1a7d4d60e01b845260048401525af1801561061257614779575b50602435906001600160a01b0382168203610606578f8054610100600160a81b038460081b1690610100600160a81b03191617905562ffffff8116810361060657604051926142cc84612492565b89845262ffffff602085019216825260806040850194600186526060810192835201908c82526040519462ffffff60208701948d86525116604087015251151560608601525160808501525160a084015260a0835261432c60c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918f9183918290849082906143709060248301906122d5565b03926001600160a01b03165af19081156146e4578e916146fb575b508d5460081c6001600160a01b031615806146f1575b15613fbc57602081519181808201938492010103126106065751906143c6828a612520565b906143d282479261252d565b03613f61578e81614697575b6080915001525b5f978061453957506024356001600160a01b03811696908781036140955750613f525750613d9457506064359062ffffff821691828103613d9c57505160405160209361448293610104939161443a846124ae565b8c845286840152604083015230606083015260e435608083015260a08201528560c08201528a60e08201528a6040519586948593631b67c43360e31b85526004850190614c11565b5af1908115613eea578791614507575b5060808801525b6080870151116144b257612a71575015613cdf57505090565b60405162461bcd60e51b815260206004820152602760248201527f556e697377617020627579206865646765206578636565646564206d6178696d6044820152660eada40ae8aa8960cb1b6064820152608490fd5b90506020813d602011614531575b81614522602093836124cb565b8101031261060657515f614492565b3d9150614515565b5f98509395949360010361467f57604051946145566060876124cb565b600286526040366020880137613f5257508961457185614af2565b52613d94575061458082614b13565b526024356001600160a01b03811690818103613d94579189916145c5949350835183604051809781958294634401edf760e11b845260e435918c309260048701614ba9565b03925af1918215614674578892614658575b5060028251149081614643575b50156145fe576145f390614af2565b516080880152614499565b60405162461bcd60e51b815260206004820152601e60248201527f556e69737761702056322062757920616d6f756e747320696e76616c696400006044820152606490fd5b905061464e82614b13565b519051145f6145e4565b61466d9192503d808a833e613edb81836124cb565b905f6145d7565b6040513d8a823e3d90fd5b50505050505090505f9061469284614aa6565b614499565b508c3b15614095578d8d600460405180948193630d0e30db60e41b83525af180156146e4578f90918f926146cc575b506143de565b6146d8915082906124cb565b613da0578c8e5f6146c6565b8e604051903d90823e3d90fd5b50600154156143a1565b90503d808f833e61470c81836124cb565b8101906020818303126140915780519067ffffffffffffffff8211614775570181601f820112156140915780519061474382612621565b9261475160405194856124cb565b82845260208383010111614775578f918060208093018386015e830101525f61438b565b8f80fd5b614786919f505f906124cb565b5f9d5f61427e565b90946024356001600160a01b0381168103610606576147af9088908d6136f8565b6143e5565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520627579206865646765206578636565647320746f6b60448201526f32b7191031b7b73a3934b13aba34b7b760811b6064820152608490fd5b6001600160801b035f6138e9565b6001600160801b0382168203610606576148436001600160801b0383168461252d565b6001600160801b0383168303610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846148859550166001600160801b03871661246b565b6001600160801b038316830361060657610364359062ffffff821682036106065762989680610a3e62ffffff846148c79550166001600160801b03871661246b565b916001600160801b0387168703610606576001600160801b038716881115614912576001600160801b0387168703610606576001600160801b0361490b888461342d565b1691613994565b6001600160801b035f61490b565b6001600160a01b0316803b15614a4357815f92918360208194519301915af13d15614a3b573d9061495082612621565b9161495e60405193846124cb565b82523d5f602084013e5b156149f757805180614978575050565b816020918101031261060657602001518015908115036106065761499857565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090614968565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b15614aad57565b60405162461bcd60e51b815260206004820152601760248201527f556e737570706f727465642068656467652076656e75650000000000000000006044820152606490fd5b805115614aff5760200190565b634e487b7160e01b5f52603260045260245ffd5b805160011015614aff5760400190565b6020818303126106065780519067ffffffffffffffff821161060657019080601f830112156106065781519167ffffffffffffffff83116106ea578260051b906020820193614b7560405195866124cb565b845260208085019282010192831161060657602001905b828210614b995750505090565b8151815260209182019101614b8c565b92919594939560a08401918452602084015260a060408401528151809152602060c084019201905f5b818110614bf2575050506001600160a01b03909416606082015260800152565b82516001600160a01b0316845260209384019390920191600101614bd2565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff16908401526060808301518216908401526080808301519084015260a0808301519084015260c0808301519084015260e0918201511691015256fea26469706673582212205580925ec9cf30c28f96f24a2efd4caebf322aa533aa00c4d93f3b23458957df64736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610087575b3615610018575f80fd5b60ff5f54161561002457005b60405162461bcd60e51b815260206004820152603560248201527f4f70656e4f7261636c6520617262697472616765206578656375746f722072656044820152740d4cac6e8e640eadce6ded8d2c6d2e8cac8408aa89605b1b6064820152608490fd5b5f3560e01c806329d76f5f14611df05780633b0b89cc14611dcf5780637f8d61f414611c1357806391dd734614611b9c578063a7b06edf1461102b578063cc5eed78146108a25763f6a2b58e0361000e57346106065736600319016104a0811261060657610120136106065761028036610123190112610606576080366103a319011261060657608036610423190112610606575f5461012a60ff82161561230d565b610424359061013c610104358361253a565b61014f61014761236a565b3b1515612409565b610157612380565b3b15610843576001600160a01b0361016d6123db565b16151580610825575b61017f90612f0f565b6101ae61018a6123db565b6001600160a01b0361019a6123f2565b6001600160a01b0390921691161415612f77565b610164356001600160a01b038116808203610606576101d09150331415613253565b60e43542116107cc5760ff19166001175f556001600160a01b036101f26123db565b166001600160a01b036102036123f2565b16906040516370a0823160e01b8152306004820152602081602481855afa908115610612575f9161079a575b506040516370a0823160e01b815230600482015292602084602481845afa938415610612575f94610766575b5061026461236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292602084602481845afa938415610612575f94610732575b506102a261236a565b6040516370a0823160e01b81526001600160a01b03909116600482015290602082602481865afa918215610612575f926106fe575b50604051936080850185811067ffffffffffffffff8211176106ea5760405284526020840195865260408401948552606084019182526103168461381b565b9461031f61236a565b966103316020880198895190856136f8565b61033961236a565b9561034b6040890197885190886136f8565b6001600160a01b0361035b61236a565b166103a4359a610369612ff2565b9160a435906001600160801b038216918281036106065750803b15610606575f928e926001600160801b03604051968795638c64ed9560e01b8752600487015216602485015260448401523360648401528360848401528360a48401526103d560c4840161012461301b565b6103e561034484016103a461321f565b6103c483015261044480356103e4840152610464356104048401526104843561042484015290829084905af18015610612576106da575b5061042e61042861236a565b85613644565b61043f61043961236a565b87613644565b8751156106d057610456608089015160c435612520565b806106bf575b506040516370a0823160e01b815230600482015290602082602481885afa908115610612575f91610689575b610494925051146132c5565b6040516370a0823160e01b815230600482015290602082602481895afa908115610612575f91610653575b6104cb9250511461331d565b60206104d561236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f9261061d575b509061051c610522925188519061252d565b14613375565b602061052c61236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f926105d8575b5090610573610579925184519061252d565b146133d1565b815115159260806060840151930151905191519260405194855260208501526040840152606083015260808201527fb87b87bb3eacf9e5cf9ed834f7142300b841a66f3e9695eb6d3f6e7e89257ea560a03392a35f805460ff19169055005b91506020823d60201161060a575b816105f3602093836124cb565b8101031261060657905190610573610561565b5f80fd5b3d91506105e6565b6040513d5f823e3d90fd5b91506020823d60201161064b575b81610638602093836124cb565b810103126106065790519061051c61050a565b3d915061062b565b90506020823d602011610681575b8161066e602093836124cb565b81010312610606576104cb9151906104bf565b3d9150610661565b90506020823d6020116106b7575b816106a4602093836124cb565b8101031261060657610494915190610488565b3d9150610697565b6106ca9033866134d0565b5f61045c565b6080880151610456565b5f6106e4916124cb565b5f61041c565b634e487b7160e01b5f52604160045260245ffd5b9091506020813d60201161072a575b8161071a602093836124cb565b810103126106065751905f6102d7565b3d915061070d565b9093506020813d60201161075e575b8161074e602093836124cb565b810103126106065751925f610299565b3d9150610741565b9093506020813d602011610792575b81610782602093836124cb565b810103126106065751925f61025b565b3d9150610775565b90506020813d6020116107c4575b816107b5602093836124cb565b8101031261060657515f61022f565b3d91506107a8565b60405162461bcd60e51b815260206004820152602b60248201527f4f70656e4f7261636c652061726269747261676520686564676520646561646c60448201526a1a5b9948195e1c1a5c995960aa1b6064820152608490fd5b5061017f6001600160a01b036108396123f2565b1615159050610176565b60405162461bcd60e51b815260206004820152603160248201527f556e697377617020726f757465722061646472657373206d75737420636f6e7460448201527061696e20636f6e747261637420636f646560781b6064820152608490fd5b34610606576103e0366003190112610606576004356001600160a01b038116808203610606576024356001600160801b0381169283820361060657604435906001600160801b03821680830361060657610280366063190112610606576080366102e319011261060657608036610363190112610606575f5461092860ff82161561230d565b610934833b1515612409565b6001600160a01b036109446123c4565b1615158061100d575b61095690612f0f565b6109716109616123c4565b6001600160a01b0361019a6123db565b60a4356001600160a01b0381169190828103610606576001926109979150331415613253565b60ff1916175f55606435926001600160801b03841693848114159586610606576109c1868561246b565b95608435976001600160801b03891694858a14159889610606576109e58d8861246b565b1015610f7c575061060657891115610f6c576001600160801b0391610a099161342d565b169361060657610a19818361252d565b610224359062ffffff821680830361060657610a3e6298968091610a4594508561246b565b049061252d565b6102a435955062ffffff86169182870361060657610a3e610a6e9362989680926024995061246b565b955b60206001600160a01b03610a826123c4565b16604051968780926370a0823160e01b82523060048301525afa8015610612575f90610f39575b6024955060206001600160a01b03610abf6123db565b16604051978880926370a0823160e01b82523060048301525afa938415610612575f94610f04575b6024965060206001600160a01b03610afd6123c4565b16604051988980926370a0823160e01b82528c60048301525afa938415610612575f94610ecf575b6024975060206001600160a01b03610b3b6123db565b16604051998a80926370a0823160e01b82528d60048301525afa978815610612575f98610e9b575b50610b7f83886001600160a01b03610b796123c4565b1661351a565b610b94868b6001600160a01b03610b796123db565b610baf87836001600160a01b03610ba96123c4565b166136f8565b610bc48a836001600160a01b03610ba96123db565b883b156106065760405193638c64ed9560e01b85526102e4356004860152602485015260448401523360648401525f60848401525f60a4840152610c0c60c48401606461301b565b610c1c61034484016102e461321f565b610364356103c484810191909152610384356103e48501526103a435610404850152356104248401525f8361044481838c5af190811561061257602493610c8d92610e8b575b50610c7d816001600160a01b03610c776123c4565b16613644565b6001600160a01b03610c776123db565b60206001600160a01b03610c9f6123c4565b16604051938480926370a0823160e01b82523060048301525afa8015610612575f90610e57575b610cd19250146132c5565b602460206001600160a01b03610ce56123db565b16604051928380926370a0823160e01b82523060048301525afa908115610612575f91610e24575b50602492610d1b911461331d565b60206001600160a01b03610d2d6123c4565b16604051938480926370a0823160e01b82528960048301525afa918215610612575f92610dee575b50610d639261051c9161252d565b60206001600160a01b03610d756123db565b16926024604051809581936370a0823160e01b835260048301525afa918215610612575f92610db8575b50610dad926105739161252d565b5f805460ff19169055005b9091506020813d602011610de6575b81610dd4602093836124cb565b81010312610606575190610dad610d9f565b3d9150610dc7565b9091506020813d602011610e1c575b81610e0a602093836124cb565b81010312610606575190610d63610d55565b3d9150610dfd565b90506020813d602011610e4f575b81610e3f602093836124cb565b8101031261060657516024610d0d565b3d9150610e32565b506020823d602011610e83575b81610e71602093836124cb565b8101031261060657610cd19151610cc6565b3d9150610e64565b5f610e95916124cb565b8a610c62565b9097506020813d602011610ec7575b81610eb7602093836124cb565b810103126106065751968a610b63565b3d9150610eaa565b93506020873d602011610efc575b81610eea602093836124cb565b81010312610606576024965193610b25565b3d9150610edd565b93506020863d602011610f31575b81610f1f602093836124cb565b81010312610606576024955193610ae7565b3d9150610f12565b506020853d602011610f64575b81610f53602093836124cb565b810103126106065760249451610aa9565b3d9150610f46565b50506001600160801b035f610a09565b9398949750509050610f8e818a61252d565b610224359062ffffff821680830361060657610a3e6298968091610fb394508561246b565b6102a435975062ffffff88169182890361060657610a3e610fdc93629896809260249b5061246b565b94831115610ffd576001600160801b0391610ff69161342d565b1695610a70565b50506001600160801b035f610ff6565b506109566001600160a01b036110216123db565b161515905061094d565b346106065736600319016103a081126106065760a013610606576102803660a319011261060657608036610323190112610606575f5461106e60ff82161561230d565b61107c60443560243561253a565b61108761014761236a565b6001600160a01b03611097612396565b16151580611b7e575b6110a990612f0f565b6110c46110b4612396565b6001600160a01b0361019a6123ad565b6001600160801b036110d4612fdc565b16151580611b65575b15611b0a5760ff19166001175f556001600160a01b036110fb61236a565b5f911660e4356001600160a01b038116908181036106065733821480611aef575b61187e575b506024905060206001600160a01b03611138612396565b16604051928380926370a0823160e01b82523360048301525afa8015610612575f9061184b575b6024915060206001600160a01b036111756123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611817575b506111a6612396565b6111ae612fdc565b90843b1561060657604051637d656cf760e01b8152915f91839182916111da91903033600486016124ed565b038183885af1801561061257611807575b506111f46123ad565b6111fc612ff2565b90843b1561060657604051637d656cf760e01b8152915f918391829161122891903033600486016124ed565b038183885af18015610612576117f7575b5061128c6020611247612396565b61124f612fdc565b60405163627160f360e11b81526001600160a01b0390921660048301526001600160801b0316602482015233604482015291829081906064820190565b03815f885af1908115610612575f916117c5575b506001600160801b036112b1612fdc565b1603611763576112cc60206112c46123ad565b61124f612ff2565b03815f885af1908115610612575f91611731575b506001600160801b036112f1612ff2565b16036116cf5760249060206001600160a01b0361130c612396565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611699575b50611350906001600160801b03611349612fdc565b169061252d565b0361163a5760249060206001600160a01b0361136a6123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611604575b506113a7906001600160801b03611349612ff2565b036115a5578161143c575b506113bb612396565b6113c3612fdc565b916001600160801b036113d46123ad565b6113dc612ff2565b90826040519616865260018060a01b03166020860152166040840152606083015260018060a01b03169061032435907f8e60feda1ea0461bfc923501c0e2478e40a145bb3c95a75eb987a1fd4afc85e260803392a45f805460ff19169055005b60405163627160f360e11b81525f600482018190526024820184905233604483018190523192602091839160649183915af180156106125783915f91611570575b50036115055761148f8233319261252d565b0361149a57816113b2565b60405162461bcd60e51b815260206004820152603960248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207265636569707420776173206e6f74206578616374000000000000006064820152608490fd5b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207769746864726177616c20776173206e6f74206578616374000000006064820152608490fd5b9150506020813d60201161159d575b8161158c602093836124cb565b81010312610606578290518461147d565b3d915061157f565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3220726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d602011611632575b81611620602093836124cb565b810103126106065751906113a7611392565b3d9150611613565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3120726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d6020116116c7575b816116b5602093836124cb565b81010312610606575190611350611334565b3d91506116a8565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e322077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d60201161175b575b8161174c602093836124cb565b810103126106065751856112e0565b3d915061173f565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e312077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d6020116117ef575b816117e0602093836124cb565b810103126106065751856112a0565b3d91506117d3565b5f611801916124cb565b84611239565b5f611811916124cb565b846111eb565b9091506020813d602011611843575b81611833602093836124cb565b810103126106065751908461119d565b3d9150611826565b506020813d602011611876575b81611865602093836124cb565b81010312610606576024905161115f565b3d9150611858565b909192506103243591833b15610606576040519163cad5e7a960e01b835283600484015260a4356001600160801b03811680910361060657602484015260c4356001600160801b0381168091036106065760448401525060648201526101043565ffffffffffff81168091036106065760848201526101243565ffffffffffff81168091036106065760a4820152610144356001600160a01b038116908190036106065760c48201526101643565ffffffffffff81168091036106065760e48201526101843565ffffffffffff8116809103610606576101048201526101a4356001600160801b038116809103610606576101248201526101c4356001600160a01b03811690819003610606576101448201526101e435916bffffffffffffffffffffffff8316809303610606576101648201839052610204356001600160a01b03811690819003610606576101848301526102243562ffffff8116809103610606576101a48301526102443562ffffff8116809103610606576101c48301526102643562ffffff8116809103610606576101e48301526102843561ffff8116809103610606576102048301526102a4356001600160a01b03811690819003610606576102248301526102c43563ffffffff8116809103610606576102448301526102e43562ffffff8116809103610606576102648301526103043560ff8116809103610606576102848301526102a4820152610344356001600160a01b03811690819003610606576102c4820152610364356102e4820152610384356103048201525f816103248183875af1801561061257611adf575b50908280611121565b5f611ae9916124cb565b82611ad6565b506101243565ffffffffffff8116809103610606571561111c565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c65206c6966656379636c6520616d6f756e7473206d757360448201526c7420626520706f73697469766560981b6064820152608490fd5b506001600160801b03611b76612ff2565b1615156110dd565b506110a96001600160a01b03611b926123ad565b16151590506110a0565b346106065760203660031901126106065760043567ffffffffffffffff811161060657366023820112156106065780600401359067ffffffffffffffff821161060657366024838301011161060657611c0f916024611bfb920161266d565b6040519182916020835260208301906122d5565b0390f35b346106065736600319016102c08112610606576102801361060657610284356001600160801b038116808203610606576102a4356001600160801b038116808203610606576004356001600160801b0381169485821415958661060657611c7a818561246b565b95602435976001600160801b03891696878a1415988961060657611c9e848a61246b565b1015611d3b5750610606571115611d2b576001600160801b0391611cc19161342d565b16926106065781611cd19161252d565b6101c4359062ffffff821680830361060657610a3e6298968091611cf694508561246b565b61024435935062ffffff84169182850361060657610a3e611d1f9362989680926040975061246b565b82519182526020820152f35b50506001600160801b035f611cc1565b9597505081925090611d52915f989694985061252d565b6101c4359062ffffff821680830361060657610a3e6298968091611d7794508561246b565b61024435965062ffffff87169182880361060657610a3e611da093629896809260409a5061246b565b931115611dbf576001600160801b0391611db99161342d565b16611d1f565b50506001600160801b035f611db9565b3461060657604036600319011261060657611dee60243560043561253a565b005b3461060657366003190160c081126106065760a013610606575f54611e1860ff82161561230d565b611e2660643560443561253a565b611e3161014761236a565b611e39612380565b3b1561226057608435908115612208577003fffffffffffffffffffffffffffffffc82116121ab5760ff19166001175f55602460206001600160a01b03611e7e612380565b16604051928380926370a0823160e01b82523360048301525afa908115610612575f91612179575b506001600160a01b03611eb761236a565b1682805b6120e057506020611eca612380565b60405163627160f360e11b81526001600160a01b0390911660048201526024810185905233604482015291829060649082905f905af180156106125783915f916120ab575b500361204757602460206001600160a01b03611f29612380565b16604051928380926370a0823160e01b82523360048301525afa9081156106125783905f92612011575b50611f5e919261252d565b03611fb0576001600160a01b03611f73612380565b169060405190815260a435907f158110513241d1756a66549dae81bce1d1cbc3db022d0fe8a62af9206771abb160203392a45f805460ff19169055005b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c65207265706c6163656d656e742063726564697420726560448201527218d95a5c1d081dd85cc81b9bdd08195e1858dd606a1b6064820152608490fd5b9150506020813d60201161203f575b8161202d602093836124cb565b81010312610606575182611f5e611f53565b3d9150612020565b60405162461bcd60e51b815260206004820152603660248201527f4f70656e4f7261636c65207265706c6163656d656e74206372656469742077696044820152751d1a191c985dd85b081dd85cc81b9bdd08195e1858dd60521b6064820152608490fd5b9150506020813d6020116120d8575b816120c7602093836124cb565b810103126106065782905184611f0f565b3d91506120ba565b6001600160801b03811115612169576001600160801b03905b612101612380565b91833b15610606575f8161212c946040519586928392637d656cf760e01b84523033600486016124ed565b038183885af190811561061257612153936001600160801b0392612159575b501690612520565b80611ebb565b5f612163916124cb565b8761214b565b6001600160801b038116906120f9565b90506020813d6020116121a3575b81612194602093836124cb565b81010312610606575182611ea6565b3d9150612187565b60405162461bcd60e51b815260206004820152602f60248201527f5265706c6163656d656e742063726564697420616d6f756e742065786365656460448201526e73207265706f727420626f756e647360881b6064820152608490fd5b60405162461bcd60e51b815260206004820152602a60248201527f5265706c6163656d656e742063726564697420616d6f756e74206d75737420626044820152696520706f73697469766560b01b6064820152608490fd5b60405162461bcd60e51b815260206004820152603360248201527f5265706c6163656d656e742063726564697420746f6b656e206d75737420636f6044820152726e7461696e20636f6e747261637420636f646560681b6064820152608490fd5b35906001600160801b038216820361060657565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b35906001600160a01b038216820361060657565b1561231457565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c6520617262697472616765206578656375746f72207265604482015267656e7472616e637960c01b6064820152608490fd5b6004356001600160a01b03811681036106065790565b6024356001600160a01b03811681036106065790565b610144356001600160a01b03811681036106065790565b610204356001600160a01b03811681036106065790565b610104356001600160a01b03811681036106065790565b6101c4356001600160a01b03811681036106065790565b610284356001600160a01b03811681036106065790565b1561241057565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c652061646472657373206d75737420636f6e7461696e2060448201526c636f6e747261637420636f646560981b6064820152608490fd5b8181029291811591840414171561247e57565b634e487b7160e01b5f52601160045260245ffd5b60a0810190811067ffffffffffffffff8211176106ea57604052565b610100810190811067ffffffffffffffff8211176106ea57604052565b90601f8019910116810190811067ffffffffffffffff8211176106ea57604052565b6001600160a01b039182168152918116602083015290911660408201526001600160801b03909116606082015260800190565b9190820391821161247e57565b9190820180921161247e57565b904315158061260e575b156125bd5780151591826125b2575b50501561255c57565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2063616e6f6e6963616c20706172656e7420626c6f636b6044820152670818da185b99d95960c21b6064820152608490fd5b401490505f80612553565b60405162461bcd60e51b8152602060048201526024808201527f457865637574696f6e206d7573742074617267657420746865206e65787420626044820152636c6f636b60e01b6064820152608490fd5b505f19430143811161247e578214612544565b67ffffffffffffffff81116106ea57601f01601f191660200190565b359062ffffff8216820361060657565b600f0b6f7fffffffffffffffffffffffffffffff19811461247e575f0390565b906060905f905f5460ff811680612ef9575b15612ea45761268d82612621565b61269a60405191826124cb565b828152602081019083870193368511610606576020815f928a86378301015251902060015403612e4e57610100600160a81b0319165f90815560015560a09084900312610606576040516126ed81612492565b6126f6846122f9565b908181526127066020860161263d565b918260208301526040860135908115158203610606576040830191825262ffffff868401948789013586526080808601990135895216906127468261344d565b9060405161275381612492565b5f8152602081019160018060a01b03168252604081019384528881019260020b835260808101925f84528551151590815f14612e3a5788515b875115612e1f576401000276a4905b6040519c8d01938d851067ffffffffffffffff8611176106ea57612864946040528d5260208d0190815260408d019160018060a01b0316825260209c8d97604051946127e78a876124cb565b5f8652604051633cf3645360e21b815297516001600160a01b0390811660048a0152985189166024890152995162ffffff166044880152985160020b60648701529751861660848601529651151560a4850152955160c4840152945190921660e482015261012061010482015292839182916101248301906122d5565b03815f335af1908115610612575f91612df2575b508060801d916001600160801b0383600f0b9216600f0b9051612b72576128a28551600f0b61264d565b600f0b03612b2157841215612ad1576001600160801b031694518510612a755780516001600160a01b0316333b15612a715760405190632961046560e21b82526004820152838160248183335af18015612a6657908491612a4d575b50505181516129179133906001600160a01b03166134d0565b604051630476982d60e21b815290838260048186335af1918215612a42578392612a13575b5051036129bc57333b156129ae57604051630b0d9c0960e01b81526004810182905230602482015260448101849052818160648183335af180156129b157612999575b5050604051918183015281526129966040826124cb565b90565b6129a48280926124cb565b6129ae578061297f565b80fd5b6040513d84823e3d90fd5b60405162461bcd60e51b815260048101839052602960248201527f556e697377617020563420746f6b656e20736574746c656d656e7420776173206044820152681b9bdd08195e1858dd60ba1b6064820152608490fd5b9091508381813d8311612a3b575b612a2b81836124cb565b810103126106065751905f61293c565b503d612a21565b6040513d85823e3d90fd5b81612a57916124cb565b612a6257825f6128fe565b8280fd5b6040513d86823e3d90fd5b8380fd5b60405162461bcd60e51b815260048101859052602e60248201527f556e69737761702056342073656c6c206865646765207265636569766564207460448201526d0dede40d8d2e8e8d8ca40ae8aa8960931b6064820152608490fd5b60405162461bcd60e51b815260048101869052602260248201527f556e69737761702056342073656c6c206f75747075742077617320696e76616c6044820152611a5960f21b6064820152608490fd5b60405162461bcd60e51b815260048101879052602360248201527f556e69737761702056342073656c6c20696e70757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b9091508351600f0b03612da1575f811215612d5e57612b986001600160801b039161264d565b1694518511612d0657333b1561060657604051632961046560e21b81525f600482018190528160248183335af1801561061257612cf1575b50604051630476982d60e21b8152848160048189335af1908115612a66579086918591612cc0575b5003612c6857519051906001600160a01b0316333b15612a6257604051630b0d9c0960e01b81526001600160a01b039190911660048201523060248201526044810191909152818160648183335af180156129b157612999575050604051918183015281526129966040826124cb565b60405162461bcd60e51b815260048101859052602a60248201527f556e6973776170205634206e617469766520736574746c656d656e7420776173604482015269081b9bdd08195e1858dd60b21b6064820152608490fd5b809250868092503d8311612cea575b612cd981836124cb565b81010312610606578590515f612bf8565b503d612ccf565b612cfe9193505f906124cb565b5f915f612bd0565b60405162461bcd60e51b815260048101859052602a60248201527f556e697377617020563420627579206865646765206578636565646564206d616044820152690f0d2daeada40ae8aa8960b31b6064820152608490fd5b6064856040519062461bcd60e51b825280600483015260248201527f556e69737761702056342062757920696e7075742077617320696e76616c69646044820152fd5b60405162461bcd60e51b815260048101869052602360248201527f556e697377617020563420627579206f757470757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b90508581813d8311612e18575b612e0981836124cb565b8101031261060657515f612878565b503d612dff565b73fffd8963efd1fc6a506488495d951d5263988d259061279b565b8851600160ff1b811461247e575f0361278c565b60405162461bcd60e51b815260206004820152602860248201527f556e617574686f72697a656420556e69737761702056342063616c6c6261636b604482015267081c185e5b1bd85960c21b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f556e617574686f72697a656420556e697377617020563420756e6c6f636b2063604482015266616c6c6261636b60c81b6064820152608490fd5b5033600882901c6001600160a01b03161461267f565b15612f1657565b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220726560448201527271756972657320455243323020746f6b656e7360681b6064820152608490fd5b15612f7e57565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220746f60448201526f35b2b7399036bab9ba103234b33332b960811b6064820152608490fd5b6064356001600160801b03811681036106065790565b6084356001600160801b03811681036106065790565b359065ffffffffffff8216820361060657565b6001600160801b0361302c826122c1565b1682526001600160801b03613043602083016122c1565b1660208301526001600160a01b0361305d604083016122f9565b16604083015265ffffffffffff61307660608301613008565b16606083015265ffffffffffff61308f60808301613008565b1660808301526001600160a01b036130a960a083016122f9565b1660a083015265ffffffffffff6130c260c08301613008565b1660c083015265ffffffffffff6130db60e08301613008565b1660e08301526001600160801b036130f661010083016122c1565b166101008301526001600160a01b0361311261012083016122f9565b166101208301526101408101356bffffffffffffffffffffffff8116809103610606576101408301526001600160a01b0361315061016083016122f9565b1661016083015262ffffff613168610180830161263d565b1661018083015262ffffff6131806101a0830161263d565b166101a083015262ffffff6131986101c0830161263d565b166101c08301526101e081013561ffff8116809103610606576101e08301526001600160a01b036131cc61020083016122f9565b166102008301526102208101359063ffffffff8216809203610606576102609161022084015262ffffff613203610240830161263d565b1661024084015201359060ff8216809203610606576102600152565b8035825260609081906001600160a01b0361323c602083016122f9565b166020850152604081013560408501520135910152565b1561325a57565b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220646f60448201527f6573206e6f7420737570706f72742073656c662d6469737075746573000000006064820152608490fd5b156132cc57565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e31207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561332457565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e32207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561337c57565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e31207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b156133d857565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e32207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b906001600160801b03809116911603906001600160801b03821161247e57565b62ffffff16606481146134ca576101f481146134c457610bb881146134be57612710146134b95760405162461bcd60e51b815260206004820152601f60248201527f556e737570706f7274656420556e697377617020563420706f6f6c20666565006044820152606490fd5b60c890565b50603c90565b50600a90565b50600190565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526135189161351382606481015b03601f1981018452836124cb565b614920565b565b90801561363f576040516323b872dd60e01b60208281019190915233602483810191909152306044840152606483018490529390916135709061356a81608481015b03601f1981018352826124cb565b82614920565b6040516370a0823160e01b815230600482015293849182906001600160a01b03165afa918215610612575f92613609575b506135ac919261252d565b036135b357565b60405162461bcd60e51b815260206004820152602860248201527f546f6b656e207472616e7366657220746f206578656375746f7220776173206e6044820152671bdd08195e1858dd60c21b6064820152608490fd5b91506020823d602011613637575b81613624602093836124cb565b81010312610606576135ac9151916135a1565b3d9150613617565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529192916020908290604490829087165afa908115610612575f916136c6575b5061368f575050565b60405163095ea7b360e01b60208201526001600160a01b0390911660248201525f6044820152613518916135138260648101613505565b90506020813d6020116136f0575b816136e1602093836124cb565b8101031261060657515f613686565b3d91506136d4565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529293926020908290604490829086165afa908115610612575f916137bf575b50613784575b8161374a57505050565b60405163095ea7b360e01b60208201526001600160a01b039093166024840152604483019190915261351891906135138260648101613505565b60405163095ea7b360e01b60208201526001600160a01b03841660248201525f60448201526137ba9061356a816064810161355c565b613740565b90506020813d6020116137e9575b816137da602093836124cb565b8101031261060657515f61373a565b3d91506137cd565b604051906137fe82612492565b5f6080838281528260208201528260408201528260608201520152565b6138236137f1565b90608435906001600160801b038216918281036106065760a435916001600160801b0383169384840361060657610124356001600160801b0381168103610606576138776001600160801b0382168761246b565b9461014435956001600160801b0387168703610606576138a0846001600160801b03891661246b565b1015614820576001600160801b0382168203610606576001600160801b038216831115614812576001600160801b0382168203610606576001600160801b036138e9838761342d565b16916001600160801b03871687036106065761390e6001600160801b0388168961252d565b6001600160801b0388168803610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846139509550166001600160801b038c1661246b565b6001600160801b038816880361060657610364359062ffffff821682036106065762989680610a3e62ffffff846139929550166001600160801b038c1661246b565b915b60208a019360408b019384528452505f976001600160801b03821690818303610606576139c1925061246b565b6001600160801b03871695868814159485610606576139e191508761246b565b108089526141245750506139f36137f1565b506101c4356001600160a01b03811680821415949193918561412057610284356001600160a01b03811697888214159591949186613d9c57613d945790613a69915060608b01928352613a4d60208c01518251908661351a565b6020613a5f60408d015185519061252d565b910151908961351a565b6044359460ff861694858703613d9457600286149788156140fa5750505f5f94835160643562ffffff8116810361060657613aa38161344d565b5060016001607f1b038211614099574791602435906001600160a01b0382168203610606578e54610100600160a81b031916600883901b610100600160a81b0316178f5562ffffff83168303610606578e8e916080604051613b0481612492565b84815262ffffff60208201971687526040810193845260608101928352019160c435835262ffffff60405196602088019586525116604087015251151560608601525160808501525160a084015260a08352613b6160c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918e918391829084908290613ba59060248301906122d5565b03926001600160a01b03165af1908115613f56578d91614017575b508c5460081c6001600160a01b0316158061400d575b15613fbc5760208151918180820193849201010312610606575190613bfc82479261252d565b03613f615780613f0b575b60808d01525b5f9680613da457506024356001600160a01b0381169690878103613da05750613d9c5750613d9857506064359062ffffff821691828103613d94575051604051602093613cac936101049391613c62846124ae565b8b845286840152604083015230606083015260e435608083015260a082015260c43560c08201528960e082015289604051958694859363414bf38960e01b85526004850190614c11565b5af1908115613d89578691613d57575b5060808701525b608086015160c43511613cfe57612a71575015613cdf57505090565b602435916001600160a01b03831683036129ae57509061299691613644565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761702073656c6c20686564676520726563656976656420746f6f2060448201526a0d8d2e8e8d8ca40ae8aa8960ab1b6064820152608490fd5b90506020813d602011613d81575b81613d72602093836124cb565b8101031261060657515f613cbc565b3d9150613d65565b6040513d88823e3d90fd5b8980fd5b8880fd5b8a80fd5b8c80fd5b5f975093959493600103613ef55760405194613dc16060876124cb565b600286526040366020880137613d9c575088613ddc85614af2565b52613d985750613deb82614b13565b526024356001600160a01b03811690818103613d9857918891613e339493508351836040518097819582946338ed173960e01b845260e43591309160c4359060048701614ba9565b03925af1918215613eea578792613ec6575b5060028251149081613eb1575b5015613e6c57613e6190614b13565b516080870152613cc3565b60405162461bcd60e51b815260206004820152601f60248201527f556e69737761702056322073656c6c20616d6f756e747320696e76616c6964006044820152606490fd5b9050613ebc82614af2565b519051145f613e52565b613ee39192503d8089833e613edb81836124cb565b810190614b23565b905f613e45565b6040513d89823e3d90fd5b505050505050505f613f0683614aa6565b613cc3565b853b15613f5257604051630d0e30db60e41b81528c81600481858b5af18015613f5657908d91613f3d575b5050613c07565b81613f47916124cb565b613f52578b5f613f36565b8b80fd5b6040513d8f823e3d90fd5b60405162461bcd60e51b815260206004820152602d60248201527f556e6973776170205634206e61746976652062616c616e63652064656c74612060448201526c1dd85cc81b9bdd08195e1858dd609a1b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f556e69737761702056342063616c6c6261636b20776173206e6f7420636f6e736044820152631d5b595960e21b6064820152608490fd5b5060015415613bd6565b90503d808e833e61402881836124cb565b8101906020818303126140955780519067ffffffffffffffff8211614091570181601f820112156140955780519061405f82612621565b9261406d60405194856124cb565b8284526020838301011161409157818f9260208093018386015e830101525f613bc0565b8e80fd5b8d80fd5b60405162461bcd60e51b815260206004820152603360248201527f556e697377617020563420686564676520616d6f756e742065786365656473206044820152727369676e65642064656c746120626f756e647360681b6064820152608490fd5b90946024356001600160a01b0381168103613f5257845161411b918c6136f8565b613c0d565b8780fd5b909291506141339493946137f1565b506101c4356001600160a01b0381169586821415938461060657610284359560018060a01b03871692838814159889610606576102e43562ffffff81168082036106065761418a629896809161419193508561246b565b048361252d565b9050610364359162ffffff831692838103610606576141b9936298968092610a3e925061246b565b9160608c01928084528651106147b45760206141f66142009351976141ed8d6141e560c435809c61252d565b86519161351a565b51855190612520565b910151908461351a565b6044359560ff871695868803610606576002871498891561478e5750505f935f9083516064359062ffffff821682036106065761423c8261344d565b5060016001607f1b0381116140995747918d3b15610606575f808f60248d6040519485938492632e1a7d4d60e01b845260048401525af1801561061257614779575b50602435906001600160a01b0382168203610606578f8054610100600160a81b038460081b1690610100600160a81b03191617905562ffffff8116810361060657604051926142cc84612492565b89845262ffffff602085019216825260806040850194600186526060810192835201908c82526040519462ffffff60208701948d86525116604087015251151560608601525160808501525160a084015260a0835261432c60c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918f9183918290849082906143709060248301906122d5565b03926001600160a01b03165af19081156146e4578e916146fb575b508d5460081c6001600160a01b031615806146f1575b15613fbc57602081519181808201938492010103126106065751906143c6828a612520565b906143d282479261252d565b03613f61578e81614697575b6080915001525b5f978061453957506024356001600160a01b03811696908781036140955750613f525750613d9457506064359062ffffff821691828103613d9c57505160405160209361448293610104939161443a846124ae565b8c845286840152604083015230606083015260e435608083015260a08201528560c08201528a60e08201528a6040519586948593631b67c43360e31b85526004850190614c11565b5af1908115613eea578791614507575b5060808801525b6080870151116144b257612a71575015613cdf57505090565b60405162461bcd60e51b815260206004820152602760248201527f556e697377617020627579206865646765206578636565646564206d6178696d6044820152660eada40ae8aa8960cb1b6064820152608490fd5b90506020813d602011614531575b81614522602093836124cb565b8101031261060657515f614492565b3d9150614515565b5f98509395949360010361467f57604051946145566060876124cb565b600286526040366020880137613f5257508961457185614af2565b52613d94575061458082614b13565b526024356001600160a01b03811690818103613d94579189916145c5949350835183604051809781958294634401edf760e11b845260e435918c309260048701614ba9565b03925af1918215614674578892614658575b5060028251149081614643575b50156145fe576145f390614af2565b516080880152614499565b60405162461bcd60e51b815260206004820152601e60248201527f556e69737761702056322062757920616d6f756e747320696e76616c696400006044820152606490fd5b905061464e82614b13565b519051145f6145e4565b61466d9192503d808a833e613edb81836124cb565b905f6145d7565b6040513d8a823e3d90fd5b50505050505090505f9061469284614aa6565b614499565b508c3b15614095578d8d600460405180948193630d0e30db60e41b83525af180156146e4578f90918f926146cc575b506143de565b6146d8915082906124cb565b613da0578c8e5f6146c6565b8e604051903d90823e3d90fd5b50600154156143a1565b90503d808f833e61470c81836124cb565b8101906020818303126140915780519067ffffffffffffffff8211614775570181601f820112156140915780519061474382612621565b9261475160405194856124cb565b82845260208383010111614775578f918060208093018386015e830101525f61438b565b8f80fd5b614786919f505f906124cb565b5f9d5f61427e565b90946024356001600160a01b0381168103610606576147af9088908d6136f8565b6143e5565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520627579206865646765206578636565647320746f6b60448201526f32b7191031b7b73a3934b13aba34b7b760811b6064820152608490fd5b6001600160801b035f6138e9565b6001600160801b0382168203610606576148436001600160801b0383168461252d565b6001600160801b0383168303610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846148859550166001600160801b03871661246b565b6001600160801b038316830361060657610364359062ffffff821682036106065762989680610a3e62ffffff846148c79550166001600160801b03871661246b565b916001600160801b0387168703610606576001600160801b038716881115614912576001600160801b0387168703610606576001600160801b0361490b888461342d565b1691613994565b6001600160801b035f61490b565b6001600160a01b0316803b15614a4357815f92918360208194519301915af13d15614a3b573d9061495082612621565b9161495e60405193846124cb565b82523d5f602084013e5b156149f757805180614978575050565b816020918101031261060657602001518015908115036106065761499857565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090614968565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b15614aad57565b60405162461bcd60e51b815260206004820152601760248201527f556e737570706f727465642068656467652076656e75650000000000000000006044820152606490fd5b805115614aff5760200190565b634e487b7160e01b5f52603260045260245ffd5b805160011015614aff5760400190565b6020818303126106065780519067ffffffffffffffff821161060657019080601f830112156106065781519167ffffffffffffffff83116106ea578260051b906020820193614b7560405195866124cb565b845260208085019282010192831161060657602001905b828210614b995750505090565b8151815260209182019101614b8c565b92919594939560a08401918452602084015260a060408401528151809152602060c084019201905f5b818110614bf2575050506001600160a01b03909416606082015260800152565b82516001600160a01b0316845260209384019390920191600101614bd2565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff16908401526060808301518216908401526080808301519084015260a0808301519084015260c0808301519084015260e0918201511691015256fea26469706673582212201c45d9ea582d95758797a1cf6f2b0692a3340fa87cb0e4df152b08fde09cadfb64736f6c63430008230033', + '60806040526004361015610087575b3615610018575f80fd5b60ff5f54161561002457005b60405162461bcd60e51b815260206004820152603560248201527f4f70656e4f7261636c6520617262697472616765206578656375746f722072656044820152740d4cac6e8e640eadce6ded8d2c6d2e8cac8408aa89605b1b6064820152608490fd5b5f3560e01c806329d76f5f14611df05780633b0b89cc14611dcf5780637f8d61f414611c1357806391dd734614611b9c578063a7b06edf1461102b578063cc5eed78146108a25763f6a2b58e0361000e57346106065736600319016104a0811261060657610120136106065761028036610123190112610606576080366103a319011261060657608036610423190112610606575f5461012a60ff82161561230d565b610424359061013c610104358361253a565b61014f61014761236a565b3b1515612409565b610157612380565b3b15610843576001600160a01b0361016d6123db565b16151580610825575b61017f90612f0f565b6101ae61018a6123db565b6001600160a01b0361019a6123f2565b6001600160a01b0390921691161415612f77565b610164356001600160a01b038116808203610606576101d09150331415613253565b60e43542116107cc5760ff19166001175f556001600160a01b036101f26123db565b166001600160a01b036102036123f2565b16906040516370a0823160e01b8152306004820152602081602481855afa908115610612575f9161079a575b506040516370a0823160e01b815230600482015292602084602481845afa938415610612575f94610766575b5061026461236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292602084602481845afa938415610612575f94610732575b506102a261236a565b6040516370a0823160e01b81526001600160a01b03909116600482015290602082602481865afa918215610612575f926106fe575b50604051936080850185811067ffffffffffffffff8211176106ea5760405284526020840195865260408401948552606084019182526103168461381b565b9461031f61236a565b966103316020880198895190856136f8565b61033961236a565b9561034b6040890197885190886136f8565b6001600160a01b0361035b61236a565b166103a4359a610369612ff2565b9160a435906001600160801b038216918281036106065750803b15610606575f928e926001600160801b03604051968795638c64ed9560e01b8752600487015216602485015260448401523360648401528360848401528360a48401526103d560c4840161012461301b565b6103e561034484016103a461321f565b6103c483015261044480356103e4840152610464356104048401526104843561042484015290829084905af18015610612576106da575b5061042e61042861236a565b85613644565b61043f61043961236a565b87613644565b8751156106d057610456608089015160c435612520565b806106bf575b506040516370a0823160e01b815230600482015290602082602481885afa908115610612575f91610689575b610494925051146132c5565b6040516370a0823160e01b815230600482015290602082602481895afa908115610612575f91610653575b6104cb9250511461331d565b60206104d561236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f9261061d575b509061051c610522925188519061252d565b14613375565b602061052c61236a565b6040516370a0823160e01b81526001600160a01b03909116600482015292839060249082905afa918215610612575f926105d8575b5090610573610579925184519061252d565b146133d1565b815115159260806060840151930151905191519260405194855260208501526040840152606083015260808201527fb87b87bb3eacf9e5cf9ed834f7142300b841a66f3e9695eb6d3f6e7e89257ea560a03392a35f805460ff19169055005b91506020823d60201161060a575b816105f3602093836124cb565b8101031261060657905190610573610561565b5f80fd5b3d91506105e6565b6040513d5f823e3d90fd5b91506020823d60201161064b575b81610638602093836124cb565b810103126106065790519061051c61050a565b3d915061062b565b90506020823d602011610681575b8161066e602093836124cb565b81010312610606576104cb9151906104bf565b3d9150610661565b90506020823d6020116106b7575b816106a4602093836124cb565b8101031261060657610494915190610488565b3d9150610697565b6106ca9033866134d0565b5f61045c565b6080880151610456565b5f6106e4916124cb565b5f61041c565b634e487b7160e01b5f52604160045260245ffd5b9091506020813d60201161072a575b8161071a602093836124cb565b810103126106065751905f6102d7565b3d915061070d565b9093506020813d60201161075e575b8161074e602093836124cb565b810103126106065751925f610299565b3d9150610741565b9093506020813d602011610792575b81610782602093836124cb565b810103126106065751925f61025b565b3d9150610775565b90506020813d6020116107c4575b816107b5602093836124cb565b8101031261060657515f61022f565b3d91506107a8565b60405162461bcd60e51b815260206004820152602b60248201527f4f70656e4f7261636c652061726269747261676520686564676520646561646c60448201526a1a5b9948195e1c1a5c995960aa1b6064820152608490fd5b5061017f6001600160a01b036108396123f2565b1615159050610176565b60405162461bcd60e51b815260206004820152603160248201527f556e697377617020726f757465722061646472657373206d75737420636f6e7460448201527061696e20636f6e747261637420636f646560781b6064820152608490fd5b34610606576103e0366003190112610606576004356001600160a01b038116808203610606576024356001600160801b0381169283820361060657604435906001600160801b03821680830361060657610280366063190112610606576080366102e319011261060657608036610363190112610606575f5461092860ff82161561230d565b610934833b1515612409565b6001600160a01b036109446123c4565b1615158061100d575b61095690612f0f565b6109716109616123c4565b6001600160a01b0361019a6123db565b60a4356001600160a01b0381169190828103610606576001926109979150331415613253565b60ff1916175f55606435926001600160801b03841693848114159586610606576109c1868561246b565b95608435976001600160801b03891694858a14159889610606576109e58d8861246b565b1015610f7c575061060657891115610f6c576001600160801b0391610a099161342d565b169361060657610a19818361252d565b610224359062ffffff821680830361060657610a3e6298968091610a4594508561246b565b049061252d565b6102a435955062ffffff86169182870361060657610a3e610a6e9362989680926024995061246b565b955b60206001600160a01b03610a826123c4565b16604051968780926370a0823160e01b82523060048301525afa8015610612575f90610f39575b6024955060206001600160a01b03610abf6123db565b16604051978880926370a0823160e01b82523060048301525afa938415610612575f94610f04575b6024965060206001600160a01b03610afd6123c4565b16604051988980926370a0823160e01b82528c60048301525afa938415610612575f94610ecf575b6024975060206001600160a01b03610b3b6123db565b16604051998a80926370a0823160e01b82528d60048301525afa978815610612575f98610e9b575b50610b7f83886001600160a01b03610b796123c4565b1661351a565b610b94868b6001600160a01b03610b796123db565b610baf87836001600160a01b03610ba96123c4565b166136f8565b610bc48a836001600160a01b03610ba96123db565b883b156106065760405193638c64ed9560e01b85526102e4356004860152602485015260448401523360648401525f60848401525f60a4840152610c0c60c48401606461301b565b610c1c61034484016102e461321f565b610364356103c484810191909152610384356103e48501526103a435610404850152356104248401525f8361044481838c5af190811561061257602493610c8d92610e8b575b50610c7d816001600160a01b03610c776123c4565b16613644565b6001600160a01b03610c776123db565b60206001600160a01b03610c9f6123c4565b16604051938480926370a0823160e01b82523060048301525afa8015610612575f90610e57575b610cd19250146132c5565b602460206001600160a01b03610ce56123db565b16604051928380926370a0823160e01b82523060048301525afa908115610612575f91610e24575b50602492610d1b911461331d565b60206001600160a01b03610d2d6123c4565b16604051938480926370a0823160e01b82528960048301525afa918215610612575f92610dee575b50610d639261051c9161252d565b60206001600160a01b03610d756123db565b16926024604051809581936370a0823160e01b835260048301525afa918215610612575f92610db8575b50610dad926105739161252d565b5f805460ff19169055005b9091506020813d602011610de6575b81610dd4602093836124cb565b81010312610606575190610dad610d9f565b3d9150610dc7565b9091506020813d602011610e1c575b81610e0a602093836124cb565b81010312610606575190610d63610d55565b3d9150610dfd565b90506020813d602011610e4f575b81610e3f602093836124cb565b8101031261060657516024610d0d565b3d9150610e32565b506020823d602011610e83575b81610e71602093836124cb565b8101031261060657610cd19151610cc6565b3d9150610e64565b5f610e95916124cb565b8a610c62565b9097506020813d602011610ec7575b81610eb7602093836124cb565b810103126106065751968a610b63565b3d9150610eaa565b93506020873d602011610efc575b81610eea602093836124cb565b81010312610606576024965193610b25565b3d9150610edd565b93506020863d602011610f31575b81610f1f602093836124cb565b81010312610606576024955193610ae7565b3d9150610f12565b506020853d602011610f64575b81610f53602093836124cb565b810103126106065760249451610aa9565b3d9150610f46565b50506001600160801b035f610a09565b9398949750509050610f8e818a61252d565b610224359062ffffff821680830361060657610a3e6298968091610fb394508561246b565b6102a435975062ffffff88169182890361060657610a3e610fdc93629896809260249b5061246b565b94831115610ffd576001600160801b0391610ff69161342d565b1695610a70565b50506001600160801b035f610ff6565b506109566001600160a01b036110216123db565b161515905061094d565b346106065736600319016103a081126106065760a013610606576102803660a319011261060657608036610323190112610606575f5461106e60ff82161561230d565b61107c60443560243561253a565b61108761014761236a565b6001600160a01b03611097612396565b16151580611b7e575b6110a990612f0f565b6110c46110b4612396565b6001600160a01b0361019a6123ad565b6001600160801b036110d4612fdc565b16151580611b65575b15611b0a5760ff19166001175f556001600160a01b036110fb61236a565b5f911660e4356001600160a01b038116908181036106065733821480611aef575b61187e575b506024905060206001600160a01b03611138612396565b16604051928380926370a0823160e01b82523360048301525afa8015610612575f9061184b575b6024915060206001600160a01b036111756123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611817575b506111a6612396565b6111ae612fdc565b90843b1561060657604051637d656cf760e01b8152915f91839182916111da91903033600486016124ed565b038183885af1801561061257611807575b506111f46123ad565b6111fc612ff2565b90843b1561060657604051637d656cf760e01b8152915f918391829161122891903033600486016124ed565b038183885af18015610612576117f7575b5061128c6020611247612396565b61124f612fdc565b60405163627160f360e11b81526001600160a01b0390921660048301526001600160801b0316602482015233604482015291829081906064820190565b03815f885af1908115610612575f916117c5575b506001600160801b036112b1612fdc565b1603611763576112cc60206112c46123ad565b61124f612ff2565b03815f885af1908115610612575f91611731575b506001600160801b036112f1612ff2565b16036116cf5760249060206001600160a01b0361130c612396565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611699575b50611350906001600160801b03611349612fdc565b169061252d565b0361163a5760249060206001600160a01b0361136a6123ad565b16604051938480926370a0823160e01b82523360048301525afa918215610612575f92611604575b506113a7906001600160801b03611349612ff2565b036115a5578161143c575b506113bb612396565b6113c3612fdc565b916001600160801b036113d46123ad565b6113dc612ff2565b90826040519616865260018060a01b03166020860152166040840152606083015260018060a01b03169061032435907f8e60feda1ea0461bfc923501c0e2478e40a145bb3c95a75eb987a1fd4afc85e260803392a45f805460ff19169055005b60405163627160f360e11b81525f600482018190526024820184905233604483018190523192602091839160649183915af180156106125783915f91611570575b50036115055761148f8233319261252d565b0361149a57816113b2565b60405162461bcd60e51b815260206004820152603960248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207265636569707420776173206e6f74206578616374000000000000006064820152608490fd5b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c65206c6966656379636c6520736574746c65722072657760448201527f617264207769746864726177616c20776173206e6f74206578616374000000006064820152608490fd5b9150506020813d60201161159d575b8161158c602093836124cb565b81010312610606578290518461147d565b3d915061157f565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3220726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d602011611632575b81611620602093836124cb565b810103126106065751906113a7611392565b3d9150611613565b60405162461bcd60e51b815260206004820152603160248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e3120726563656044820152701a5c1d081dd85cc81b9bdd08195e1858dd607a1b6064820152608490fd5b9091506020813d6020116116c7575b816116b5602093836124cb565b81010312610606575190611350611334565b3d91506116a8565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e322077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d60201161175b575b8161174c602093836124cb565b810103126106065751856112e0565b3d915061173f565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c65206c6966656379636c6520746f6b656e312077697468604482015273191c985dd85b081dd85cc81b9bdd08195e1858dd60621b6064820152608490fd5b90506020813d6020116117ef575b816117e0602093836124cb565b810103126106065751856112a0565b3d91506117d3565b5f611801916124cb565b84611239565b5f611811916124cb565b846111eb565b9091506020813d602011611843575b81611833602093836124cb565b810103126106065751908461119d565b3d9150611826565b506020813d602011611876575b81611865602093836124cb565b81010312610606576024905161115f565b3d9150611858565b909192506103243591833b15610606576040519163cad5e7a960e01b835283600484015260a4356001600160801b03811680910361060657602484015260c4356001600160801b0381168091036106065760448401525060648201526101043565ffffffffffff81168091036106065760848201526101243565ffffffffffff81168091036106065760a4820152610144356001600160a01b038116908190036106065760c48201526101643565ffffffffffff81168091036106065760e48201526101843565ffffffffffff8116809103610606576101048201526101a4356001600160801b038116809103610606576101248201526101c4356001600160a01b03811690819003610606576101448201526101e435916bffffffffffffffffffffffff8316809303610606576101648201839052610204356001600160a01b03811690819003610606576101848301526102243562ffffff8116809103610606576101a48301526102443562ffffff8116809103610606576101c48301526102643562ffffff8116809103610606576101e48301526102843561ffff8116809103610606576102048301526102a4356001600160a01b03811690819003610606576102248301526102c43563ffffffff8116809103610606576102448301526102e43562ffffff8116809103610606576102648301526103043560ff8116809103610606576102848301526102a4820152610344356001600160a01b03811690819003610606576102c4820152610364356102e4820152610384356103048201525f816103248183875af1801561061257611adf575b50908280611121565b5f611ae9916124cb565b82611ad6565b506101243565ffffffffffff8116809103610606571561111c565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c65206c6966656379636c6520616d6f756e7473206d757360448201526c7420626520706f73697469766560981b6064820152608490fd5b506001600160801b03611b76612ff2565b1615156110dd565b506110a96001600160a01b03611b926123ad565b16151590506110a0565b346106065760203660031901126106065760043567ffffffffffffffff811161060657366023820112156106065780600401359067ffffffffffffffff821161060657366024838301011161060657611c0f916024611bfb920161266d565b6040519182916020835260208301906122d5565b0390f35b346106065736600319016102c08112610606576102801361060657610284356001600160801b038116808203610606576102a4356001600160801b038116808203610606576004356001600160801b0381169485821415958661060657611c7a818561246b565b95602435976001600160801b03891696878a1415988961060657611c9e848a61246b565b1015611d3b5750610606571115611d2b576001600160801b0391611cc19161342d565b16926106065781611cd19161252d565b6101c4359062ffffff821680830361060657610a3e6298968091611cf694508561246b565b61024435935062ffffff84169182850361060657610a3e611d1f9362989680926040975061246b565b82519182526020820152f35b50506001600160801b035f611cc1565b9597505081925090611d52915f989694985061252d565b6101c4359062ffffff821680830361060657610a3e6298968091611d7794508561246b565b61024435965062ffffff87169182880361060657610a3e611da093629896809260409a5061246b565b931115611dbf576001600160801b0391611db99161342d565b16611d1f565b50506001600160801b035f611db9565b3461060657604036600319011261060657611dee60243560043561253a565b005b3461060657366003190160c081126106065760a013610606575f54611e1860ff82161561230d565b611e2660643560443561253a565b611e3161014761236a565b611e39612380565b3b1561226057608435908115612208577003fffffffffffffffffffffffffffffffc82116121ab5760ff19166001175f55602460206001600160a01b03611e7e612380565b16604051928380926370a0823160e01b82523360048301525afa908115610612575f91612179575b506001600160a01b03611eb761236a565b1682805b6120e057506020611eca612380565b60405163627160f360e11b81526001600160a01b0390911660048201526024810185905233604482015291829060649082905f905af180156106125783915f916120ab575b500361204757602460206001600160a01b03611f29612380565b16604051928380926370a0823160e01b82523360048301525afa9081156106125783905f92612011575b50611f5e919261252d565b03611fb0576001600160a01b03611f73612380565b169060405190815260a435907f158110513241d1756a66549dae81bce1d1cbc3db022d0fe8a62af9206771abb160203392a45f805460ff19169055005b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c65207265706c6163656d656e742063726564697420726560448201527218d95a5c1d081dd85cc81b9bdd08195e1858dd606a1b6064820152608490fd5b9150506020813d60201161203f575b8161202d602093836124cb565b81010312610606575182611f5e611f53565b3d9150612020565b60405162461bcd60e51b815260206004820152603660248201527f4f70656e4f7261636c65207265706c6163656d656e74206372656469742077696044820152751d1a191c985dd85b081dd85cc81b9bdd08195e1858dd60521b6064820152608490fd5b9150506020813d6020116120d8575b816120c7602093836124cb565b810103126106065782905184611f0f565b3d91506120ba565b6001600160801b03811115612169576001600160801b03905b612101612380565b91833b15610606575f8161212c946040519586928392637d656cf760e01b84523033600486016124ed565b038183885af190811561061257612153936001600160801b0392612159575b501690612520565b80611ebb565b5f612163916124cb565b8761214b565b6001600160801b038116906120f9565b90506020813d6020116121a3575b81612194602093836124cb565b81010312610606575182611ea6565b3d9150612187565b60405162461bcd60e51b815260206004820152602f60248201527f5265706c6163656d656e742063726564697420616d6f756e742065786365656460448201526e73207265706f727420626f756e647360881b6064820152608490fd5b60405162461bcd60e51b815260206004820152602a60248201527f5265706c6163656d656e742063726564697420616d6f756e74206d75737420626044820152696520706f73697469766560b01b6064820152608490fd5b60405162461bcd60e51b815260206004820152603360248201527f5265706c6163656d656e742063726564697420746f6b656e206d75737420636f6044820152726e7461696e20636f6e747261637420636f646560681b6064820152608490fd5b35906001600160801b038216820361060657565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b35906001600160a01b038216820361060657565b1561231457565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c6520617262697472616765206578656375746f72207265604482015267656e7472616e637960c01b6064820152608490fd5b6004356001600160a01b03811681036106065790565b6024356001600160a01b03811681036106065790565b610144356001600160a01b03811681036106065790565b610204356001600160a01b03811681036106065790565b610104356001600160a01b03811681036106065790565b6101c4356001600160a01b03811681036106065790565b610284356001600160a01b03811681036106065790565b1561241057565b60405162461bcd60e51b815260206004820152602d60248201527f4f70656e4f7261636c652061646472657373206d75737420636f6e7461696e2060448201526c636f6e747261637420636f646560981b6064820152608490fd5b8181029291811591840414171561247e57565b634e487b7160e01b5f52601160045260245ffd5b60a0810190811067ffffffffffffffff8211176106ea57604052565b610100810190811067ffffffffffffffff8211176106ea57604052565b90601f8019910116810190811067ffffffffffffffff8211176106ea57604052565b6001600160a01b039182168152918116602083015290911660408201526001600160801b03909116606082015260800190565b9190820391821161247e57565b9190820180921161247e57565b904315158061260e575b156125bd5780151591826125b2575b50501561255c57565b60405162461bcd60e51b815260206004820152602860248201527f457865637574696f6e2063616e6f6e6963616c20706172656e7420626c6f636b6044820152670818da185b99d95960c21b6064820152608490fd5b401490505f80612553565b60405162461bcd60e51b8152602060048201526024808201527f457865637574696f6e206d7573742074617267657420746865206e65787420626044820152636c6f636b60e01b6064820152608490fd5b505f19430143811161247e578214612544565b67ffffffffffffffff81116106ea57601f01601f191660200190565b359062ffffff8216820361060657565b600f0b6f7fffffffffffffffffffffffffffffff19811461247e575f0390565b906060905f905f5460ff811680612ef9575b15612ea45761268d82612621565b61269a60405191826124cb565b828152602081019083870193368511610606576020815f928a86378301015251902060015403612e4e57610100600160a81b0319165f90815560015560a09084900312610606576040516126ed81612492565b6126f6846122f9565b908181526127066020860161263d565b918260208301526040860135908115158203610606576040830191825262ffffff868401948789013586526080808601990135895216906127468261344d565b9060405161275381612492565b5f8152602081019160018060a01b03168252604081019384528881019260020b835260808101925f84528551151590815f14612e3a5788515b875115612e1f576401000276a4905b6040519c8d01938d851067ffffffffffffffff8611176106ea57612864946040528d5260208d0190815260408d019160018060a01b0316825260209c8d97604051946127e78a876124cb565b5f8652604051633cf3645360e21b815297516001600160a01b0390811660048a0152985189166024890152995162ffffff166044880152985160020b60648701529751861660848601529651151560a4850152955160c4840152945190921660e482015261012061010482015292839182916101248301906122d5565b03815f335af1908115610612575f91612df2575b508060801d916001600160801b0383600f0b9216600f0b9051612b72576128a28551600f0b61264d565b600f0b03612b2157841215612ad1576001600160801b031694518510612a755780516001600160a01b0316333b15612a715760405190632961046560e21b82526004820152838160248183335af18015612a6657908491612a4d575b50505181516129179133906001600160a01b03166134d0565b604051630476982d60e21b815290838260048186335af1918215612a42578392612a13575b5051036129bc57333b156129ae57604051630b0d9c0960e01b81526004810182905230602482015260448101849052818160648183335af180156129b157612999575b5050604051918183015281526129966040826124cb565b90565b6129a48280926124cb565b6129ae578061297f565b80fd5b6040513d84823e3d90fd5b60405162461bcd60e51b815260048101839052602960248201527f556e697377617020563420746f6b656e20736574746c656d656e7420776173206044820152681b9bdd08195e1858dd60ba1b6064820152608490fd5b9091508381813d8311612a3b575b612a2b81836124cb565b810103126106065751905f61293c565b503d612a21565b6040513d85823e3d90fd5b81612a57916124cb565b612a6257825f6128fe565b8280fd5b6040513d86823e3d90fd5b8380fd5b60405162461bcd60e51b815260048101859052602e60248201527f556e69737761702056342073656c6c206865646765207265636569766564207460448201526d0dede40d8d2e8e8d8ca40ae8aa8960931b6064820152608490fd5b60405162461bcd60e51b815260048101869052602260248201527f556e69737761702056342073656c6c206f75747075742077617320696e76616c6044820152611a5960f21b6064820152608490fd5b60405162461bcd60e51b815260048101879052602360248201527f556e69737761702056342073656c6c20696e70757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b9091508351600f0b03612da1575f811215612d5e57612b986001600160801b039161264d565b1694518511612d0657333b1561060657604051632961046560e21b81525f600482018190528160248183335af1801561061257612cf1575b50604051630476982d60e21b8152848160048189335af1908115612a66579086918591612cc0575b5003612c6857519051906001600160a01b0316333b15612a6257604051630b0d9c0960e01b81526001600160a01b039190911660048201523060248201526044810191909152818160648183335af180156129b157612999575050604051918183015281526129966040826124cb565b60405162461bcd60e51b815260048101859052602a60248201527f556e6973776170205634206e617469766520736574746c656d656e7420776173604482015269081b9bdd08195e1858dd60b21b6064820152608490fd5b809250868092503d8311612cea575b612cd981836124cb565b81010312610606578590515f612bf8565b503d612ccf565b612cfe9193505f906124cb565b5f915f612bd0565b60405162461bcd60e51b815260048101859052602a60248201527f556e697377617020563420627579206865646765206578636565646564206d616044820152690f0d2daeada40ae8aa8960b31b6064820152608490fd5b6064856040519062461bcd60e51b825280600483015260248201527f556e69737761702056342062757920696e7075742077617320696e76616c69646044820152fd5b60405162461bcd60e51b815260048101869052602360248201527f556e697377617020563420627579206f757470757420776173206e6f742065786044820152621858dd60ea1b6064820152608490fd5b90508581813d8311612e18575b612e0981836124cb565b8101031261060657515f612878565b503d612dff565b73fffd8963efd1fc6a506488495d951d5263988d259061279b565b8851600160ff1b811461247e575f0361278c565b60405162461bcd60e51b815260206004820152602860248201527f556e617574686f72697a656420556e69737761702056342063616c6c6261636b604482015267081c185e5b1bd85960c21b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f556e617574686f72697a656420556e697377617020563420756e6c6f636b2063604482015266616c6c6261636b60c81b6064820152608490fd5b5033600882901c6001600160a01b03161461267f565b15612f1657565b60405162461bcd60e51b815260206004820152603360248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220726560448201527271756972657320455243323020746f6b656e7360681b6064820152608490fd5b15612f7e57565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220746f60448201526f35b2b7399036bab9ba103234b33332b960811b6064820152608490fd5b6064356001600160801b03811681036106065790565b6084356001600160801b03811681036106065790565b359065ffffffffffff8216820361060657565b6001600160801b0361302c826122c1565b1682526001600160801b03613043602083016122c1565b1660208301526001600160a01b0361305d604083016122f9565b16604083015265ffffffffffff61307660608301613008565b16606083015265ffffffffffff61308f60808301613008565b1660808301526001600160a01b036130a960a083016122f9565b1660a083015265ffffffffffff6130c260c08301613008565b1660c083015265ffffffffffff6130db60e08301613008565b1660e08301526001600160801b036130f661010083016122c1565b166101008301526001600160a01b0361311261012083016122f9565b166101208301526101408101356bffffffffffffffffffffffff8116809103610606576101408301526001600160a01b0361315061016083016122f9565b1661016083015262ffffff613168610180830161263d565b1661018083015262ffffff6131806101a0830161263d565b166101a083015262ffffff6131986101c0830161263d565b166101c08301526101e081013561ffff8116809103610606576101e08301526001600160a01b036131cc61020083016122f9565b166102008301526102208101359063ffffffff8216809203610606576102609161022084015262ffffff613203610240830161263d565b1661024084015201359060ff8216809203610606576102600152565b8035825260609081906001600160a01b0361323c602083016122f9565b166020850152604081013560408501520135910152565b1561325a57565b60405162461bcd60e51b815260206004820152603c60248201527f4f70656e4f7261636c6520617262697472616765206578656375746f7220646f60448201527f6573206e6f7420737570706f72742073656c662d6469737075746573000000006064820152608490fd5b156132cc57565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e31207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561332457565b60405162461bcd60e51b8152602060048201526024808201527f546f6b656e32207472616e7366657220616d6f756e7420776173206e6f7420656044820152631e1858dd60e21b6064820152608490fd5b1561337c57565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e31207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b156133d857565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c6520746f6b656e32207265636569707420776173206e6f6044820152661d08195e1858dd60ca1b6064820152608490fd5b906001600160801b03809116911603906001600160801b03821161247e57565b62ffffff16606481146134ca576101f481146134c457610bb881146134be57612710146134b95760405162461bcd60e51b815260206004820152601f60248201527f556e737570706f7274656420556e697377617020563420706f6f6c20666565006044820152606490fd5b60c890565b50603c90565b50600a90565b50600190565b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448201929092526135189161351382606481015b03601f1981018452836124cb565b614920565b565b90801561363f576040516323b872dd60e01b60208281019190915233602483810191909152306044840152606483018490529390916135709061356a81608481015b03601f1981018352826124cb565b82614920565b6040516370a0823160e01b815230600482015293849182906001600160a01b03165afa918215610612575f92613609575b506135ac919261252d565b036135b357565b60405162461bcd60e51b815260206004820152602860248201527f546f6b656e207472616e7366657220746f206578656375746f7220776173206e6044820152671bdd08195e1858dd60c21b6064820152608490fd5b91506020823d602011613637575b81613624602093836124cb565b81010312610606576135ac9151916135a1565b3d9150613617565b505050565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529192916020908290604490829087165afa908115610612575f916136c6575b5061368f575050565b60405163095ea7b360e01b60208201526001600160a01b0390911660248201525f6044820152613518916135138260648101613505565b90506020813d6020116136f0575b816136e1602093836124cb565b8101031261060657515f613686565b3d91506136d4565b604051636eb1769f60e11b81523060048201526001600160a01b0380841660248301529293926020908290604490829086165afa908115610612575f916137bf575b50613784575b8161374a57505050565b60405163095ea7b360e01b60208201526001600160a01b039093166024840152604483019190915261351891906135138260648101613505565b60405163095ea7b360e01b60208201526001600160a01b03841660248201525f60448201526137ba9061356a816064810161355c565b613740565b90506020813d6020116137e9575b816137da602093836124cb565b8101031261060657515f61373a565b3d91506137cd565b604051906137fe82612492565b5f6080838281528260208201528260408201528260608201520152565b6138236137f1565b90608435906001600160801b038216918281036106065760a435916001600160801b0383169384840361060657610124356001600160801b0381168103610606576138776001600160801b0382168761246b565b9461014435956001600160801b0387168703610606576138a0846001600160801b03891661246b565b1015614820576001600160801b0382168203610606576001600160801b038216831115614812576001600160801b0382168203610606576001600160801b036138e9838761342d565b16916001600160801b03871687036106065761390e6001600160801b0388168961252d565b6001600160801b0388168803610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846139509550166001600160801b038c1661246b565b6001600160801b038816880361060657610364359062ffffff821682036106065762989680610a3e62ffffff846139929550166001600160801b038c1661246b565b915b60208a019360408b019384528452505f976001600160801b03821690818303610606576139c1925061246b565b6001600160801b03871695868814159485610606576139e191508761246b565b108089526141245750506139f36137f1565b506101c4356001600160a01b03811680821415949193918561412057610284356001600160a01b03811697888214159591949186613d9c57613d945790613a69915060608b01928352613a4d60208c01518251908661351a565b6020613a5f60408d015185519061252d565b910151908961351a565b6044359460ff861694858703613d9457600286149788156140fa5750505f5f94835160643562ffffff8116810361060657613aa38161344d565b5060016001607f1b038211614099574791602435906001600160a01b0382168203610606578e54610100600160a81b031916600883901b610100600160a81b0316178f5562ffffff83168303610606578e8e916080604051613b0481612492565b84815262ffffff60208201971687526040810193845260608101928352019160c435835262ffffff60405196602088019586525116604087015251151560608601525160808501525160a084015260a08352613b6160c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918e918391829084908290613ba59060248301906122d5565b03926001600160a01b03165af1908115613f56578d91614017575b508c5460081c6001600160a01b0316158061400d575b15613fbc5760208151918180820193849201010312610606575190613bfc82479261252d565b03613f615780613f0b575b60808d01525b5f9680613da457506024356001600160a01b0381169690878103613da05750613d9c5750613d9857506064359062ffffff821691828103613d94575051604051602093613cac936101049391613c62846124ae565b8b845286840152604083015230606083015260e435608083015260a082015260c43560c08201528960e082015289604051958694859363414bf38960e01b85526004850190614c11565b5af1908115613d89578691613d57575b5060808701525b608086015160c43511613cfe57612a71575015613cdf57505090565b602435916001600160a01b03831683036129ae57509061299691613644565b60405162461bcd60e51b815260206004820152602b60248201527f556e69737761702073656c6c20686564676520726563656976656420746f6f2060448201526a0d8d2e8e8d8ca40ae8aa8960ab1b6064820152608490fd5b90506020813d602011613d81575b81613d72602093836124cb565b8101031261060657515f613cbc565b3d9150613d65565b6040513d88823e3d90fd5b8980fd5b8880fd5b8a80fd5b8c80fd5b5f975093959493600103613ef55760405194613dc16060876124cb565b600286526040366020880137613d9c575088613ddc85614af2565b52613d985750613deb82614b13565b526024356001600160a01b03811690818103613d9857918891613e339493508351836040518097819582946338ed173960e01b845260e43591309160c4359060048701614ba9565b03925af1918215613eea578792613ec6575b5060028251149081613eb1575b5015613e6c57613e6190614b13565b516080870152613cc3565b60405162461bcd60e51b815260206004820152601f60248201527f556e69737761702056322073656c6c20616d6f756e747320696e76616c6964006044820152606490fd5b9050613ebc82614af2565b519051145f613e52565b613ee39192503d8089833e613edb81836124cb565b810190614b23565b905f613e45565b6040513d89823e3d90fd5b505050505050505f613f0683614aa6565b613cc3565b853b15613f5257604051630d0e30db60e41b81528c81600481858b5af18015613f5657908d91613f3d575b5050613c07565b81613f47916124cb565b613f52578b5f613f36565b8b80fd5b6040513d8f823e3d90fd5b60405162461bcd60e51b815260206004820152602d60248201527f556e6973776170205634206e61746976652062616c616e63652064656c74612060448201526c1dd85cc81b9bdd08195e1858dd609a1b6064820152608490fd5b60405162461bcd60e51b8152602060048201526024808201527f556e69737761702056342063616c6c6261636b20776173206e6f7420636f6e736044820152631d5b595960e21b6064820152608490fd5b5060015415613bd6565b90503d808e833e61402881836124cb565b8101906020818303126140955780519067ffffffffffffffff8211614091570181601f820112156140955780519061405f82612621565b9261406d60405194856124cb565b8284526020838301011161409157818f9260208093018386015e830101525f613bc0565b8e80fd5b8d80fd5b60405162461bcd60e51b815260206004820152603360248201527f556e697377617020563420686564676520616d6f756e742065786365656473206044820152727369676e65642064656c746120626f756e647360681b6064820152608490fd5b90946024356001600160a01b0381168103613f5257845161411b918c6136f8565b613c0d565b8780fd5b909291506141339493946137f1565b506101c4356001600160a01b0381169586821415938461060657610284359560018060a01b03871692838814159889610606576102e43562ffffff81168082036106065761418a629896809161419193508561246b565b048361252d565b9050610364359162ffffff831692838103610606576141b9936298968092610a3e925061246b565b9160608c01928084528651106147b45760206141f66142009351976141ed8d6141e560c435809c61252d565b86519161351a565b51855190612520565b910151908461351a565b6044359560ff871695868803610606576002871498891561478e5750505f935f9083516064359062ffffff821682036106065761423c8261344d565b5060016001607f1b0381116140995747918d3b15610606575f808f60248d6040519485938492632e1a7d4d60e01b845260048401525af1801561061257614779575b50602435906001600160a01b0382168203610606578f8054610100600160a81b038460081b1690610100600160a81b03191617905562ffffff8116810361060657604051926142cc84612492565b89845262ffffff602085019216825260806040850194600186526060810192835201908c82526040519462ffffff60208701948d86525116604087015251151560608601525160808501525160a084015260a0835261432c60c0846124cb565b825190206001556001600160a01b0381168103610606576040516348c8949160e01b815260206004820152918f9183918290849082906143709060248301906122d5565b03926001600160a01b03165af19081156146e4578e916146fb575b508d5460081c6001600160a01b031615806146f1575b15613fbc57602081519181808201938492010103126106065751906143c6828a612520565b906143d282479261252d565b03613f61578e81614697575b6080915001525b5f978061453957506024356001600160a01b03811696908781036140955750613f525750613d9457506064359062ffffff821691828103613d9c57505160405160209361448293610104939161443a846124ae565b8c845286840152604083015230606083015260e435608083015260a08201528560c08201528a60e08201528a6040519586948593631b67c43360e31b85526004850190614c11565b5af1908115613eea578791614507575b5060808801525b6080870151116144b257612a71575015613cdf57505090565b60405162461bcd60e51b815260206004820152602760248201527f556e697377617020627579206865646765206578636565646564206d6178696d6044820152660eada40ae8aa8960cb1b6064820152608490fd5b90506020813d602011614531575b81614522602093836124cb565b8101031261060657515f614492565b3d9150614515565b5f98509395949360010361467f57604051946145566060876124cb565b600286526040366020880137613f5257508961457185614af2565b52613d94575061458082614b13565b526024356001600160a01b03811690818103613d94579189916145c5949350835183604051809781958294634401edf760e11b845260e435918c309260048701614ba9565b03925af1918215614674578892614658575b5060028251149081614643575b50156145fe576145f390614af2565b516080880152614499565b60405162461bcd60e51b815260206004820152601e60248201527f556e69737761702056322062757920616d6f756e747320696e76616c696400006044820152606490fd5b905061464e82614b13565b519051145f6145e4565b61466d9192503d808a833e613edb81836124cb565b905f6145d7565b6040513d8a823e3d90fd5b50505050505090505f9061469284614aa6565b614499565b508c3b15614095578d8d600460405180948193630d0e30db60e41b83525af180156146e4578f90918f926146cc575b506143de565b6146d8915082906124cb565b613da0578c8e5f6146c6565b8e604051903d90823e3d90fd5b50600154156143a1565b90503d808f833e61470c81836124cb565b8101906020818303126140915780519067ffffffffffffffff8211614775570181601f820112156140915780519061474382612621565b9261475160405194856124cb565b82845260208383010111614775578f918060208093018386015e830101525f61438b565b8f80fd5b614786919f505f906124cb565b5f9d5f61427e565b90946024356001600160a01b0381168103610606576147af9088908d6136f8565b6143e5565b60405162461bcd60e51b815260206004820152603060248201527f4f70656e4f7261636c6520627579206865646765206578636565647320746f6b60448201526f32b7191031b7b73a3934b13aba34b7b760811b6064820152608490fd5b6001600160801b035f6138e9565b6001600160801b0382168203610606576148436001600160801b0383168461252d565b6001600160801b0383168303610606576102e4359062ffffff821682036106065762989680610a3e62ffffff846148859550166001600160801b03871661246b565b6001600160801b038316830361060657610364359062ffffff821682036106065762989680610a3e62ffffff846148c79550166001600160801b03871661246b565b916001600160801b0387168703610606576001600160801b038716881115614912576001600160801b0387168703610606576001600160801b0361490b888461342d565b1691613994565b6001600160801b035f61490b565b6001600160a01b0316803b15614a4357815f92918360208194519301915af13d15614a3b573d9061495082612621565b9161495e60405193846124cb565b82523d5f602084013e5b156149f757805180614978575050565b816020918101031261060657602001518015908115036106065761499857565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090614968565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b15614aad57565b60405162461bcd60e51b815260206004820152601760248201527f556e737570706f727465642068656467652076656e75650000000000000000006044820152606490fd5b805115614aff5760200190565b634e487b7160e01b5f52603260045260245ffd5b805160011015614aff5760400190565b6020818303126106065780519067ffffffffffffffff821161060657019080601f830112156106065781519167ffffffffffffffff83116106ea578260051b906020820193614b7560405195866124cb565b845260208085019282010192831161060657602001905b828210614b995750505090565b8151815260209182019101614b8c565b92919594939560a08401918452602084015260a060408401528151809152602060c084019201905f5b818110614bf2575050506001600160a01b03909416606082015260800152565b82516001600160a01b0316845260209384019390920191600101614bd2565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff16908401526060808301518216908401526080808301519084015260a0808301519084015260c0808301519084015260e0918201511691015256fea26469706673582212205580925ec9cf30c28f96f24a2efd4caebf322aa533aa00c4d93f3b23458957df64736f6c63430008230033', }, }, } as const @@ -1468,11 +1468,11 @@ export const targetArtifact = { evm: { bytecode: { object: - '60808060405234601557610aab908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80634fe21fe8146106fd57806356189cb4146106a75780635743fcbc146106445780636ea3387d146105f65780637d656cf71461040c5780638c64ed9514610273578063c4e2c1e6146101bb578063cad5e7a91461009d5763cb8ae6491461007c575f80fd5b34610099575f366003190112610099576020600254604051908152f35b5f80fd5b346100995761032036600319011261009957610280366023190112610099576080366102a319011261009957600254600181018091116101a7576002556024356001600160801b0381169081810361009957506001600160a01b03610100610848565b165f90815260208190526040902060c4356001600160a01b03811681036100995760018060a01b03165f5260205261013d60405f2091825461082e565b90556044356001600160801b0381169081810361009957506001600160a01b03610165610848565b165f908152602081905260409020610184356001600160a01b03811681036100995760018060a01b03165f526020526101a360405f2091825461082e565b9055005b634e487b7160e01b5f52601160045260245ffd5b346100995760206102606101ce366107aa565b335f90815280865260408082206001600160a01b0386168352875290205493929091848111156102685750610203848061083b565b335f90815280875260408082206001600160a01b0385811684529089529181902092909255905163a9059cbb60e01b81880152921660248301526044808301859052825261025260648361085e565b6001600160a01b03166108df565b604051908152f35b61020390809561083b565b610440366003190112610099576102886107e4565b506102916107fa565b5061029a610794565b506102a3610810565b506102ac61081f565b506102803660c319011261009957608036610343190112610099576080366103c319011261009957610164356001600160a01b038116908181036100995750610224356001600160a01b03811691908281036100995750604051636eb1769f60e11b815233600482015230602482015290602082604481845afa9182156103cd575f926103d8575b50604051636eb1769f60e11b815233600482015230602482015291602083604481875afa9283156103cd575f93610399575b5080610385575b50508061037657005b6103839130903390610894565b005b6103929130903390610894565b828061036d565b9092506020813d6020116103c5575b816103b56020938361085e565b8101031261009957519184610366565b3d91506103a8565b6040513d5f823e3d90fd5b9091506020813d602011610404575b816103f46020938361085e565b8101031261009957519083610334565b3d91506103e7565b3461009957608036600319011261009957610425610752565b61042d610768565b61043561077e565b90606435926001600160801b038416809403610099576001600160a01b039081165f81815260016020908152604080832033845282528083209487168352939052919091205484811061059c578460018201610562575b5050805f525f60205260405f2060018060a01b0384165f526020528360405f20541061050a575f525f60205260405f2060018060a01b0383165f5260205260405f206104d984825461083b565b905560018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b60405162461bcd60e51b815260206004820152602a60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c2062616c616e604482015269636520746f6f206c6f7760b01b6064820152608490fd5b61056b9161083b565b5f82815260016020908152604080832033845282528083206001600160a01b0388168452909152902055848461048c565b60405162461bcd60e51b815260206004820152602c60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c20616c6c6f7760448201526b616e636520746f6f206c6f7760a01b6064820152608490fd5b346100995760403660031901126100995761060f610752565b610617610768565b6001600160a01b039182165f90815260208181526040808320949093168252928352819020549051908152f35b346100995760603660031901126100995761065d610752565b610665610768565b61066d61077e565b6001600160a01b039283165f9081526001602090815260408083209486168352938152838220929094168152908352819020549051908152f35b34610099576060366003190112610099576106c0610752565b6106c8610768565b335f9081526001602090815260408083206001600160a01b039586168452825280832093909416825291909152206044359055005b346100995761070b366107aa565b9091906107238330336001600160a01b038616610894565b60018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b600435906001600160a01b038216820361009957565b602435906001600160a01b038216820361009957565b604435906001600160a01b038216820361009957565b606435906001600160a01b038216820361009957565b6060906003190112610099576004356001600160a01b03811681036100995790602435906044356001600160a01b03811681036100995790565b602435906001600160801b038216820361009957565b604435906001600160801b038216820361009957565b60843590811515820361009957565b60a43590811515820361009957565b919082018092116101a757565b919082039182116101a757565b6064356001600160a01b03811681036100995790565b90601f8019910116810190811067ffffffffffffffff82111761088057604052565b634e487b7160e01b5f52604160045260245ffd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526108dd916108d860848361085e565b6108df565b565b6001600160a01b0316803b15610a1257815f92918360208194519301915af13d15610a0a573d9067ffffffffffffffff8211610880576040519161092d601f8201601f19166020018461085e565b82523d5f602084013e5b156109c657805180610947575050565b816020918101031261009957602001518015908115036100995761096757565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610937565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea2646970667358221220e19d5567c05739d54809caa16843e323f8bc79a24eb4b811abcecc6171f760b764736f6c63430008230033', + '60808060405234601557610aab908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80634fe21fe8146106fd57806356189cb4146106a75780635743fcbc146106445780636ea3387d146105f65780637d656cf71461040c5780638c64ed9514610273578063c4e2c1e6146101bb578063cad5e7a91461009d5763cb8ae6491461007c575f80fd5b34610099575f366003190112610099576020600254604051908152f35b5f80fd5b346100995761032036600319011261009957610280366023190112610099576080366102a319011261009957600254600181018091116101a7576002556024356001600160801b0381169081810361009957506001600160a01b03610100610848565b165f90815260208190526040902060c4356001600160a01b03811681036100995760018060a01b03165f5260205261013d60405f2091825461082e565b90556044356001600160801b0381169081810361009957506001600160a01b03610165610848565b165f908152602081905260409020610184356001600160a01b03811681036100995760018060a01b03165f526020526101a360405f2091825461082e565b9055005b634e487b7160e01b5f52601160045260245ffd5b346100995760206102606101ce366107aa565b335f90815280865260408082206001600160a01b0386168352875290205493929091848111156102685750610203848061083b565b335f90815280875260408082206001600160a01b0385811684529089529181902092909255905163a9059cbb60e01b81880152921660248301526044808301859052825261025260648361085e565b6001600160a01b03166108df565b604051908152f35b61020390809561083b565b610440366003190112610099576102886107e4565b506102916107fa565b5061029a610794565b506102a3610810565b506102ac61081f565b506102803660c319011261009957608036610343190112610099576080366103c319011261009957610164356001600160a01b038116908181036100995750610224356001600160a01b03811691908281036100995750604051636eb1769f60e11b815233600482015230602482015290602082604481845afa9182156103cd575f926103d8575b50604051636eb1769f60e11b815233600482015230602482015291602083604481875afa9283156103cd575f93610399575b5080610385575b50508061037657005b6103839130903390610894565b005b6103929130903390610894565b828061036d565b9092506020813d6020116103c5575b816103b56020938361085e565b8101031261009957519184610366565b3d91506103a8565b6040513d5f823e3d90fd5b9091506020813d602011610404575b816103f46020938361085e565b8101031261009957519083610334565b3d91506103e7565b3461009957608036600319011261009957610425610752565b61042d610768565b61043561077e565b90606435926001600160801b038416809403610099576001600160a01b039081165f81815260016020908152604080832033845282528083209487168352939052919091205484811061059c578460018201610562575b5050805f525f60205260405f2060018060a01b0384165f526020528360405f20541061050a575f525f60205260405f2060018060a01b0383165f5260205260405f206104d984825461083b565b905560018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b60405162461bcd60e51b815260206004820152602a60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c2062616c616e604482015269636520746f6f206c6f7760b01b6064820152608490fd5b61056b9161083b565b5f82815260016020908152604080832033845282528083206001600160a01b0388168452909152902055848461048c565b60405162461bcd60e51b815260206004820152602c60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c20616c6c6f7760448201526b616e636520746f6f206c6f7760a01b6064820152608490fd5b346100995760403660031901126100995761060f610752565b610617610768565b6001600160a01b039182165f90815260208181526040808320949093168252928352819020549051908152f35b346100995760603660031901126100995761065d610752565b610665610768565b61066d61077e565b6001600160a01b039283165f9081526001602090815260408083209486168352938152838220929094168152908352819020549051908152f35b34610099576060366003190112610099576106c0610752565b6106c8610768565b335f9081526001602090815260408083206001600160a01b039586168452825280832093909416825291909152206044359055005b346100995761070b366107aa565b9091906107238330336001600160a01b038616610894565b60018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b600435906001600160a01b038216820361009957565b602435906001600160a01b038216820361009957565b604435906001600160a01b038216820361009957565b606435906001600160a01b038216820361009957565b6060906003190112610099576004356001600160a01b03811681036100995790602435906044356001600160a01b03811681036100995790565b602435906001600160801b038216820361009957565b604435906001600160801b038216820361009957565b60843590811515820361009957565b60a43590811515820361009957565b919082018092116101a757565b919082039182116101a757565b6064356001600160a01b03811681036100995790565b90601f8019910116810190811067ffffffffffffffff82111761088057604052565b634e487b7160e01b5f52604160045260245ffd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526108dd916108d860848361085e565b6108df565b565b6001600160a01b0316803b15610a1257815f92918360208194519301915af13d15610a0a573d9067ffffffffffffffff8211610880576040519161092d601f8201601f19166020018461085e565b82523d5f602084013e5b156109c657805180610947575050565b816020918101031261009957602001518015908115036100995761096757565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610937565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212204ac0c90604cedf243553be01bdcd5d526dabc9733dae353b377607ea0907e10764736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610011575f80fd5b5f3560e01c80634fe21fe8146106fd57806356189cb4146106a75780635743fcbc146106445780636ea3387d146105f65780637d656cf71461040c5780638c64ed9514610273578063c4e2c1e6146101bb578063cad5e7a91461009d5763cb8ae6491461007c575f80fd5b34610099575f366003190112610099576020600254604051908152f35b5f80fd5b346100995761032036600319011261009957610280366023190112610099576080366102a319011261009957600254600181018091116101a7576002556024356001600160801b0381169081810361009957506001600160a01b03610100610848565b165f90815260208190526040902060c4356001600160a01b03811681036100995760018060a01b03165f5260205261013d60405f2091825461082e565b90556044356001600160801b0381169081810361009957506001600160a01b03610165610848565b165f908152602081905260409020610184356001600160a01b03811681036100995760018060a01b03165f526020526101a360405f2091825461082e565b9055005b634e487b7160e01b5f52601160045260245ffd5b346100995760206102606101ce366107aa565b335f90815280865260408082206001600160a01b0386168352875290205493929091848111156102685750610203848061083b565b335f90815280875260408082206001600160a01b0385811684529089529181902092909255905163a9059cbb60e01b81880152921660248301526044808301859052825261025260648361085e565b6001600160a01b03166108df565b604051908152f35b61020390809561083b565b610440366003190112610099576102886107e4565b506102916107fa565b5061029a610794565b506102a3610810565b506102ac61081f565b506102803660c319011261009957608036610343190112610099576080366103c319011261009957610164356001600160a01b038116908181036100995750610224356001600160a01b03811691908281036100995750604051636eb1769f60e11b815233600482015230602482015290602082604481845afa9182156103cd575f926103d8575b50604051636eb1769f60e11b815233600482015230602482015291602083604481875afa9283156103cd575f93610399575b5080610385575b50508061037657005b6103839130903390610894565b005b6103929130903390610894565b828061036d565b9092506020813d6020116103c5575b816103b56020938361085e565b8101031261009957519184610366565b3d91506103a8565b6040513d5f823e3d90fd5b9091506020813d602011610404575b816103f46020938361085e565b8101031261009957519083610334565b3d91506103e7565b3461009957608036600319011261009957610425610752565b61042d610768565b61043561077e565b90606435926001600160801b038416809403610099576001600160a01b039081165f81815260016020908152604080832033845282528083209487168352939052919091205484811061059c578460018201610562575b5050805f525f60205260405f2060018060a01b0384165f526020528360405f20541061050a575f525f60205260405f2060018060a01b0383165f5260205260405f206104d984825461083b565b905560018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b60405162461bcd60e51b815260206004820152602a60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c2062616c616e604482015269636520746f6f206c6f7760b01b6064820152608490fd5b61056b9161083b565b5f82815260016020908152604080832033845282528083206001600160a01b0388168452909152902055848461048c565b60405162461bcd60e51b815260206004820152602c60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c20616c6c6f7760448201526b616e636520746f6f206c6f7760a01b6064820152608490fd5b346100995760403660031901126100995761060f610752565b610617610768565b6001600160a01b039182165f90815260208181526040808320949093168252928352819020549051908152f35b346100995760603660031901126100995761065d610752565b610665610768565b61066d61077e565b6001600160a01b039283165f9081526001602090815260408083209486168352938152838220929094168152908352819020549051908152f35b34610099576060366003190112610099576106c0610752565b6106c8610768565b335f9081526001602090815260408083206001600160a01b039586168452825280832093909416825291909152206044359055005b346100995761070b366107aa565b9091906107238330336001600160a01b038616610894565b60018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b600435906001600160a01b038216820361009957565b602435906001600160a01b038216820361009957565b604435906001600160a01b038216820361009957565b606435906001600160a01b038216820361009957565b6060906003190112610099576004356001600160a01b03811681036100995790602435906044356001600160a01b03811681036100995790565b602435906001600160801b038216820361009957565b604435906001600160801b038216820361009957565b60843590811515820361009957565b60a43590811515820361009957565b919082018092116101a757565b919082039182116101a757565b6064356001600160a01b03811681036100995790565b90601f8019910116810190811067ffffffffffffffff82111761088057604052565b634e487b7160e01b5f52604160045260245ffd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526108dd916108d860848361085e565b6108df565b565b6001600160a01b0316803b15610a1257815f92918360208194519301915af13d15610a0a573d9067ffffffffffffffff8211610880576040519161092d601f8201601f19166020018461085e565b82523d5f602084013e5b156109c657805180610947575050565b816020918101031261009957602001518015908115036100995761096757565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610937565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea2646970667358221220e19d5567c05739d54809caa16843e323f8bc79a24eb4b811abcecc6171f760b764736f6c63430008230033', + '60806040526004361015610011575f80fd5b5f3560e01c80634fe21fe8146106fd57806356189cb4146106a75780635743fcbc146106445780636ea3387d146105f65780637d656cf71461040c5780638c64ed9514610273578063c4e2c1e6146101bb578063cad5e7a91461009d5763cb8ae6491461007c575f80fd5b34610099575f366003190112610099576020600254604051908152f35b5f80fd5b346100995761032036600319011261009957610280366023190112610099576080366102a319011261009957600254600181018091116101a7576002556024356001600160801b0381169081810361009957506001600160a01b03610100610848565b165f90815260208190526040902060c4356001600160a01b03811681036100995760018060a01b03165f5260205261013d60405f2091825461082e565b90556044356001600160801b0381169081810361009957506001600160a01b03610165610848565b165f908152602081905260409020610184356001600160a01b03811681036100995760018060a01b03165f526020526101a360405f2091825461082e565b9055005b634e487b7160e01b5f52601160045260245ffd5b346100995760206102606101ce366107aa565b335f90815280865260408082206001600160a01b0386168352875290205493929091848111156102685750610203848061083b565b335f90815280875260408082206001600160a01b0385811684529089529181902092909255905163a9059cbb60e01b81880152921660248301526044808301859052825261025260648361085e565b6001600160a01b03166108df565b604051908152f35b61020390809561083b565b610440366003190112610099576102886107e4565b506102916107fa565b5061029a610794565b506102a3610810565b506102ac61081f565b506102803660c319011261009957608036610343190112610099576080366103c319011261009957610164356001600160a01b038116908181036100995750610224356001600160a01b03811691908281036100995750604051636eb1769f60e11b815233600482015230602482015290602082604481845afa9182156103cd575f926103d8575b50604051636eb1769f60e11b815233600482015230602482015291602083604481875afa9283156103cd575f93610399575b5080610385575b50508061037657005b6103839130903390610894565b005b6103929130903390610894565b828061036d565b9092506020813d6020116103c5575b816103b56020938361085e565b8101031261009957519184610366565b3d91506103a8565b6040513d5f823e3d90fd5b9091506020813d602011610404575b816103f46020938361085e565b8101031261009957519083610334565b3d91506103e7565b3461009957608036600319011261009957610425610752565b61042d610768565b61043561077e565b90606435926001600160801b038416809403610099576001600160a01b039081165f81815260016020908152604080832033845282528083209487168352939052919091205484811061059c578460018201610562575b5050805f525f60205260405f2060018060a01b0384165f526020528360405f20541061050a575f525f60205260405f2060018060a01b0383165f5260205260405f206104d984825461083b565b905560018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b60405162461bcd60e51b815260206004820152602a60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c2062616c616e604482015269636520746f6f206c6f7760b01b6064820152608490fd5b61056b9161083b565b5f82815260016020908152604080832033845282528083206001600160a01b0388168452909152902055848461048c565b60405162461bcd60e51b815260206004820152602c60248201527f4f70656e4f7261636c652074617267657420696e7465726e616c20616c6c6f7760448201526b616e636520746f6f206c6f7760a01b6064820152608490fd5b346100995760403660031901126100995761060f610752565b610617610768565b6001600160a01b039182165f90815260208181526040808320949093168252928352819020549051908152f35b346100995760603660031901126100995761065d610752565b610665610768565b61066d61077e565b6001600160a01b039283165f9081526001602090815260408083209486168352938152838220929094168152908352819020549051908152f35b34610099576060366003190112610099576106c0610752565b6106c8610768565b335f9081526001602090815260408083206001600160a01b039586168452825280832093909416825291909152206044359055005b346100995761070b366107aa565b9091906107238330336001600160a01b038616610894565b60018060a01b03165f525f60205260405f209060018060a01b03165f526020526101a360405f2091825461082e565b600435906001600160a01b038216820361009957565b602435906001600160a01b038216820361009957565b604435906001600160a01b038216820361009957565b606435906001600160a01b038216820361009957565b6060906003190112610099576004356001600160a01b03811681036100995790602435906044356001600160a01b03811681036100995790565b602435906001600160801b038216820361009957565b604435906001600160801b038216820361009957565b60843590811515820361009957565b60a43590811515820361009957565b919082018092116101a757565b919082039182116101a757565b6064356001600160a01b03811681036100995790565b90601f8019910116810190811067ffffffffffffffff82111761088057604052565b634e487b7160e01b5f52604160045260245ffd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526108dd916108d860848361085e565b6108df565b565b6001600160a01b0316803b15610a1257815f92918360208194519301915af13d15610a0a573d9067ffffffffffffffff8211610880576040519161092d601f8201601f19166020018461085e565b82523d5f602084013e5b156109c657805180610947575050565b816020918101031261009957602001518015908115036100995761096757565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610937565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212204ac0c90604cedf243553be01bdcd5d526dabc9733dae353b377607ea0907e10764736f6c63430008230033', }, }, } as const @@ -1791,11 +1791,11 @@ export const feeTokenArtifact = { evm: { bytecode: { object: - '60a06040523461039357610cbb6020813803918261001c81610397565b93849283398101031261039357516100346040610397565b9060098252682332b2902a37b5b2b760b91b60208301526100556040610397565b600381526246454560e81b602082015282519091906001600160401b03811161029c575f54600181811c91168015610389575b602082101461027e57601f811161031c575b506020601f82116001146102bb57819293945f926102b0575b50508160011b915f199060031b1c1916175f555b81516001600160401b03811161029c57600154600181811c91168015610292575b602082101461027e57601f8111610210575b50602092601f82116001146101af57928192935f926101a4575b50508160011b915f199060031b1c1916176001555b612710811015610155576080526040516108fe90816103bd82396080518181816103ca01526107640152f35b60405162461bcd60e51b815260206004820152602160248201527f4f70656e4f7261636c652066656520746f6b656e2066656520746f6f206869676044820152600d60fb1b6064820152608490fd5b015190505f80610114565b601f1982169360015f52805f20915f5b8681106101f857508360019596106101e0575b505050811b01600155610129565b01515f1960f88460031b161c191690555f80806101d2565b919260206001819286850151815501940192016101bf565b818111156100fa5760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660208410610276575b81601f9101920160051c03905f5b8281106102695750506100fa565b5f8282015560010161025b565b5f915061024d565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e8565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b3565b601f198216905f8052805f20915f5b818110610304575095836001959697106102ec575b505050811b015f556100c7565b01515f1960f88460031b161c191690555f80806102df565b9192602060018192868b0151815501940192016102ca565b8181111561009a575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360208410610381575b81601f9101920160051c03905f5b82811061037457505061009a565b5f82820155600101610366565b5f9150610358565b90607f1690610088565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761029c5760405256fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104b2578063095ea7b31461043957806318160ddd1461041c57806323b872dd146103ed57806324a9d853146103b3578063313ce5671461039857806333ea03891461034f57806340c10f19146102d557806370a082311461029d57806395d89b411461017f578063a0cf6e8b1461015d578063a9059cbb1461012c578063d17c8d43146101075763dd62ed3e146100b3575f80fd5b34610103576040366003190112610103576100cc6105ae565b6100d46105c4565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b34610103575f36600319011261010357602060ff60035460081c166040519015158152f35b3461010357604036600319011261010357602061015361014a6105ae565b60243590610714565b6040519015158152f35b34610103575f36600319011261010357602060ff600354166040519015158152f35b34610103575f366003190112610103576040515f6001548060011c90600181168015610293575b60208310811461027f57828552908115610263575060011461020d575b50819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82821061024d575060209150820101826101c3565b6001816020925483858801015201910190610238565b90506020925060ff191682840152151560051b820101826101c3565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101a6565b34610103576020366003190112610103576001600160a01b036102be6105ae565b165f526004602052602060405f2054604051908152f35b34610103576040366003190112610103576102ee6105ae565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206024359361032285600254610707565b60025560018060a01b0316938484526004825260408420610344828254610707565b9055604051908152a3005b3461010357604036600319011261010357600435801515809103610103576024358015158091036101035760ff61ff006003549260081b1692169061ffff191617176003555f80f35b34610103575f36600319011261010357602060405160128152f35b34610103575f3660031901126101035760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461010357606036600319011261010357602061015361040b6105ae565b6104136105c4565b604435916105fb565b34610103575f366003190112610103576020600254604051908152f35b34610103576040366003190112610103576104526105ae565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b34610103575f366003190112610103576040515f5f548060011c9060018116801561057a575b60208310811461027f5782855290811561026357506001146105265750819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828210610564575060209150820101826101c3565b600181602092548385880101520191019061054f565b91607f16916104d8565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361010357565b602435906001600160a01b038216820361010357565b919082039182116105e757565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c16610700576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106ab5761064c948460018201610651575b50505061072f565b600190565b61065a916105da565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610644565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105e757565b9060ff600354166107295761064c913361072f565b50505f90565b6001600160a01b0390911691908215610874576001600160a01b03165f818152600460205260409020549091808210610823577f0000000000000000000000000000000000000000000000000000000000000000908181029181830414811517156105e7575f94847fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206108138297866107d1612710859a049889936105da565b868d526004855260408d20556107e782826105da565b878d52600485526107fd60408e20918254610707565b905561080b826002546105da565b6002556105da565b604051908152a3604051908152a3565b60405162461bcd60e51b8152602060048201526024808201527f4f70656e4f7261636c652066656520746f6b656e2062616c616e636520746f6f604482015263206c6f7760e01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652066656520746f6b656e20726563697069656e74206960448201526573207a65726f60d01b6064820152608490fdfea2646970667358221220dc307f89bd31814d718ca3d1f6e7d0245623ceb2c2b4b77abf6cad155e0f001064736f6c63430008230033', + '60a06040523461039357610cbb6020813803918261001c81610397565b93849283398101031261039357516100346040610397565b9060098252682332b2902a37b5b2b760b91b60208301526100556040610397565b600381526246454560e81b602082015282519091906001600160401b03811161029c575f54600181811c91168015610389575b602082101461027e57601f811161031c575b506020601f82116001146102bb57819293945f926102b0575b50508160011b915f199060031b1c1916175f555b81516001600160401b03811161029c57600154600181811c91168015610292575b602082101461027e57601f8111610210575b50602092601f82116001146101af57928192935f926101a4575b50508160011b915f199060031b1c1916176001555b612710811015610155576080526040516108fe90816103bd82396080518181816103ca01526107640152f35b60405162461bcd60e51b815260206004820152602160248201527f4f70656e4f7261636c652066656520746f6b656e2066656520746f6f206869676044820152600d60fb1b6064820152608490fd5b015190505f80610114565b601f1982169360015f52805f20915f5b8681106101f857508360019596106101e0575b505050811b01600155610129565b01515f1960f88460031b161c191690555f80806101d2565b919260206001819286850151815501940192016101bf565b818111156100fa5760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660208410610276575b81601f9101920160051c03905f5b8281106102695750506100fa565b5f8282015560010161025b565b5f915061024d565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e8565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b3565b601f198216905f8052805f20915f5b818110610304575095836001959697106102ec575b505050811b015f556100c7565b01515f1960f88460031b161c191690555f80806102df565b9192602060018192868b0151815501940192016102ca565b8181111561009a575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360208410610381575b81601f9101920160051c03905f5b82811061037457505061009a565b5f82820155600101610366565b5f9150610358565b90607f1690610088565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761029c5760405256fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104b2578063095ea7b31461043957806318160ddd1461041c57806323b872dd146103ed57806324a9d853146103b3578063313ce5671461039857806333ea03891461034f57806340c10f19146102d557806370a082311461029d57806395d89b411461017f578063a0cf6e8b1461015d578063a9059cbb1461012c578063d17c8d43146101075763dd62ed3e146100b3575f80fd5b34610103576040366003190112610103576100cc6105ae565b6100d46105c4565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b34610103575f36600319011261010357602060ff60035460081c166040519015158152f35b3461010357604036600319011261010357602061015361014a6105ae565b60243590610714565b6040519015158152f35b34610103575f36600319011261010357602060ff600354166040519015158152f35b34610103575f366003190112610103576040515f6001548060011c90600181168015610293575b60208310811461027f57828552908115610263575060011461020d575b50819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82821061024d575060209150820101826101c3565b6001816020925483858801015201910190610238565b90506020925060ff191682840152151560051b820101826101c3565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101a6565b34610103576020366003190112610103576001600160a01b036102be6105ae565b165f526004602052602060405f2054604051908152f35b34610103576040366003190112610103576102ee6105ae565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206024359361032285600254610707565b60025560018060a01b0316938484526004825260408420610344828254610707565b9055604051908152a3005b3461010357604036600319011261010357600435801515809103610103576024358015158091036101035760ff61ff006003549260081b1692169061ffff191617176003555f80f35b34610103575f36600319011261010357602060405160128152f35b34610103575f3660031901126101035760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461010357606036600319011261010357602061015361040b6105ae565b6104136105c4565b604435916105fb565b34610103575f366003190112610103576020600254604051908152f35b34610103576040366003190112610103576104526105ae565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b34610103575f366003190112610103576040515f5f548060011c9060018116801561057a575b60208310811461027f5782855290811561026357506001146105265750819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828210610564575060209150820101826101c3565b600181602092548385880101520191019061054f565b91607f16916104d8565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361010357565b602435906001600160a01b038216820361010357565b919082039182116105e757565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c16610700576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106ab5761064c948460018201610651575b50505061072f565b600190565b61065a916105da565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610644565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105e757565b9060ff600354166107295761064c913361072f565b50505f90565b6001600160a01b0390911691908215610874576001600160a01b03165f818152600460205260409020549091808210610823577f0000000000000000000000000000000000000000000000000000000000000000908181029181830414811517156105e7575f94847fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206108138297866107d1612710859a049889936105da565b868d526004855260408d20556107e782826105da565b878d52600485526107fd60408e20918254610707565b905561080b826002546105da565b6002556105da565b604051908152a3604051908152a3565b60405162461bcd60e51b8152602060048201526024808201527f4f70656e4f7261636c652066656520746f6b656e2062616c616e636520746f6f604482015263206c6f7760e01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652066656520746f6b656e20726563697069656e74206960448201526573207a65726f60d01b6064820152608490fdfea264697066735822122099b870a40477f2f0558c62f6f83993901c3c538afa48d3d92695f1d6a8ad98f964736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104b2578063095ea7b31461043957806318160ddd1461041c57806323b872dd146103ed57806324a9d853146103b3578063313ce5671461039857806333ea03891461034f57806340c10f19146102d557806370a082311461029d57806395d89b411461017f578063a0cf6e8b1461015d578063a9059cbb1461012c578063d17c8d43146101075763dd62ed3e146100b3575f80fd5b34610103576040366003190112610103576100cc6105ae565b6100d46105c4565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b34610103575f36600319011261010357602060ff60035460081c166040519015158152f35b3461010357604036600319011261010357602061015361014a6105ae565b60243590610714565b6040519015158152f35b34610103575f36600319011261010357602060ff600354166040519015158152f35b34610103575f366003190112610103576040515f6001548060011c90600181168015610293575b60208310811461027f57828552908115610263575060011461020d575b50819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82821061024d575060209150820101826101c3565b6001816020925483858801015201910190610238565b90506020925060ff191682840152151560051b820101826101c3565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101a6565b34610103576020366003190112610103576001600160a01b036102be6105ae565b165f526004602052602060405f2054604051908152f35b34610103576040366003190112610103576102ee6105ae565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206024359361032285600254610707565b60025560018060a01b0316938484526004825260408420610344828254610707565b9055604051908152a3005b3461010357604036600319011261010357600435801515809103610103576024358015158091036101035760ff61ff006003549260081b1692169061ffff191617176003555f80f35b34610103575f36600319011261010357602060405160128152f35b34610103575f3660031901126101035760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461010357606036600319011261010357602061015361040b6105ae565b6104136105c4565b604435916105fb565b34610103575f366003190112610103576020600254604051908152f35b34610103576040366003190112610103576104526105ae565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b34610103575f366003190112610103576040515f5f548060011c9060018116801561057a575b60208310811461027f5782855290811561026357506001146105265750819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828210610564575060209150820101826101c3565b600181602092548385880101520191019061054f565b91607f16916104d8565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361010357565b602435906001600160a01b038216820361010357565b919082039182116105e757565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c16610700576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106ab5761064c948460018201610651575b50505061072f565b600190565b61065a916105da565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610644565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105e757565b9060ff600354166107295761064c913361072f565b50505f90565b6001600160a01b0390911691908215610874576001600160a01b03165f818152600460205260409020549091808210610823577f0000000000000000000000000000000000000000000000000000000000000000908181029181830414811517156105e7575f94847fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206108138297866107d1612710859a049889936105da565b868d526004855260408d20556107e782826105da565b878d52600485526107fd60408e20918254610707565b905561080b826002546105da565b6002556105da565b604051908152a3604051908152a3565b60405162461bcd60e51b8152602060048201526024808201527f4f70656e4f7261636c652066656520746f6b656e2062616c616e636520746f6f604482015263206c6f7760e01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652066656520746f6b656e20726563697069656e74206960448201526573207a65726f60d01b6064820152608490fdfea2646970667358221220dc307f89bd31814d718ca3d1f6e7d0245623ceb2c2b4b77abf6cad155e0f001064736f6c63430008230033', + '60806040526004361015610011575f80fd5b5f3560e01c806306fdde03146104b2578063095ea7b31461043957806318160ddd1461041c57806323b872dd146103ed57806324a9d853146103b3578063313ce5671461039857806333ea03891461034f57806340c10f19146102d557806370a082311461029d57806395d89b411461017f578063a0cf6e8b1461015d578063a9059cbb1461012c578063d17c8d43146101075763dd62ed3e146100b3575f80fd5b34610103576040366003190112610103576100cc6105ae565b6100d46105c4565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b34610103575f36600319011261010357602060ff60035460081c166040519015158152f35b3461010357604036600319011261010357602061015361014a6105ae565b60243590610714565b6040519015158152f35b34610103575f36600319011261010357602060ff600354166040519015158152f35b34610103575f366003190112610103576040515f6001548060011c90600181168015610293575b60208310811461027f57828552908115610263575060011461020d575b50819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b82821061024d575060209150820101826101c3565b6001816020925483858801015201910190610238565b90506020925060ff191682840152151560051b820101826101c3565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101a6565b34610103576020366003190112610103576001600160a01b036102be6105ae565b165f526004602052602060405f2054604051908152f35b34610103576040366003190112610103576102ee6105ae565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206024359361032285600254610707565b60025560018060a01b0316938484526004825260408420610344828254610707565b9055604051908152a3005b3461010357604036600319011261010357600435801515809103610103576024358015158091036101035760ff61ff006003549260081b1692169061ffff191617176003555f80f35b34610103575f36600319011261010357602060405160128152f35b34610103575f3660031901126101035760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b3461010357606036600319011261010357602061015361040b6105ae565b6104136105c4565b604435916105fb565b34610103575f366003190112610103576020600254604051908152f35b34610103576040366003190112610103576104526105ae565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b34610103575f366003190112610103576040515f5f548060011c9060018116801561057a575b60208310811461027f5782855290811561026357506001146105265750819003601f01601f191681019067ffffffffffffffff8211818310176101f957604082905281906101f59082610584565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828210610564575060209150820101826101c3565b600181602092548385880101520191019061054f565b91607f16916104d8565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361010357565b602435906001600160a01b038216820361010357565b919082039182116105e757565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c16610700576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106ab5761064c948460018201610651575b50505061072f565b600190565b61065a916105da565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610644565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105e757565b9060ff600354166107295761064c913361072f565b50505f90565b6001600160a01b0390911691908215610874576001600160a01b03165f818152600460205260409020549091808210610823577f0000000000000000000000000000000000000000000000000000000000000000908181029181830414811517156105e7575f94847fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206108138297866107d1612710859a049889936105da565b868d526004855260408d20556107e782826105da565b878d52600485526107fd60408e20918254610707565b905561080b826002546105da565b6002556105da565b604051908152a3604051908152a3565b60405162461bcd60e51b8152602060048201526024808201527f4f70656e4f7261636c652066656520746f6b656e2062616c616e636520746f6f604482015263206c6f7760e01b6064820152608490fd5b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652066656520746f6b656e20726563697069656e74206960448201526573207a65726f60d01b6064820152608490fdfea264697066735822122099b870a40477f2f0558c62f6f83993901c3c538afa48d3d92695f1d6a8ad98f964736f6c63430008230033', }, }, } as const @@ -2006,11 +2006,11 @@ export const routerArtifact = { evm: { bytecode: { object: - '608080604052346015576106dd908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806338ed1739146101c3578063414bf389146101955780638803dbee146100c05763db3e219814610045575f80fd5b6101003660031901126100bc576100606084354211156102d8565b602060a43561007360c435821115610433565b610091816001600160a01b0361008761038a565b163090339061048b565b6100b4816001600160a01b036100a56103a0565b166100ae6103b6565b906104d6565b604051908152f35b5f80fd5b346100bc576100f96100f16100d4366101df565b6100e96002849694999599989397981461028c565b4211156102d8565b841115610433565b801561018157610114836001600160a01b03610087876103cc565b600110156101815761017d926101419183916001600160a01b039061013b906020016103cc565b166104d6565b604051906101506060836103e0565b6002825260403660208401378061016683610416565b5261017082610423565b5260405191829182610253565b0390f35b634e487b7160e01b5f52603260045260245ffd5b6101003660031901126100bc576101b06084354211156102d8565b602060a43561007360c435821015610332565b346100bc576100f96101d76100d4366101df565b841015610332565b9060a06003198301126100bc57600435916024359160443567ffffffffffffffff81116100bc57826023820112156100bc5780600401359267ffffffffffffffff84116100bc5760248460051b830101116100bc5760240191906064356001600160a01b03811681036100bc579060843590565b60206040818301928281528451809452019201905f5b8181106102765750505090565b8251845260209384019390920191600101610269565b1561029357565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c652074657374205632207061746820696e76616c6964006044820152606490fd5b156102df57565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c652074657374207377617020646561646c696e652065786044820152641c1a5c995960da1b6064820152608490fd5b1561033957565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c6520746573742073776170206f757470757420746f6f206044820152626c6f7760e81b6064820152608490fd5b6004356001600160a01b03811681036100bc5790565b6024356001600160a01b03811681036100bc5790565b6064356001600160a01b03811681036100bc5790565b356001600160a01b03811681036100bc5790565b90601f8019910116810190811067ffffffffffffffff82111761040257604052565b634e487b7160e01b5f52604160045260245ffd5b8051156101815760200190565b8051600110156101815760400190565b1561043a57565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c652074657374207377617020696e70757420746f6f20686044820152620d2ced60eb1b6064820152608490fd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526104d4916104cf6084836103e0565b610511565b565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044808301939093529181526104d4916104cf6064836103e0565b6001600160a01b0316803b1561064457815f92918360208194519301915af13d1561063c573d9067ffffffffffffffff8211610402576040519161055f601f8201601f1916602001846103e0565b82523d5f602084013e5b156105f857805180610579575050565b81602091810103126100bc57602001518015908115036100bc5761059957565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610569565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212204226ea157f9c6236d896099c9326da14a500c96f93721bc8568003a092e4687b64736f6c63430008230033', + '608080604052346015576106dd908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806338ed1739146101c3578063414bf389146101955780638803dbee146100c05763db3e219814610045575f80fd5b6101003660031901126100bc576100606084354211156102d8565b602060a43561007360c435821115610433565b610091816001600160a01b0361008761038a565b163090339061048b565b6100b4816001600160a01b036100a56103a0565b166100ae6103b6565b906104d6565b604051908152f35b5f80fd5b346100bc576100f96100f16100d4366101df565b6100e96002849694999599989397981461028c565b4211156102d8565b841115610433565b801561018157610114836001600160a01b03610087876103cc565b600110156101815761017d926101419183916001600160a01b039061013b906020016103cc565b166104d6565b604051906101506060836103e0565b6002825260403660208401378061016683610416565b5261017082610423565b5260405191829182610253565b0390f35b634e487b7160e01b5f52603260045260245ffd5b6101003660031901126100bc576101b06084354211156102d8565b602060a43561007360c435821015610332565b346100bc576100f96101d76100d4366101df565b841015610332565b9060a06003198301126100bc57600435916024359160443567ffffffffffffffff81116100bc57826023820112156100bc5780600401359267ffffffffffffffff84116100bc5760248460051b830101116100bc5760240191906064356001600160a01b03811681036100bc579060843590565b60206040818301928281528451809452019201905f5b8181106102765750505090565b8251845260209384019390920191600101610269565b1561029357565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c652074657374205632207061746820696e76616c6964006044820152606490fd5b156102df57565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c652074657374207377617020646561646c696e652065786044820152641c1a5c995960da1b6064820152608490fd5b1561033957565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c6520746573742073776170206f757470757420746f6f206044820152626c6f7760e81b6064820152608490fd5b6004356001600160a01b03811681036100bc5790565b6024356001600160a01b03811681036100bc5790565b6064356001600160a01b03811681036100bc5790565b356001600160a01b03811681036100bc5790565b90601f8019910116810190811067ffffffffffffffff82111761040257604052565b634e487b7160e01b5f52604160045260245ffd5b8051156101815760200190565b8051600110156101815760400190565b1561043a57565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c652074657374207377617020696e70757420746f6f20686044820152620d2ced60eb1b6064820152608490fd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526104d4916104cf6084836103e0565b610511565b565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044808301939093529181526104d4916104cf6064836103e0565b6001600160a01b0316803b1561064457815f92918360208194519301915af13d1561063c573d9067ffffffffffffffff8211610402576040519161055f601f8201601f1916602001846103e0565b82523d5f602084013e5b156105f857805180610579575050565b81602091810103126100bc57602001518015908115036100bc5761059957565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610569565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212200fb680297ad1ed4ba9ef2dff594b71e40b0971a7936bb42f5bd4ac01989146c264736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610011575f80fd5b5f3560e01c806338ed1739146101c3578063414bf389146101955780638803dbee146100c05763db3e219814610045575f80fd5b6101003660031901126100bc576100606084354211156102d8565b602060a43561007360c435821115610433565b610091816001600160a01b0361008761038a565b163090339061048b565b6100b4816001600160a01b036100a56103a0565b166100ae6103b6565b906104d6565b604051908152f35b5f80fd5b346100bc576100f96100f16100d4366101df565b6100e96002849694999599989397981461028c565b4211156102d8565b841115610433565b801561018157610114836001600160a01b03610087876103cc565b600110156101815761017d926101419183916001600160a01b039061013b906020016103cc565b166104d6565b604051906101506060836103e0565b6002825260403660208401378061016683610416565b5261017082610423565b5260405191829182610253565b0390f35b634e487b7160e01b5f52603260045260245ffd5b6101003660031901126100bc576101b06084354211156102d8565b602060a43561007360c435821015610332565b346100bc576100f96101d76100d4366101df565b841015610332565b9060a06003198301126100bc57600435916024359160443567ffffffffffffffff81116100bc57826023820112156100bc5780600401359267ffffffffffffffff84116100bc5760248460051b830101116100bc5760240191906064356001600160a01b03811681036100bc579060843590565b60206040818301928281528451809452019201905f5b8181106102765750505090565b8251845260209384019390920191600101610269565b1561029357565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c652074657374205632207061746820696e76616c6964006044820152606490fd5b156102df57565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c652074657374207377617020646561646c696e652065786044820152641c1a5c995960da1b6064820152608490fd5b1561033957565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c6520746573742073776170206f757470757420746f6f206044820152626c6f7760e81b6064820152608490fd5b6004356001600160a01b03811681036100bc5790565b6024356001600160a01b03811681036100bc5790565b6064356001600160a01b03811681036100bc5790565b356001600160a01b03811681036100bc5790565b90601f8019910116810190811067ffffffffffffffff82111761040257604052565b634e487b7160e01b5f52604160045260245ffd5b8051156101815760200190565b8051600110156101815760400190565b1561043a57565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c652074657374207377617020696e70757420746f6f20686044820152620d2ced60eb1b6064820152608490fd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526104d4916104cf6084836103e0565b610511565b565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044808301939093529181526104d4916104cf6064836103e0565b6001600160a01b0316803b1561064457815f92918360208194519301915af13d1561063c573d9067ffffffffffffffff8211610402576040519161055f601f8201601f1916602001846103e0565b82523d5f602084013e5b156105f857805180610579575050565b81602091810103126100bc57602001518015908115036100bc5761059957565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610569565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212204226ea157f9c6236d896099c9326da14a500c96f93721bc8568003a092e4687b64736f6c63430008230033', + '60806040526004361015610011575f80fd5b5f3560e01c806338ed1739146101c3578063414bf389146101955780638803dbee146100c05763db3e219814610045575f80fd5b6101003660031901126100bc576100606084354211156102d8565b602060a43561007360c435821115610433565b610091816001600160a01b0361008761038a565b163090339061048b565b6100b4816001600160a01b036100a56103a0565b166100ae6103b6565b906104d6565b604051908152f35b5f80fd5b346100bc576100f96100f16100d4366101df565b6100e96002849694999599989397981461028c565b4211156102d8565b841115610433565b801561018157610114836001600160a01b03610087876103cc565b600110156101815761017d926101419183916001600160a01b039061013b906020016103cc565b166104d6565b604051906101506060836103e0565b6002825260403660208401378061016683610416565b5261017082610423565b5260405191829182610253565b0390f35b634e487b7160e01b5f52603260045260245ffd5b6101003660031901126100bc576101b06084354211156102d8565b602060a43561007360c435821015610332565b346100bc576100f96101d76100d4366101df565b841015610332565b9060a06003198301126100bc57600435916024359160443567ffffffffffffffff81116100bc57826023820112156100bc5780600401359267ffffffffffffffff84116100bc5760248460051b830101116100bc5760240191906064356001600160a01b03811681036100bc579060843590565b60206040818301928281528451809452019201905f5b8181106102765750505090565b8251845260209384019390920191600101610269565b1561029357565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c652074657374205632207061746820696e76616c6964006044820152606490fd5b156102df57565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c652074657374207377617020646561646c696e652065786044820152641c1a5c995960da1b6064820152608490fd5b1561033957565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c6520746573742073776170206f757470757420746f6f206044820152626c6f7760e81b6064820152608490fd5b6004356001600160a01b03811681036100bc5790565b6024356001600160a01b03811681036100bc5790565b6064356001600160a01b03811681036100bc5790565b356001600160a01b03811681036100bc5790565b90601f8019910116810190811067ffffffffffffffff82111761040257604052565b634e487b7160e01b5f52604160045260245ffd5b8051156101815760200190565b8051600110156101815760400190565b1561043a57565b60405162461bcd60e51b815260206004820152602360248201527f4f70656e4f7261636c652074657374207377617020696e70757420746f6f20686044820152620d2ced60eb1b6064820152608490fd5b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064808301939093529181526104d4916104cf6084836103e0565b610511565b565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044808301939093529181526104d4916104cf6064836103e0565b6001600160a01b0316803b1561064457815f92918360208194519301915af13d1561063c573d9067ffffffffffffffff8211610402576040519161055f601f8201601f1916602001846103e0565b82523d5f602084013e5b156105f857805180610579575050565b81602091810103126100bc57602001518015908115036100bc5761059957565b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b606090610569565b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fdfea26469706673582212200fb680297ad1ed4ba9ef2dff594b71e40b0971a7936bb42f5bd4ac01989146c264736f6c63430008230033', }, }, } as const @@ -2321,11 +2321,11 @@ export const tokenArtifact = { evm: { bytecode: { object: - '60806040523461032157610bf38038038061001981610325565b9283398101906040818303126103215780516001600160401b038111610321578261004591830161034a565b60208201519092906001600160401b03811161032157610065920161034a565b81516001600160401b03811161022c575f54600181811c91168015610317575b602082101461020e57601f81116102aa575b50602092601f821160011461024b57928192935f92610240575b50508160011b915f199060031b1c1916175f555b80516001600160401b03811161022c57600154600181811c91168015610222575b602082101461020e57601f81116101a0575b50602091601f8211600114610140579181925f92610135575b50508160011b915f199060031b1c1916176001555b604051610857908161039c8239f35b015190505f80610111565b601f1982169260015f52805f20915f5b85811061018857508360019510610170575b505050811b01600155610126565b01515f1960f88460031b161c191690555f8080610162565b91926020600181928685015181550194019201610150565b818111156100f85760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660208410610206575b81601f9101920160051c03905f5b8281106101f95750506100f8565b5f828201556001016101eb565b5f91506101dd565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e6565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b1565b601f198216935f8052805f20915f5b868110610292575083600195961061027a575b505050811b015f556100c5565b01515f1960f88460031b161c191690555f808061026d565b9192602060018192868501518155019401920161025a565b81811115610097575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5636020841061030f575b81601f9101920160051c03905f5b828110610302575050610097565b5f828201556001016102f4565b5f91506102e6565b90607f1690610085565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761022c57604052565b81601f82011215610321578051906001600160401b03821161022c57610379601f8301601f1916602001610325565b928284526020838301011161032157815f9260208093018386015e830101529056fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde031461046d578063095ea7b3146103f457806318160ddd146103d757806323b872dd146103a8578063313ce5671461038d57806333ea03891461034457806340c10f19146102ca57806370a082311461029257806395d89b4114610174578063a0cf6e8b14610152578063a9059cbb14610121578063d17c8d43146100fc5763dd62ed3e146100a8575f80fd5b346100f85760403660031901126100f8576100c1610569565b6100c961057f565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100f8575f3660031901126100f857602060ff60035460081c166040519015158152f35b346100f85760403660031901126100f857602061014861013f610569565b602435906106cf565b6040519015158152f35b346100f8575f3660031901126100f857602060ff600354166040519015158152f35b346100f8575f3660031901126100f8576040515f6001548060011c90600181168015610288575b602083108114610274578285529081156102585750600114610202575b50819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828210610242575060209150820101826101b8565b600181602092548385880101520191019061022d565b90506020925060ff191682840152151560051b820101826101b8565b634e487b7160e01b5f52602260045260245ffd5b91607f169161019b565b346100f85760203660031901126100f8576001600160a01b036102b3610569565b165f526004602052602060405f2054604051908152f35b346100f85760403660031901126100f8576102e3610569565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602060243593610317856002546106c2565b60025560018060a01b03169384845260048252604084206103398282546106c2565b9055604051908152a3005b346100f85760403660031901126100f8576004358015158091036100f8576024358015158091036100f85760ff61ff006003549260081b1692169061ffff191617176003555f80f35b346100f8575f3660031901126100f857602060405160128152f35b346100f85760603660031901126100f85760206101486103c6610569565b6103ce61057f565b604435916105b6565b346100f8575f3660031901126100f8576020600254604051908152f35b346100f85760403660031901126100f85761040d610569565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100f8575f3660031901126100f8576040515f5f548060011c90600181168015610535575b6020831081146102745782855290811561025857506001146104e15750819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061051f575060209150820101826101b8565b600181602092548385880101520191019061050a565b91607f1691610493565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f857565b602435906001600160a01b03821682036100f857565b919082039182116105a257565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c166106bb576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106665761060794846001820161060c575b5050506106ea565b600190565b61061591610595565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f80846105ff565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105a257565b9060ff600354166106e45761060791336106ea565b50505f90565b6001600160a01b03909116919082156107cc576001600160a01b03165f81815260046020526040902054909190818110610779578161074c7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef93602093610595565b845f526004835260405f2055845f526004825260405f2061076e8282546106c2565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfea2646970667358221220517a80ae0659b2c15a7986c106ff551f974681544effe73392ffc282bfd0fb1664736f6c63430008230033', + '60806040523461032157610bf38038038061001981610325565b9283398101906040818303126103215780516001600160401b038111610321578261004591830161034a565b60208201519092906001600160401b03811161032157610065920161034a565b81516001600160401b03811161022c575f54600181811c91168015610317575b602082101461020e57601f81116102aa575b50602092601f821160011461024b57928192935f92610240575b50508160011b915f199060031b1c1916175f555b80516001600160401b03811161022c57600154600181811c91168015610222575b602082101461020e57601f81116101a0575b50602091601f8211600114610140579181925f92610135575b50508160011b915f199060031b1c1916176001555b604051610857908161039c8239f35b015190505f80610111565b601f1982169260015f52805f20915f5b85811061018857508360019510610170575b505050811b01600155610126565b01515f1960f88460031b161c191690555f8080610162565b91926020600181928685015181550194019201610150565b818111156100f85760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf660208410610206575b81601f9101920160051c03905f5b8281106101f95750506100f8565b5f828201556001016101eb565b5f91506101dd565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100e6565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b1565b601f198216935f8052805f20915f5b868110610292575083600195961061027a575b505050811b015f556100c5565b01515f1960f88460031b161c191690555f808061026d565b9192602060018192868501518155019401920161025a565b81811115610097575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5636020841061030f575b81601f9101920160051c03905f5b828110610302575050610097565b5f828201556001016102f4565b5f91506102e6565b90607f1690610085565b5f80fd5b6040519190601f01601f191682016001600160401b0381118382101761022c57604052565b81601f82011215610321578051906001600160401b03821161022c57610379601f8301601f1916602001610325565b928284526020838301011161032157815f9260208093018386015e830101529056fe60806040526004361015610011575f80fd5b5f3560e01c806306fdde031461046d578063095ea7b3146103f457806318160ddd146103d757806323b872dd146103a8578063313ce5671461038d57806333ea03891461034457806340c10f19146102ca57806370a082311461029257806395d89b4114610174578063a0cf6e8b14610152578063a9059cbb14610121578063d17c8d43146100fc5763dd62ed3e146100a8575f80fd5b346100f85760403660031901126100f8576100c1610569565b6100c961057f565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100f8575f3660031901126100f857602060ff60035460081c166040519015158152f35b346100f85760403660031901126100f857602061014861013f610569565b602435906106cf565b6040519015158152f35b346100f8575f3660031901126100f857602060ff600354166040519015158152f35b346100f8575f3660031901126100f8576040515f6001548060011c90600181168015610288575b602083108114610274578285529081156102585750600114610202575b50819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828210610242575060209150820101826101b8565b600181602092548385880101520191019061022d565b90506020925060ff191682840152151560051b820101826101b8565b634e487b7160e01b5f52602260045260245ffd5b91607f169161019b565b346100f85760203660031901126100f8576001600160a01b036102b3610569565b165f526004602052602060405f2054604051908152f35b346100f85760403660031901126100f8576102e3610569565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602060243593610317856002546106c2565b60025560018060a01b03169384845260048252604084206103398282546106c2565b9055604051908152a3005b346100f85760403660031901126100f8576004358015158091036100f8576024358015158091036100f85760ff61ff006003549260081b1692169061ffff191617176003555f80f35b346100f8575f3660031901126100f857602060405160128152f35b346100f85760603660031901126100f85760206101486103c6610569565b6103ce61057f565b604435916105b6565b346100f8575f3660031901126100f8576020600254604051908152f35b346100f85760403660031901126100f85761040d610569565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100f8575f3660031901126100f8576040515f5f548060011c90600181168015610535575b6020831081146102745782855290811561025857506001146104e15750819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061051f575060209150820101826101b8565b600181602092548385880101520191019061050a565b91607f1691610493565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f857565b602435906001600160a01b03821682036100f857565b919082039182116105a257565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c166106bb576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106665761060794846001820161060c575b5050506106ea565b600190565b61061591610595565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f80846105ff565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105a257565b9060ff600354166106e45761060791336106ea565b50505f90565b6001600160a01b03909116919082156107cc576001600160a01b03165f81815260046020526040902054909190818110610779578161074c7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef93602093610595565b845f526004835260405f2055845f526004825260405f2061076e8282546106c2565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfea264697066735822122073e44ae8363f1a3efb3789cb269065efcded49f8eadfe772140e8e465d342c9d64736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610011575f80fd5b5f3560e01c806306fdde031461046d578063095ea7b3146103f457806318160ddd146103d757806323b872dd146103a8578063313ce5671461038d57806333ea03891461034457806340c10f19146102ca57806370a082311461029257806395d89b4114610174578063a0cf6e8b14610152578063a9059cbb14610121578063d17c8d43146100fc5763dd62ed3e146100a8575f80fd5b346100f85760403660031901126100f8576100c1610569565b6100c961057f565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100f8575f3660031901126100f857602060ff60035460081c166040519015158152f35b346100f85760403660031901126100f857602061014861013f610569565b602435906106cf565b6040519015158152f35b346100f8575f3660031901126100f857602060ff600354166040519015158152f35b346100f8575f3660031901126100f8576040515f6001548060011c90600181168015610288575b602083108114610274578285529081156102585750600114610202575b50819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828210610242575060209150820101826101b8565b600181602092548385880101520191019061022d565b90506020925060ff191682840152151560051b820101826101b8565b634e487b7160e01b5f52602260045260245ffd5b91607f169161019b565b346100f85760203660031901126100f8576001600160a01b036102b3610569565b165f526004602052602060405f2054604051908152f35b346100f85760403660031901126100f8576102e3610569565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602060243593610317856002546106c2565b60025560018060a01b03169384845260048252604084206103398282546106c2565b9055604051908152a3005b346100f85760403660031901126100f8576004358015158091036100f8576024358015158091036100f85760ff61ff006003549260081b1692169061ffff191617176003555f80f35b346100f8575f3660031901126100f857602060405160128152f35b346100f85760603660031901126100f85760206101486103c6610569565b6103ce61057f565b604435916105b6565b346100f8575f3660031901126100f8576020600254604051908152f35b346100f85760403660031901126100f85761040d610569565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100f8575f3660031901126100f8576040515f5f548060011c90600181168015610535575b6020831081146102745782855290811561025857506001146104e15750819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061051f575060209150820101826101b8565b600181602092548385880101520191019061050a565b91607f1691610493565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f857565b602435906001600160a01b03821682036100f857565b919082039182116105a257565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c166106bb576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106665761060794846001820161060c575b5050506106ea565b600190565b61061591610595565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f80846105ff565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105a257565b9060ff600354166106e45761060791336106ea565b50505f90565b6001600160a01b03909116919082156107cc576001600160a01b03165f81815260046020526040902054909190818110610779578161074c7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef93602093610595565b845f526004835260405f2055845f526004825260405f2061076e8282546106c2565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfea2646970667358221220517a80ae0659b2c15a7986c106ff551f974681544effe73392ffc282bfd0fb1664736f6c63430008230033', + '60806040526004361015610011575f80fd5b5f3560e01c806306fdde031461046d578063095ea7b3146103f457806318160ddd146103d757806323b872dd146103a8578063313ce5671461038d57806333ea03891461034457806340c10f19146102ca57806370a082311461029257806395d89b4114610174578063a0cf6e8b14610152578063a9059cbb14610121578063d17c8d43146100fc5763dd62ed3e146100a8575f80fd5b346100f85760403660031901126100f8576100c1610569565b6100c961057f565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b346100f8575f3660031901126100f857602060ff60035460081c166040519015158152f35b346100f85760403660031901126100f857602061014861013f610569565b602435906106cf565b6040519015158152f35b346100f8575f3660031901126100f857602060ff600354166040519015158152f35b346100f8575f3660031901126100f8576040515f6001548060011c90600181168015610288575b602083108114610274578285529081156102585750600114610202575b50819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b0390f35b634e487b7160e01b5f52604160045260245ffd5b60015f9081529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828210610242575060209150820101826101b8565b600181602092548385880101520191019061022d565b90506020925060ff191682840152151560051b820101826101b8565b634e487b7160e01b5f52602260045260245ffd5b91607f169161019b565b346100f85760203660031901126100f8576001600160a01b036102b3610569565b165f526004602052602060405f2054604051908152f35b346100f85760403660031901126100f8576102e3610569565b5f7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef602060243593610317856002546106c2565b60025560018060a01b03169384845260048252604084206103398282546106c2565b9055604051908152a3005b346100f85760403660031901126100f8576004358015158091036100f8576024358015158091036100f85760ff61ff006003549260081b1692169061ffff191617176003555f80f35b346100f8575f3660031901126100f857602060405160128152f35b346100f85760603660031901126100f85760206101486103c6610569565b6103ce61057f565b604435916105b6565b346100f8575f3660031901126100f8576020600254604051908152f35b346100f85760403660031901126100f85761040d610569565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b346100f8575f3660031901126100f8576040515f5f548060011c90600181168015610535575b6020831081146102745782855290811561025857506001146104e15750819003601f01601f191681019067ffffffffffffffff8211818310176101ee57604082905281906101ea908261053f565b5f8080529091507f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b82821061051f575060209150820101826101b8565b600181602092548385880101520191019061050a565b91607f1691610493565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f857565b602435906001600160a01b03821682036100f857565b919082039182116105a257565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c166106bb576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106106665761060794846001820161060c575b5050506106ea565b600190565b61061591610595565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f80846105ff565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b919082018092116105a257565b9060ff600354166106e45761060791336106ea565b50505f90565b6001600160a01b03909116919082156107cc576001600160a01b03165f81815260046020526040902054909190818110610779578161074c7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef93602093610595565b845f526004835260405f2055845f526004825260405f2061076e8282546106c2565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfea264697066735822122073e44ae8363f1a3efb3789cb269065efcded49f8eadfe772140e8e465d342c9d64736f6c63430008230033', }, }, } as const @@ -2512,11 +2512,11 @@ export const v4PoolManagerArtifact = { evm: { bytecode: { object: - '60808060405234601557610d93908161001a8239f35b5f80fdfe6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c80630b0d9c09146107af57806311da60b41461079257806348c894911461051d57806380b68aab14610378578063816def3a1461032e578063a5841194146102675763f3cd914c0361000e57346101f257366003190161012081126101f25760a0136101f25760603660a31901126101f2576101043567ffffffffffffffff81116101f2576100b29036906004016109f1565b90506100c860018060a01b035f54163314610a49565b6004356001600160a01b038116908181036101f257501580610249575b156101f6576084356001600160a01b038116908181036101f257501590816101e9575b50156101a45760c4355f81131561017b576020905b600f0b610128610d2e565b156101755761013681610d3d565b905b610140610d2e565b1561016657905b6fffffffffffffffffffffffffffffffff60405192169060801b178152f35b61016f90610d3d565b90610147565b80610138565b600160ff1b8114610190576020905f0361011d565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520563420686f6f6b7320756e737570706f72746564006044820152606490fd5b9050155f610108565b5f80fd5b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c6520563420706f6f6c2063757272656e6369657320696e6044820152641d985b1a5960da1b6064820152608490fd5b506024356001600160a01b038116908181036101f2575015156100e5565b346101f25760203660031901126101f2576102806109db565b61029460018060a01b035f54163314610a49565b600180546001600160a01b0319166001600160a01b03929092169182179055806102bf575047600255005b6020602491604051928380926370a0823160e01b82523060048301525afa908115610323575f916102f1575b50600255005b90506020813d60201161031b575b8161030c60209383610a95565b810103126101f25751816102eb565b3d91506102ff565b6040513d5f823e3d90fd5b346101f25760203660031901126101f2576001600160a01b0361034f6109db565b16806bffffffffffffffffffffffff60a01b600154161760015580155f146102bf575047600255005b346101f25760403660031901126101f25760043567ffffffffffffffff81116101f2576103a99036906004016109f1565b602435908115158092036101f25767ffffffffffffffff8111610509576103d1600354610c93565b601f811161049f575b505f601f8211600114610423578192935f92610418575b50508160011b915f199060031b1c1916176003555b60ff8019600454169116176004555f80f35b0135905083806103f1565b601f198216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610487575083600195961061046e575b505050811b01600355610406565b01355f19600384901b60f8161c19169055838080610460565b9092602060018192868601358155019401910161044d565b818111156103da57601f820160051c9060208310610501575b601f82910160051c03905f5b8281106104d25750506103da565b5f8282017fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01556001016104c4565b5f91506104b8565b634e487b7160e01b5f52604160045260245ffd5b346101f25760203660031901126101f25760043567ffffffffffffffff81116101f25761054e9036906004016109f1565b5f546001600160a01b03811661073e576001600160a01b03191633175f5561057581610ab7565b916105836040519384610a95565b81835236828201116101f257815f9260209283860137830101526003546105a981610c93565b610686575b506040516348eeb9a360e11b81525f81806105cc8560048301610a1f565b038183335af1908115610323575f9161066c575b5060ff6004541661060f575b5f80546001600160a01b031916905560405190819061060b9082610a1f565b0390f35b5f61062e92604051809481926348eeb9a360e11b835260048301610a1f565b038183335af19182156103235761060b9261064a575b506105ec565b610665903d805f833e61065d8183610a95565b810190610ccb565b5082610644565b61068091503d805f833e61065d8183610a95565b826105e0565b6040519150815f61069683610c93565b808352926001811690811561071f57506001146106c0575b6106ba92500382610a95565b816105ae565b509060035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b905f915b8183106107035750509060206106ba928201016106ae565b60209193508060019154838588010152019101909183926106eb565b602092506106ba94915060ff191682840152151560051b8201016106ae565b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c65205634206d616e6167657220616c726561647920756e6044820152651b1bd8dad95960d21b6064820152608490fd5b5f3660031901126101f25760206107a7610b0f565b604051908152f35b346101f25760603660031901126101f2576107c86109db565b6024356001600160a01b03811691908290036101f257604435906107f660018060a01b035f54163314610a49565b6001600160a01b03168061086257505f8080939281935af1610816610ad3565b501561081e57005b606460405162461bcd60e51b815260206004820152602060248201527f4f70656e4f7261636c65205634206e61746976652074616b65206661696c65646044820152fd5b60405191602083019363a9059cbb60e01b8552602484015260448301526044825261088e606483610a95565b803b15610978575f9283809351925af16108a6610ad3565b9015610934578051806108b557005b81602091810103126101f257602001518015908115036101f2576108d557005b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b600435906001600160a01b03821682036101f257565b9181601f840112156101f25782359167ffffffffffffffff83116101f257602083818601950101116101f257565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b15610a5057565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c65205634206d616e61676572206973206c6f636b6564006044820152606490fd5b90601f8019910116810190811067ffffffffffffffff82111761050957604052565b67ffffffffffffffff811161050957601f01601f191660200190565b3d15610afd573d90610ae482610ab7565b91610af26040519384610a95565b82523d5f602084013e565b606090565b9190820391821161019057565b610b2360018060a01b035f54163314610a49565b600154906001600160a01b0382168015610c205734610bbe576020602491604051928380926370a0823160e01b82523060048301525afa8015610323575f90610b8a575b610b75915060025490610b02565b6001600160a01b03199092166001555f600255565b506020813d602011610bb6575b81610ba460209383610a95565b810103126101f257610b759051610b67565b3d9150610b97565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c6520563420746f6b656e20736574746c656d656e74207260448201527365636569766564206e61746976652076616c756560601b6064820152608490fd5b5090503415610c3d57610c364760025490610b02565b5f60025590565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c65205634206e617469766520736574746c656d656e7420604482015267697320656d70747960c01b6064820152608490fd5b90600182811c92168015610cc1575b6020831014610cad57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610ca2565b6020818303126101f25780519067ffffffffffffffff82116101f2570181601f820112156101f257805190610cff82610ab7565b92610d0d6040519485610a95565b828452602083830101116101f257815f9260208093018386015e8301015290565b60a43580151581036101f25790565b600f0b6f7fffffffffffffffffffffffffffffff198114610190575f039056fea2646970667358221220d842263234440c10a7ec74ae5ac54c8b477870ed7b78532f356b798241b680f264736f6c63430008230033', + '60808060405234601557610d93908161001a8239f35b5f80fdfe6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c80630b0d9c09146107af57806311da60b41461079257806348c894911461051d57806380b68aab14610378578063816def3a1461032e578063a5841194146102675763f3cd914c0361000e57346101f257366003190161012081126101f25760a0136101f25760603660a31901126101f2576101043567ffffffffffffffff81116101f2576100b29036906004016109f1565b90506100c860018060a01b035f54163314610a49565b6004356001600160a01b038116908181036101f257501580610249575b156101f6576084356001600160a01b038116908181036101f257501590816101e9575b50156101a45760c4355f81131561017b576020905b600f0b610128610d2e565b156101755761013681610d3d565b905b610140610d2e565b1561016657905b6fffffffffffffffffffffffffffffffff60405192169060801b178152f35b61016f90610d3d565b90610147565b80610138565b600160ff1b8114610190576020905f0361011d565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520563420686f6f6b7320756e737570706f72746564006044820152606490fd5b9050155f610108565b5f80fd5b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c6520563420706f6f6c2063757272656e6369657320696e6044820152641d985b1a5960da1b6064820152608490fd5b506024356001600160a01b038116908181036101f2575015156100e5565b346101f25760203660031901126101f2576102806109db565b61029460018060a01b035f54163314610a49565b600180546001600160a01b0319166001600160a01b03929092169182179055806102bf575047600255005b6020602491604051928380926370a0823160e01b82523060048301525afa908115610323575f916102f1575b50600255005b90506020813d60201161031b575b8161030c60209383610a95565b810103126101f25751816102eb565b3d91506102ff565b6040513d5f823e3d90fd5b346101f25760203660031901126101f2576001600160a01b0361034f6109db565b16806bffffffffffffffffffffffff60a01b600154161760015580155f146102bf575047600255005b346101f25760403660031901126101f25760043567ffffffffffffffff81116101f2576103a99036906004016109f1565b602435908115158092036101f25767ffffffffffffffff8111610509576103d1600354610c93565b601f811161049f575b505f601f8211600114610423578192935f92610418575b50508160011b915f199060031b1c1916176003555b60ff8019600454169116176004555f80f35b0135905083806103f1565b601f198216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610487575083600195961061046e575b505050811b01600355610406565b01355f19600384901b60f8161c19169055838080610460565b9092602060018192868601358155019401910161044d565b818111156103da57601f820160051c9060208310610501575b601f82910160051c03905f5b8281106104d25750506103da565b5f8282017fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01556001016104c4565b5f91506104b8565b634e487b7160e01b5f52604160045260245ffd5b346101f25760203660031901126101f25760043567ffffffffffffffff81116101f25761054e9036906004016109f1565b5f546001600160a01b03811661073e576001600160a01b03191633175f5561057581610ab7565b916105836040519384610a95565b81835236828201116101f257815f9260209283860137830101526003546105a981610c93565b610686575b506040516348eeb9a360e11b81525f81806105cc8560048301610a1f565b038183335af1908115610323575f9161066c575b5060ff6004541661060f575b5f80546001600160a01b031916905560405190819061060b9082610a1f565b0390f35b5f61062e92604051809481926348eeb9a360e11b835260048301610a1f565b038183335af19182156103235761060b9261064a575b506105ec565b610665903d805f833e61065d8183610a95565b810190610ccb565b5082610644565b61068091503d805f833e61065d8183610a95565b826105e0565b6040519150815f61069683610c93565b808352926001811690811561071f57506001146106c0575b6106ba92500382610a95565b816105ae565b509060035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b905f915b8183106107035750509060206106ba928201016106ae565b60209193508060019154838588010152019101909183926106eb565b602092506106ba94915060ff191682840152151560051b8201016106ae565b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c65205634206d616e6167657220616c726561647920756e6044820152651b1bd8dad95960d21b6064820152608490fd5b5f3660031901126101f25760206107a7610b0f565b604051908152f35b346101f25760603660031901126101f2576107c86109db565b6024356001600160a01b03811691908290036101f257604435906107f660018060a01b035f54163314610a49565b6001600160a01b03168061086257505f8080939281935af1610816610ad3565b501561081e57005b606460405162461bcd60e51b815260206004820152602060248201527f4f70656e4f7261636c65205634206e61746976652074616b65206661696c65646044820152fd5b60405191602083019363a9059cbb60e01b8552602484015260448301526044825261088e606483610a95565b803b15610978575f9283809351925af16108a6610ad3565b9015610934578051806108b557005b81602091810103126101f257602001518015908115036101f2576108d557005b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b600435906001600160a01b03821682036101f257565b9181601f840112156101f25782359167ffffffffffffffff83116101f257602083818601950101116101f257565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b15610a5057565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c65205634206d616e61676572206973206c6f636b6564006044820152606490fd5b90601f8019910116810190811067ffffffffffffffff82111761050957604052565b67ffffffffffffffff811161050957601f01601f191660200190565b3d15610afd573d90610ae482610ab7565b91610af26040519384610a95565b82523d5f602084013e565b606090565b9190820391821161019057565b610b2360018060a01b035f54163314610a49565b600154906001600160a01b0382168015610c205734610bbe576020602491604051928380926370a0823160e01b82523060048301525afa8015610323575f90610b8a575b610b75915060025490610b02565b6001600160a01b03199092166001555f600255565b506020813d602011610bb6575b81610ba460209383610a95565b810103126101f257610b759051610b67565b3d9150610b97565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c6520563420746f6b656e20736574746c656d656e74207260448201527365636569766564206e61746976652076616c756560601b6064820152608490fd5b5090503415610c3d57610c364760025490610b02565b5f60025590565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c65205634206e617469766520736574746c656d656e7420604482015267697320656d70747960c01b6064820152608490fd5b90600182811c92168015610cc1575b6020831014610cad57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610ca2565b6020818303126101f25780519067ffffffffffffffff82116101f2570181601f820112156101f257805190610cff82610ab7565b92610d0d6040519485610a95565b828452602083830101116101f257815f9260208093018386015e8301015290565b60a43580151581036101f25790565b600f0b6f7fffffffffffffffffffffffffffffff198114610190575f039056fea2646970667358221220f62cad6d9bcaadbaa0c7dbe8d66c384d64c936c744439784423091f18a8ba22564736f6c63430008230033', }, deployedBytecode: { object: - '6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c80630b0d9c09146107af57806311da60b41461079257806348c894911461051d57806380b68aab14610378578063816def3a1461032e578063a5841194146102675763f3cd914c0361000e57346101f257366003190161012081126101f25760a0136101f25760603660a31901126101f2576101043567ffffffffffffffff81116101f2576100b29036906004016109f1565b90506100c860018060a01b035f54163314610a49565b6004356001600160a01b038116908181036101f257501580610249575b156101f6576084356001600160a01b038116908181036101f257501590816101e9575b50156101a45760c4355f81131561017b576020905b600f0b610128610d2e565b156101755761013681610d3d565b905b610140610d2e565b1561016657905b6fffffffffffffffffffffffffffffffff60405192169060801b178152f35b61016f90610d3d565b90610147565b80610138565b600160ff1b8114610190576020905f0361011d565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520563420686f6f6b7320756e737570706f72746564006044820152606490fd5b9050155f610108565b5f80fd5b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c6520563420706f6f6c2063757272656e6369657320696e6044820152641d985b1a5960da1b6064820152608490fd5b506024356001600160a01b038116908181036101f2575015156100e5565b346101f25760203660031901126101f2576102806109db565b61029460018060a01b035f54163314610a49565b600180546001600160a01b0319166001600160a01b03929092169182179055806102bf575047600255005b6020602491604051928380926370a0823160e01b82523060048301525afa908115610323575f916102f1575b50600255005b90506020813d60201161031b575b8161030c60209383610a95565b810103126101f25751816102eb565b3d91506102ff565b6040513d5f823e3d90fd5b346101f25760203660031901126101f2576001600160a01b0361034f6109db565b16806bffffffffffffffffffffffff60a01b600154161760015580155f146102bf575047600255005b346101f25760403660031901126101f25760043567ffffffffffffffff81116101f2576103a99036906004016109f1565b602435908115158092036101f25767ffffffffffffffff8111610509576103d1600354610c93565b601f811161049f575b505f601f8211600114610423578192935f92610418575b50508160011b915f199060031b1c1916176003555b60ff8019600454169116176004555f80f35b0135905083806103f1565b601f198216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610487575083600195961061046e575b505050811b01600355610406565b01355f19600384901b60f8161c19169055838080610460565b9092602060018192868601358155019401910161044d565b818111156103da57601f820160051c9060208310610501575b601f82910160051c03905f5b8281106104d25750506103da565b5f8282017fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01556001016104c4565b5f91506104b8565b634e487b7160e01b5f52604160045260245ffd5b346101f25760203660031901126101f25760043567ffffffffffffffff81116101f25761054e9036906004016109f1565b5f546001600160a01b03811661073e576001600160a01b03191633175f5561057581610ab7565b916105836040519384610a95565b81835236828201116101f257815f9260209283860137830101526003546105a981610c93565b610686575b506040516348eeb9a360e11b81525f81806105cc8560048301610a1f565b038183335af1908115610323575f9161066c575b5060ff6004541661060f575b5f80546001600160a01b031916905560405190819061060b9082610a1f565b0390f35b5f61062e92604051809481926348eeb9a360e11b835260048301610a1f565b038183335af19182156103235761060b9261064a575b506105ec565b610665903d805f833e61065d8183610a95565b810190610ccb565b5082610644565b61068091503d805f833e61065d8183610a95565b826105e0565b6040519150815f61069683610c93565b808352926001811690811561071f57506001146106c0575b6106ba92500382610a95565b816105ae565b509060035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b905f915b8183106107035750509060206106ba928201016106ae565b60209193508060019154838588010152019101909183926106eb565b602092506106ba94915060ff191682840152151560051b8201016106ae565b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c65205634206d616e6167657220616c726561647920756e6044820152651b1bd8dad95960d21b6064820152608490fd5b5f3660031901126101f25760206107a7610b0f565b604051908152f35b346101f25760603660031901126101f2576107c86109db565b6024356001600160a01b03811691908290036101f257604435906107f660018060a01b035f54163314610a49565b6001600160a01b03168061086257505f8080939281935af1610816610ad3565b501561081e57005b606460405162461bcd60e51b815260206004820152602060248201527f4f70656e4f7261636c65205634206e61746976652074616b65206661696c65646044820152fd5b60405191602083019363a9059cbb60e01b8552602484015260448301526044825261088e606483610a95565b803b15610978575f9283809351925af16108a6610ad3565b9015610934578051806108b557005b81602091810103126101f257602001518015908115036101f2576108d557005b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b600435906001600160a01b03821682036101f257565b9181601f840112156101f25782359167ffffffffffffffff83116101f257602083818601950101116101f257565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b15610a5057565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c65205634206d616e61676572206973206c6f636b6564006044820152606490fd5b90601f8019910116810190811067ffffffffffffffff82111761050957604052565b67ffffffffffffffff811161050957601f01601f191660200190565b3d15610afd573d90610ae482610ab7565b91610af26040519384610a95565b82523d5f602084013e565b606090565b9190820391821161019057565b610b2360018060a01b035f54163314610a49565b600154906001600160a01b0382168015610c205734610bbe576020602491604051928380926370a0823160e01b82523060048301525afa8015610323575f90610b8a575b610b75915060025490610b02565b6001600160a01b03199092166001555f600255565b506020813d602011610bb6575b81610ba460209383610a95565b810103126101f257610b759051610b67565b3d9150610b97565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c6520563420746f6b656e20736574746c656d656e74207260448201527365636569766564206e61746976652076616c756560601b6064820152608490fd5b5090503415610c3d57610c364760025490610b02565b5f60025590565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c65205634206e617469766520736574746c656d656e7420604482015267697320656d70747960c01b6064820152608490fd5b90600182811c92168015610cc1575b6020831014610cad57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610ca2565b6020818303126101f25780519067ffffffffffffffff82116101f2570181601f820112156101f257805190610cff82610ab7565b92610d0d6040519485610a95565b828452602083830101116101f257815f9260208093018386015e8301015290565b60a43580151581036101f25790565b600f0b6f7fffffffffffffffffffffffffffffff198114610190575f039056fea2646970667358221220d842263234440c10a7ec74ae5ac54c8b477870ed7b78532f356b798241b680f264736f6c63430008230033', + '6080604052600436101561001a575b3615610018575f80fd5b005b5f3560e01c80630b0d9c09146107af57806311da60b41461079257806348c894911461051d57806380b68aab14610378578063816def3a1461032e578063a5841194146102675763f3cd914c0361000e57346101f257366003190161012081126101f25760a0136101f25760603660a31901126101f2576101043567ffffffffffffffff81116101f2576100b29036906004016109f1565b90506100c860018060a01b035f54163314610a49565b6004356001600160a01b038116908181036101f257501580610249575b156101f6576084356001600160a01b038116908181036101f257501590816101e9575b50156101a45760c4355f81131561017b576020905b600f0b610128610d2e565b156101755761013681610d3d565b905b610140610d2e565b1561016657905b6fffffffffffffffffffffffffffffffff60405192169060801b178152f35b61016f90610d3d565b90610147565b80610138565b600160ff1b8114610190576020905f0361011d565b634e487b7160e01b5f52601160045260245ffd5b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520563420686f6f6b7320756e737570706f72746564006044820152606490fd5b9050155f610108565b5f80fd5b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c6520563420706f6f6c2063757272656e6369657320696e6044820152641d985b1a5960da1b6064820152608490fd5b506024356001600160a01b038116908181036101f2575015156100e5565b346101f25760203660031901126101f2576102806109db565b61029460018060a01b035f54163314610a49565b600180546001600160a01b0319166001600160a01b03929092169182179055806102bf575047600255005b6020602491604051928380926370a0823160e01b82523060048301525afa908115610323575f916102f1575b50600255005b90506020813d60201161031b575b8161030c60209383610a95565b810103126101f25751816102eb565b3d91506102ff565b6040513d5f823e3d90fd5b346101f25760203660031901126101f2576001600160a01b0361034f6109db565b16806bffffffffffffffffffffffff60a01b600154161760015580155f146102bf575047600255005b346101f25760403660031901126101f25760043567ffffffffffffffff81116101f2576103a99036906004016109f1565b602435908115158092036101f25767ffffffffffffffff8111610509576103d1600354610c93565b601f811161049f575b505f601f8211600114610423578192935f92610418575b50508160011b915f199060031b1c1916176003555b60ff8019600454169116176004555f80f35b0135905083806103f1565b601f198216937fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b915f5b868110610487575083600195961061046e575b505050811b01600355610406565b01355f19600384901b60f8161c19169055838080610460565b9092602060018192868601358155019401910161044d565b818111156103da57601f820160051c9060208310610501575b601f82910160051c03905f5b8281106104d25750506103da565b5f8282017fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01556001016104c4565b5f91506104b8565b634e487b7160e01b5f52604160045260245ffd5b346101f25760203660031901126101f25760043567ffffffffffffffff81116101f25761054e9036906004016109f1565b5f546001600160a01b03811661073e576001600160a01b03191633175f5561057581610ab7565b916105836040519384610a95565b81835236828201116101f257815f9260209283860137830101526003546105a981610c93565b610686575b506040516348eeb9a360e11b81525f81806105cc8560048301610a1f565b038183335af1908115610323575f9161066c575b5060ff6004541661060f575b5f80546001600160a01b031916905560405190819061060b9082610a1f565b0390f35b5f61062e92604051809481926348eeb9a360e11b835260048301610a1f565b038183335af19182156103235761060b9261064a575b506105ec565b610665903d805f833e61065d8183610a95565b810190610ccb565b5082610644565b61068091503d805f833e61065d8183610a95565b826105e0565b6040519150815f61069683610c93565b808352926001811690811561071f57506001146106c0575b6106ba92500382610a95565b816105ae565b509060035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b905f915b8183106107035750509060206106ba928201016106ae565b60209193508060019154838588010152019101909183926106eb565b602092506106ba94915060ff191682840152151560051b8201016106ae565b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c65205634206d616e6167657220616c726561647920756e6044820152651b1bd8dad95960d21b6064820152608490fd5b5f3660031901126101f25760206107a7610b0f565b604051908152f35b346101f25760603660031901126101f2576107c86109db565b6024356001600160a01b03811691908290036101f257604435906107f660018060a01b035f54163314610a49565b6001600160a01b03168061086257505f8080939281935af1610816610ad3565b501561081e57005b606460405162461bcd60e51b815260206004820152602060248201527f4f70656e4f7261636c65205634206e61746976652074616b65206661696c65646044820152fd5b60405191602083019363a9059cbb60e01b8552602484015260448301526044825261088e606483610a95565b803b15610978575f9283809351925af16108a6610ad3565b9015610934578051806108b557005b81602091810103126101f257602001518015908115036101f2576108d557005b60405162461bcd60e51b815260206004820152603160248201527f5361666545524332304f707320746f6b656e2072657475726e65642066616c736044820152701948199c9bdb48115490cc8c0818d85b1b607a1b6064820152608490fd5b606460405162461bcd60e51b815260206004820152602060248201527f5361666545524332304f707320746f6b656e2063616c6c2072657665727465646044820152fd5b60405162461bcd60e51b815260206004820152603560248201527f5361666545524332304f707320746f6b656e2061646472657373206d75737420604482015274636f6e7461696e20636f6e747261637420636f646560581b6064820152608490fd5b600435906001600160a01b03821682036101f257565b9181601f840112156101f25782359167ffffffffffffffff83116101f257602083818601950101116101f257565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b15610a5057565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c65205634206d616e61676572206973206c6f636b6564006044820152606490fd5b90601f8019910116810190811067ffffffffffffffff82111761050957604052565b67ffffffffffffffff811161050957601f01601f191660200190565b3d15610afd573d90610ae482610ab7565b91610af26040519384610a95565b82523d5f602084013e565b606090565b9190820391821161019057565b610b2360018060a01b035f54163314610a49565b600154906001600160a01b0382168015610c205734610bbe576020602491604051928380926370a0823160e01b82523060048301525afa8015610323575f90610b8a575b610b75915060025490610b02565b6001600160a01b03199092166001555f600255565b506020813d602011610bb6575b81610ba460209383610a95565b810103126101f257610b759051610b67565b3d9150610b97565b60405162461bcd60e51b815260206004820152603460248201527f4f70656e4f7261636c6520563420746f6b656e20736574746c656d656e74207260448201527365636569766564206e61746976652076616c756560601b6064820152608490fd5b5090503415610c3d57610c364760025490610b02565b5f60025590565b60405162461bcd60e51b815260206004820152602860248201527f4f70656e4f7261636c65205634206e617469766520736574746c656d656e7420604482015267697320656d70747960c01b6064820152608490fd5b90600182811c92168015610cc1575b6020831014610cad57565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610ca2565b6020818303126101f25780519067ffffffffffffffff82116101f2570181601f820112156101f257805190610cff82610ab7565b92610d0d6040519485610a95565b828452602083830101116101f257815f9260208093018386015e8301015290565b60a43580151581036101f25790565b600f0b6f7fffffffffffffffffffffffffffffff198114610190575f039056fea2646970667358221220f62cad6d9bcaadbaa0c7dbe8d66c384d64c936c744439784423091f18a8ba22564736f6c63430008230033', }, }, } as const @@ -2840,11 +2840,11 @@ export const wethArtifact = { evm: { bytecode: { object: - '60806040523461032857604080519081016001600160401b03811182821017610233576040908152600d82526c2bb930b83832b21022ba3432b960991b602083015280519081016001600160401b038111828210176102335760405260048152630ae8aa8960e31b602082015281516001600160401b038111610233575f54600181811c9116801561031e575b602082101461021557601f81116102b1575b50602092601f821160011461025257928192935f92610247575b50508160011b915f199060031b1c1916175f555b80516001600160401b03811161023357600154600181811c91168015610229575b602082101461021557601f81116101a7575b50602091601f8211600114610147579181925f9261013c575b50508160011b915f199060031b1c1916176001555b604051610a1a908161032d8239f35b015190505f80610118565b601f1982169260015f52805f20915f5b85811061018f57508360019510610177575b505050811b0160015561012d565b01515f1960f88460031b161c191690555f8080610169565b91926020600181928685015181550194019201610157565b818111156100ff5760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66020841061020d575b81601f9101920160051c03905f5b8281106102005750506100ff565b5f828201556001016101f2565b5f91506101e4565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100ed565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b8565b601f198216935f8052805f20915f5b8681106102995750836001959610610281575b505050811b015f556100cc565b01515f1960f88460031b161c191690555f8080610274565b91926020600181928685015181550194019201610261565b8181111561009e575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360208410610316575b81601f9101920160051c03905f5b82811061030957505061009e565b5f828201556001016102fb565b5f91506102ed565b90607f169061008c565b5f80fdfe60806040526004361015610022575b3615610018575f80fd5b61002061085c565b005b5f3560e01c806306fdde03146105d7578063095ea7b31461055e57806318160ddd1461054157806323b872dd146105125780632e1a7d4d146103b1578063313ce5671461039657806333ea03891461034d57806340c10f19146102e657806370a08231146102ae57806395d89b41146101aa578063a0cf6e8b14610188578063a9059cbb14610157578063d0e30db014610144578063d17c8d431461011f5763dd62ed3e0361000e573461011b57604036600319011261011b576100e46106db565b6100ec6106f1565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b3461011b575f36600319011261011b57602060ff60035460081c166040519015158152f35b5f36600319011261011b5761002061085c565b3461011b57604036600319011261011b57602061017e6101756106db565b60243590610841565b6040519015158152f35b3461011b575f36600319011261011b57602060ff600354166040519015158152f35b3461011b575f36600319011261011b576040515f6001548060011c906001811680156102a4575b6020831081146102905782855290811561026c575060011461020e575b61020a836101fe8185038261068f565b604051918291826106b1565b0390f35b60015f9081527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6939250905b808210610252575090915081016020016101fe6101ee565b91926001816020925483858801015201910190929161023a565b60ff191660208086019190915291151560051b840190910191506101fe90506101ee565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101d1565b3461011b57602036600319011261011b576001600160a01b036102cf6106db565b165f526004602052602060405f2054604051908152f35b3461011b57604036600319011261011b576102ff6106db565b5f5f5160206109c55f395f51905f5260206024359361032085600254610834565b60025560018060a01b0316938484526004825260408420610342828254610834565b9055604051908152a3005b3461011b57604036600319011261011b5760043580151580910361011b5760243580151580910361011b5760ff61ff006003549260081b1692169061ffff191617176003555f80f35b3461011b575f36600319011261011b57602060405160128152f35b3461011b57602036600319011261011b57600435335f52600460205260405f20548181106104cd575f80836103e882958395610707565b3383526004602052604083205561040181600254610707565b600255816040518281525f5160206109c55f395f51905f5260203392a3335af13d156104c8573d67ffffffffffffffff81116104b4576040519061044f601f8201601f19166020018361068f565b81525f60203d92013e5b1561046057005b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652057455448206e6174697665207472616e736665722060448201526519985a5b195960d21b6064820152608490fd5b634e487b7160e01b5f52604160045260245ffd5b610459565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520574554482062616c616e636520746f6f206c6f77006044820152606490fd5b3461011b57606036600319011261011b57602061017e6105306106db565b6105386106f1565b60443591610728565b3461011b575f36600319011261011b576020600254604051908152f35b3461011b57604036600319011261011b576105776106db565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b3461011b575f36600319011261011b576040515f5f548060011c90600181168015610685575b6020831081146102905782855290811561026c57506001146106295761020a836101fe8185038261068f565b5f8080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563939250905b80821061066b575090915081016020016101fe6101ee565b919260018160209254838588010152019101909291610653565b91607f16916105fd565b90601f8019910116810190811067ffffffffffffffff8211176104b457604052565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361011b57565b602435906001600160a01b038216820361011b57565b9190820391821161071457565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c1661082d576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106107d85761077994846001820161077e575b5050506108a0565b600190565b61078791610707565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610771565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b9190820180921161071457565b9060ff600354166108565761077991336108a0565b50505f90565b61086834600254610834565b600255335f52600460205260405f20610882348254610834565b90556040513481525f5f5160206109c55f395f51905f5260203393a3565b6001600160a01b039091169190821561096f576001600160a01b03165f8181526004602052604090205490919081811061091c57816108ef5f5160206109c55f395f51905f5293602093610707565b845f526004835260405f2055845f526004825260405f20610911828254610834565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220853891adc1e7d9992426dbd3a6e5975e4a26dc87e17d7e455cfe7dff481f7a6564736f6c63430008230033', + '60806040523461032857604080519081016001600160401b03811182821017610233576040908152600d82526c2bb930b83832b21022ba3432b960991b602083015280519081016001600160401b038111828210176102335760405260048152630ae8aa8960e31b602082015281516001600160401b038111610233575f54600181811c9116801561031e575b602082101461021557601f81116102b1575b50602092601f821160011461025257928192935f92610247575b50508160011b915f199060031b1c1916175f555b80516001600160401b03811161023357600154600181811c91168015610229575b602082101461021557601f81116101a7575b50602091601f8211600114610147579181925f9261013c575b50508160011b915f199060031b1c1916176001555b604051610a1a908161032d8239f35b015190505f80610118565b601f1982169260015f52805f20915f5b85811061018f57508360019510610177575b505050811b0160015561012d565b01515f1960f88460031b161c191690555f8080610169565b91926020600181928685015181550194019201610157565b818111156100ff5760015f52601f820160051c7fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf66020841061020d575b81601f9101920160051c03905f5b8281106102005750506100ff565b5f828201556001016101f2565b5f91506101e4565b634e487b7160e01b5f52602260045260245ffd5b90607f16906100ed565b634e487b7160e01b5f52604160045260245ffd5b015190505f806100b8565b601f198216935f8052805f20915f5b8681106102995750836001959610610281575b505050811b015f556100cc565b01515f1960f88460031b161c191690555f8080610274565b91926020600181928685015181550194019201610261565b8181111561009e575f8052601f820160051c7f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56360208410610316575b81601f9101920160051c03905f5b82811061030957505061009e565b5f828201556001016102fb565b5f91506102ed565b90607f169061008c565b5f80fdfe60806040526004361015610022575b3615610018575f80fd5b61002061085c565b005b5f3560e01c806306fdde03146105d7578063095ea7b31461055e57806318160ddd1461054157806323b872dd146105125780632e1a7d4d146103b1578063313ce5671461039657806333ea03891461034d57806340c10f19146102e657806370a08231146102ae57806395d89b41146101aa578063a0cf6e8b14610188578063a9059cbb14610157578063d0e30db014610144578063d17c8d431461011f5763dd62ed3e0361000e573461011b57604036600319011261011b576100e46106db565b6100ec6106f1565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b3461011b575f36600319011261011b57602060ff60035460081c166040519015158152f35b5f36600319011261011b5761002061085c565b3461011b57604036600319011261011b57602061017e6101756106db565b60243590610841565b6040519015158152f35b3461011b575f36600319011261011b57602060ff600354166040519015158152f35b3461011b575f36600319011261011b576040515f6001548060011c906001811680156102a4575b6020831081146102905782855290811561026c575060011461020e575b61020a836101fe8185038261068f565b604051918291826106b1565b0390f35b60015f9081527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6939250905b808210610252575090915081016020016101fe6101ee565b91926001816020925483858801015201910190929161023a565b60ff191660208086019190915291151560051b840190910191506101fe90506101ee565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101d1565b3461011b57602036600319011261011b576001600160a01b036102cf6106db565b165f526004602052602060405f2054604051908152f35b3461011b57604036600319011261011b576102ff6106db565b5f5f5160206109c55f395f51905f5260206024359361032085600254610834565b60025560018060a01b0316938484526004825260408420610342828254610834565b9055604051908152a3005b3461011b57604036600319011261011b5760043580151580910361011b5760243580151580910361011b5760ff61ff006003549260081b1692169061ffff191617176003555f80f35b3461011b575f36600319011261011b57602060405160128152f35b3461011b57602036600319011261011b57600435335f52600460205260405f20548181106104cd575f80836103e882958395610707565b3383526004602052604083205561040181600254610707565b600255816040518281525f5160206109c55f395f51905f5260203392a3335af13d156104c8573d67ffffffffffffffff81116104b4576040519061044f601f8201601f19166020018361068f565b81525f60203d92013e5b1561046057005b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652057455448206e6174697665207472616e736665722060448201526519985a5b195960d21b6064820152608490fd5b634e487b7160e01b5f52604160045260245ffd5b610459565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520574554482062616c616e636520746f6f206c6f77006044820152606490fd5b3461011b57606036600319011261011b57602061017e6105306106db565b6105386106f1565b60443591610728565b3461011b575f36600319011261011b576020600254604051908152f35b3461011b57604036600319011261011b576105776106db565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b3461011b575f36600319011261011b576040515f5f548060011c90600181168015610685575b6020831081146102905782855290811561026c57506001146106295761020a836101fe8185038261068f565b5f8080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563939250905b80821061066b575090915081016020016101fe6101ee565b919260018160209254838588010152019101909291610653565b91607f16916105fd565b90601f8019910116810190811067ffffffffffffffff8211176104b457604052565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361011b57565b602435906001600160a01b038216820361011b57565b9190820391821161071457565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c1661082d576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106107d85761077994846001820161077e575b5050506108a0565b600190565b61078791610707565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610771565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b9190820180921161071457565b9060ff600354166108565761077991336108a0565b50505f90565b61086834600254610834565b600255335f52600460205260405f20610882348254610834565b90556040513481525f5f5160206109c55f395f51905f5260203393a3565b6001600160a01b039091169190821561096f576001600160a01b03165f8181526004602052604090205490919081811061091c57816108ef5f5160206109c55f395f51905f5293602093610707565b845f526004835260405f2055845f526004825260405f20610911828254610834565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220882f68f220ca5b1540ddc2e1c278c46e3ed2506469c52e2d03eba7f0379d78d764736f6c63430008230033', }, deployedBytecode: { object: - '60806040526004361015610022575b3615610018575f80fd5b61002061085c565b005b5f3560e01c806306fdde03146105d7578063095ea7b31461055e57806318160ddd1461054157806323b872dd146105125780632e1a7d4d146103b1578063313ce5671461039657806333ea03891461034d57806340c10f19146102e657806370a08231146102ae57806395d89b41146101aa578063a0cf6e8b14610188578063a9059cbb14610157578063d0e30db014610144578063d17c8d431461011f5763dd62ed3e0361000e573461011b57604036600319011261011b576100e46106db565b6100ec6106f1565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b3461011b575f36600319011261011b57602060ff60035460081c166040519015158152f35b5f36600319011261011b5761002061085c565b3461011b57604036600319011261011b57602061017e6101756106db565b60243590610841565b6040519015158152f35b3461011b575f36600319011261011b57602060ff600354166040519015158152f35b3461011b575f36600319011261011b576040515f6001548060011c906001811680156102a4575b6020831081146102905782855290811561026c575060011461020e575b61020a836101fe8185038261068f565b604051918291826106b1565b0390f35b60015f9081527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6939250905b808210610252575090915081016020016101fe6101ee565b91926001816020925483858801015201910190929161023a565b60ff191660208086019190915291151560051b840190910191506101fe90506101ee565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101d1565b3461011b57602036600319011261011b576001600160a01b036102cf6106db565b165f526004602052602060405f2054604051908152f35b3461011b57604036600319011261011b576102ff6106db565b5f5f5160206109c55f395f51905f5260206024359361032085600254610834565b60025560018060a01b0316938484526004825260408420610342828254610834565b9055604051908152a3005b3461011b57604036600319011261011b5760043580151580910361011b5760243580151580910361011b5760ff61ff006003549260081b1692169061ffff191617176003555f80f35b3461011b575f36600319011261011b57602060405160128152f35b3461011b57602036600319011261011b57600435335f52600460205260405f20548181106104cd575f80836103e882958395610707565b3383526004602052604083205561040181600254610707565b600255816040518281525f5160206109c55f395f51905f5260203392a3335af13d156104c8573d67ffffffffffffffff81116104b4576040519061044f601f8201601f19166020018361068f565b81525f60203d92013e5b1561046057005b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652057455448206e6174697665207472616e736665722060448201526519985a5b195960d21b6064820152608490fd5b634e487b7160e01b5f52604160045260245ffd5b610459565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520574554482062616c616e636520746f6f206c6f77006044820152606490fd5b3461011b57606036600319011261011b57602061017e6105306106db565b6105386106f1565b60443591610728565b3461011b575f36600319011261011b576020600254604051908152f35b3461011b57604036600319011261011b576105776106db565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b3461011b575f36600319011261011b576040515f5f548060011c90600181168015610685575b6020831081146102905782855290811561026c57506001146106295761020a836101fe8185038261068f565b5f8080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563939250905b80821061066b575090915081016020016101fe6101ee565b919260018160209254838588010152019101909291610653565b91607f16916105fd565b90601f8019910116810190811067ffffffffffffffff8211176104b457604052565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361011b57565b602435906001600160a01b038216820361011b57565b9190820391821161071457565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c1661082d576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106107d85761077994846001820161077e575b5050506108a0565b600190565b61078791610707565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610771565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b9190820180921161071457565b9060ff600354166108565761077991336108a0565b50505f90565b61086834600254610834565b600255335f52600460205260405f20610882348254610834565b90556040513481525f5f5160206109c55f395f51905f5260203393a3565b6001600160a01b039091169190821561096f576001600160a01b03165f8181526004602052604090205490919081811061091c57816108ef5f5160206109c55f395f51905f5293602093610707565b845f526004835260405f2055845f526004825260405f20610911828254610834565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220853891adc1e7d9992426dbd3a6e5975e4a26dc87e17d7e455cfe7dff481f7a6564736f6c63430008230033', + '60806040526004361015610022575b3615610018575f80fd5b61002061085c565b005b5f3560e01c806306fdde03146105d7578063095ea7b31461055e57806318160ddd1461054157806323b872dd146105125780632e1a7d4d146103b1578063313ce5671461039657806333ea03891461034d57806340c10f19146102e657806370a08231146102ae57806395d89b41146101aa578063a0cf6e8b14610188578063a9059cbb14610157578063d0e30db014610144578063d17c8d431461011f5763dd62ed3e0361000e573461011b57604036600319011261011b576100e46106db565b6100ec6106f1565b6001600160a01b039182165f908152600560209081526040808320949093168252928352819020549051908152f35b5f80fd5b3461011b575f36600319011261011b57602060ff60035460081c166040519015158152f35b5f36600319011261011b5761002061085c565b3461011b57604036600319011261011b57602061017e6101756106db565b60243590610841565b6040519015158152f35b3461011b575f36600319011261011b57602060ff600354166040519015158152f35b3461011b575f36600319011261011b576040515f6001548060011c906001811680156102a4575b6020831081146102905782855290811561026c575060011461020e575b61020a836101fe8185038261068f565b604051918291826106b1565b0390f35b60015f9081527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6939250905b808210610252575090915081016020016101fe6101ee565b91926001816020925483858801015201910190929161023a565b60ff191660208086019190915291151560051b840190910191506101fe90506101ee565b634e487b7160e01b5f52602260045260245ffd5b91607f16916101d1565b3461011b57602036600319011261011b576001600160a01b036102cf6106db565b165f526004602052602060405f2054604051908152f35b3461011b57604036600319011261011b576102ff6106db565b5f5f5160206109c55f395f51905f5260206024359361032085600254610834565b60025560018060a01b0316938484526004825260408420610342828254610834565b9055604051908152a3005b3461011b57604036600319011261011b5760043580151580910361011b5760243580151580910361011b5760ff61ff006003549260081b1692169061ffff191617176003555f80f35b3461011b575f36600319011261011b57602060405160128152f35b3461011b57602036600319011261011b57600435335f52600460205260405f20548181106104cd575f80836103e882958395610707565b3383526004602052604083205561040181600254610707565b600255816040518281525f5160206109c55f395f51905f5260203392a3335af13d156104c8573d67ffffffffffffffff81116104b4576040519061044f601f8201601f19166020018361068f565b81525f60203d92013e5b1561046057005b60405162461bcd60e51b815260206004820152602660248201527f4f70656e4f7261636c652057455448206e6174697665207472616e736665722060448201526519985a5b195960d21b6064820152608490fd5b634e487b7160e01b5f52604160045260245ffd5b610459565b60405162461bcd60e51b815260206004820152601f60248201527f4f70656e4f7261636c6520574554482062616c616e636520746f6f206c6f77006044820152606490fd5b3461011b57606036600319011261011b57602061017e6105306106db565b6105386106f1565b60443591610728565b3461011b575f36600319011261011b576020600254604051908152f35b3461011b57604036600319011261011b576105776106db565b335f8181526005602090815260408083206001600160a01b03909516808452948252918290206024359081905591519182527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a3602060405160018152f35b3461011b575f36600319011261011b576040515f5f548060011c90600181168015610685575b6020831081146102905782855290811561026c57506001146106295761020a836101fe8185038261068f565b5f8080527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563939250905b80821061066b575090915081016020016101fe6101ee565b919260018160209254838588010152019101909291610653565b91607f16916105fd565b90601f8019910116810190811067ffffffffffffffff8211176104b457604052565b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b038216820361011b57565b602435906001600160a01b038216820361011b57565b9190820391821161071457565b634e487b7160e01b5f52601160045260245ffd5b919060ff60035460081c1661082d576001600160a01b0383165f81815260056020908152604080832033845290915290205493908385106107d85761077994846001820161077e575b5050506108a0565b600190565b61078791610707565b5f8281526005602090815260408083203380855290835292819020849055519283529092917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259190a35f8084610771565b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20616c6c6f77616e636520604482015266746f6f206c6f7760c81b6064820152608490fd5b5050505f90565b9190820180921161071457565b9060ff600354166108565761077991336108a0565b50505f90565b61086834600254610834565b600255335f52600460205260405f20610882348254610834565b90556040513481525f5f5160206109c55f395f51905f5260203393a3565b6001600160a01b039091169190821561096f576001600160a01b03165f8181526004602052604090205490919081811061091c57816108ef5f5160206109c55f395f51905f5293602093610707565b845f526004835260405f2055845f526004825260405f20610911828254610834565b9055604051908152a3565b60405162461bcd60e51b815260206004820152602560248201527f4f70656e4f7261636c65207465737420746f6b656e2062616c616e636520746f6044820152646f206c6f7760d81b6064820152608490fd5b60405162461bcd60e51b815260206004820152602760248201527f4f70656e4f7261636c65207465737420746f6b656e20726563697069656e74206044820152666973207a65726f60c81b6064820152608490fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220882f68f220ca5b1540ddc2e1c278c46e3ed2506469c52e2d03eba7f0379d78d764736f6c63430008230033', }, }, } as const diff --git a/bots/open-oracle-arbitrager/src/execution/recovery-support.ts b/bots/open-oracle-arbitrager/src/execution/recovery-support.ts index 6e6cf932d..91d8767b9 100644 --- a/bots/open-oracle-arbitrager/src/execution/recovery-support.ts +++ b/bots/open-oracle-arbitrager/src/execution/recovery-support.ts @@ -130,7 +130,7 @@ async function legacyReplacementAmounts(client: ReadClient, openOracle: Address, const reportId = BigInt(position.reportId) let foundEntry = false for (const range of scanRanges({ nextBlock: BigInt(position.entrySubmissionBlockNumber) }, blockNumber, LEGACY_REPLACEMENT_LOG_SCAN_RANGE)) { - const logs = (await client.getLogs({ address: openOracle, fromBlock: range.fromBlock, toBlock: range.toBlock, topics: [OPEN_ORACLE_REPORT_DISPUTED_TOPIC, toHex(reportId, { size: 32 })] })).sort(compareLogs) + const logs = [...(await client.getLogs({ address: openOracle, fromBlock: range.fromBlock, toBlock: range.toBlock, topics: [OPEN_ORACLE_REPORT_DISPUTED_TOPIC, toHex(reportId, { size: 32 })] }))].sort(compareLogs) for (const log of logs) { if (!foundEntry) { foundEntry = log.transactionHash?.toLowerCase() === position.entryTransactionHash.toLowerCase() diff --git a/bots/open-oracle-arbitrager/tests/execution/coordinator-report-discovery.test.ts b/bots/open-oracle-arbitrager/tests/execution/coordinator-report-discovery.test.ts index e10abee21..931d39d36 100644 --- a/bots/open-oracle-arbitrager/tests/execution/coordinator-report-discovery.test.ts +++ b/bots/open-oracle-arbitrager/tests/execution/coordinator-report-discovery.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test' -import { createPublicClient, custom, decodeFunctionData, encodeAbiParameters, getAddress, mainnet, toHex, type EIP1193Provider, type Hex } from '#ethereum' +import { bytesToHex, createPublicClient, custom, decodeFunctionData, encodeAbiParameters, getAddress, hexToBytes, isHex, mainnet, toHex, type EIP1193Provider, type Hex } from '#ethereum' import { openOracleAbi, openOraclePriceCoordinatorAbi } from '#contracts/abi' import { disputeRecord, legacyReplacementAmountsWithQuorum, pendingCoordinatorReports, pendingCoordinatorReportsWithQuorum, replacementDisputeAmountsWithQuorum } from '#execution/recovery-support' import { applyCoordinatorReports, type ActiveReport } from '#monitoring/oracle-log-state' @@ -12,6 +12,12 @@ const openOracle = getAddress('0x0000000000000000000000000000000000000003') const reporter = getAddress('0x0000000000000000000000000000000000000004') const weth = getAddress('0x0000000000000000000000000000000000000005') const rep = getAddress('0x0000000000000000000000000000000000000006') + +function requiredHex(value: unknown) { + if (typeof value !== 'string' || !isHex(value, { strict: true })) throw new Error('Expected hex RPC request data') + return bytesToHex(hexToBytes(value)) +} + const gameOutputs = [ { type: 'uint128' }, { type: 'uint128' }, @@ -75,7 +81,7 @@ describe('configured coordinator report discovery', () => { if (typeof request !== 'object' || request === null || !('to' in request) || !('data' in request)) throw new Error('Malformed contract read') blockTags.push(parameters.params[1]) const to = String(request.to).toLowerCase() - const data = String(request.data) + const data = requiredHex(request.data) if (to === activeCoordinator.toLowerCase() || to === idleCoordinator.toLowerCase()) { const decoded = decodeFunctionData({ abi: openOraclePriceCoordinatorAbi, data }) if (decoded.functionName !== 'pendingReportId') throw new Error(`Unexpected coordinator read ${decoded.functionName}`) @@ -146,7 +152,7 @@ describe('configured coordinator report discovery', () => { const request = parameters.params[0] if (typeof request !== 'object' || request === null || !('to' in request) || !('data' in request)) throw new Error('Malformed contract read') const to = String(request.to).toLowerCase() - const data = String(request.data) + const data = requiredHex(request.data) if (to === activeCoordinator.toLowerCase()) { const decoded = decodeFunctionData({ abi: openOraclePriceCoordinatorAbi, data }) if (decoded.functionName !== 'pendingReportId') throw new Error(`Unexpected coordinator read ${decoded.functionName}`) @@ -162,7 +168,7 @@ describe('configured coordinator report discovery', () => { } } const client = (reportId: bigint, unavailable = false, reorg = false) => createPublicClient({ chain: mainnet, transport: custom(provider(reportId, unavailable, reorg)) }) - const config = { connectivity: { readRpcUrl: 'https://primary.example' }, coordinatorAddresses: [activeCoordinator], openOracle, quorumRpcUrls: ['https://secondary.example', 'https://tertiary.example'] } + const config = { connectivity: { publicRpcUrls: ['https://public.example'], readRpcUrl: 'https://primary.example' }, coordinatorAddresses: [activeCoordinator], openOracle, quorumRpcUrls: ['https://secondary.example', 'https://tertiary.example'] } const reports = await pendingCoordinatorReportsWithQuorum([client(7n), client(7n), client(7n, true)], config, 100n) @@ -238,7 +244,7 @@ describe('configured coordinator report discovery', () => { }) const clients = [createPublicClient({ chain: mainnet, transport: custom(provider(0)) }), createPublicClient({ chain: mainnet, transport: custom(provider(1)) })] - const replacement = await legacyReplacementAmountsWithQuorum(clients, { connectivity: { readRpcUrl: 'https://primary.example' }, openOracle, quorumRpcUrls: ['https://secondary.example'] }, { entrySubmissionBlockNumber: '0', entryTransactionHash, reportId: '7' }, 250n) + const replacement = await legacyReplacementAmountsWithQuorum(clients, { connectivity: { publicRpcUrls: ['https://public.example'], readRpcUrl: 'https://primary.example' }, openOracle, quorumRpcUrls: ['https://secondary.example'] }, { entrySubmissionBlockNumber: '0', entryTransactionHash, reportId: '7' }, 250n) expect(replacement).toEqual({ amounts: { amount1: 1_400n, amount2: 2_300n }, blockHash }) const logCalls = calls.filter(call => call.method === 'eth_getLogs') @@ -270,8 +276,8 @@ describe('configured coordinator report discovery', () => { } } const reorgClients = [createPublicClient({ chain: mainnet, transport: custom(reorgProvider()) }), createPublicClient({ chain: mainnet, transport: custom(reorgProvider()) })] - await expect(legacyReplacementAmountsWithQuorum(reorgClients, { connectivity: { readRpcUrl: 'https://primary.example' }, openOracle, quorumRpcUrls: ['https://secondary.example'] }, { entrySubmissionBlockNumber: '0', entryTransactionHash, reportId: '7' }, 250n)).rejects.toThrow( - 'changed during legacy replacement recovery', - ) + await expect( + legacyReplacementAmountsWithQuorum(reorgClients, { connectivity: { publicRpcUrls: ['https://public.example'], readRpcUrl: 'https://primary.example' }, openOracle, quorumRpcUrls: ['https://secondary.example'] }, { entrySubmissionBlockNumber: '0', entryTransactionHash, reportId: '7' }, 250n), + ).rejects.toThrow('changed during legacy replacement recovery') }) }) diff --git a/bots/open-oracle-arbitrager/tests/execution/create2-executor.test.ts b/bots/open-oracle-arbitrager/tests/execution/create2-executor.test.ts index 7462bb77e..3af80ac71 100644 --- a/bots/open-oracle-arbitrager/tests/execution/create2-executor.test.ts +++ b/bots/open-oracle-arbitrager/tests/execution/create2-executor.test.ts @@ -25,7 +25,7 @@ test('derives a stable executor address and canonical proxy calldata from a byte const salt = `0x${'00'.repeat(32)}` as Hex const plan = executorDeploymentPlan(salt) expect(deterministicDeploymentProxy).toBe('0x4e59b44847b379578588920cA78FbF26c0B4956C') - expect(plan.address).toBe('0xe04E3658Eb81792D5fc059ffF23d996b7940E1aA') + expect(plan.address).toBe('0x5D35D34367322271BB3deAE3fAce338ca1D3201b') expect(plan.salt).toBe(salt) expect(plan.calldata).toBe(`${salt}${plan.bytecode.slice(2)}` as Hex) }) diff --git a/bots/shared/tests/shared-primitives.test.ts b/bots/shared/tests/shared-primitives.test.ts index 29abe064e..51a3a2b56 100644 --- a/bots/shared/tests/shared-primitives.test.ts +++ b/bots/shared/tests/shared-primitives.test.ts @@ -22,6 +22,10 @@ afterEach(async () => { }) describe('shared bot primitives', () => { + test('resolves the root Ethereum package without generated JavaScript under Bun', () => { + expect(Bun.resolveSync('@zoltar/shared/ethereum', import.meta.dir)).toBe(join(import.meta.dir, '../../../shared/ts/ethereum.ts')) + }) + test('keeps the Ethereum facade limited to the compatibility surface', async () => { const facade = await import('../src/ethereum.ts') expect(Object.keys(facade).sort()).toEqual( diff --git a/shared/package.json b/shared/package.json index fbd2ede95..fa11df78d 100644 --- a/shared/package.json +++ b/shared/package.json @@ -25,6 +25,7 @@ }, "./ethereum": { "types": "./ts/ethereum.ts", + "bun": "./ts/ethereum.ts", "default": "./js/ethereum.js" }, "./protocolConfig": { diff --git a/trading/ui/ts/features/LiveTrading.tsx b/trading/ui/ts/features/LiveTrading.tsx index ae1f26000..2235a5f02 100644 --- a/trading/ui/ts/features/LiveTrading.tsx +++ b/trading/ui/ts/features/LiveTrading.tsx @@ -41,6 +41,7 @@ import { type BalanceState, type GuardedWalletWrite, type PortfolioBalanceEntry, + type Quote, type QuoteContext, type TransactionState, type WalletSummaryState,