UNPKG

@adraffy/blocksmith

Version:

Minimal Ethereum Testing Framework for Foundry + ethers

485 lines (428 loc) 12 kB
import { WebSocketProvider, Wallet, Contract, Interface, Fragment, JsonFragment, TransactionReceipt, TransactionResponse, TransactionDescription, BigNumberish, BytesLike, BaseContract, ContractInterface, ContractEventName, EventEmitterable, Log, Result, EventFragment, SigningKey, JsonRpcApiProvider, } from "ethers"; import { EventEmitter } from "node:events"; import { ChildProcess } from "node:child_process"; type DevWallet = Omit<Wallet, "connect">; type PatchedBaseContract = Omit< BaseContract, "target" | "connect" | "attach" > & { target: string; connect(...args: Parameters<Contract["connect"]>): Contract; attach(...args: Parameters<Contract["attach"]>): Contract; waitForDeployment(): Promise<Contract>; }; type FoundryContract = PatchedBaseContract & Omit<ContractInterface, keyof PatchedBaseContract> & EventEmitterable<ContractEventName> & { readonly __info: { readonly contract: string; }; }; type DeployedContract = FoundryContract & { readonly __receipt: TransactionReceipt; readonly __info: { readonly origin: string; readonly bytecode: Uint8Array; readonly libs: { [cid: string]: string }; readonly from: DevWallet; }; }; type EventLike = string | EventFragment; type InterfaceLike = | Interface | Contract | (Fragment | JsonFragment | string)[]; type ExternalLink = { file: string; contract: string; offsets: number[]; }; type PathLike = string | URL; type WalletLike = string | DevWallet; type CompiledArtifact = { readonly type: string; readonly contract: string; readonly origin: string; readonly abi: Interface; readonly bytecode: string; readonly links: ExternalLink[]; }; type CompiledFromSourceArtifact = CompiledArtifact & { readonly cid: string; readonly root: string; readonly compiler: string; }; type FileArtifact = CompiledFromSourceArtifact & { readonly file: string; }; type CodeArtifact = CompiledFromSourceArtifact & { readonly sol: string; }; type Artifact = CompiledArtifact | FileArtifact | CodeArtifact; type CompileOptions = { optimize?: boolean | number; solcVersion?: string; evmVersion?: string; viaIR?: boolean; autoHeader?: boolean; contract?: string; }; export function compile( sol: string | string[], options?: { foundry?: Foundry } & CompileOptions ): Promise<CodeArtifact>; type ArtifactLike = | { import: string; contract?: string } | { bytecode: string; abi?: InterfaceLike; contract?: string } | ({ sol: string } & CompileOptions) | { file: string; contract?: string }; type ToConsoleLog = boolean | PathLike | ((line: string) => any); type WalletOptions = { ether?: BigNumberish; }; type BuildInfo = { date: Date; }; type ConfirmOptions = { silent?: boolean; confirms?: number; }; type Backend = "ethereum" | "optimism"; type FoundryBaseOptions = { root?: PathLike; // default: ancestor w/foundry.toml profile?: string; // default: "default" forge?: string; // default: "forge" via PATH }; type BuildEvent = { started: Date; root: string; cmd: string[]; force: boolean; profile: string; mode: "project" | "shadow" | "compile"; }; type FoundryEventMap = { building: [event: BuildEvent]; built: [event: BuildEvent & { sources: string[] }]; shutdown: []; tx: [ tx: TransactionResponse, receipt: TransactionReceipt, desc?: TransactionDescription ]; console: [line: string]; deploy: [contract: DeployedContract]; }; export class FoundryBase extends EventEmitter { // <FoundryEventMap> { static profile(): string; static root(cwd?: PathLike): Promise<string>; static load(options?: FoundryBaseOptions): Promise<FoundryBase>; readonly root: string; readonly profile: string; readonly config: { src: string; test: string; out: string; libs: string[]; remappings: string[]; }; readonly forge: string; readonly built?: BuildInfo; compiler(solcVersion: string): Promise<string>; version(): Promise<string>; build(force?: boolean): Promise<BuildInfo>; compile( sol: string | string[], options?: CompileOptions ): Promise<CodeArtifact>; find(options: { file: string; contract?: string }): Promise<string>; resolveArtifact(artifact: ArtifactLike | string): Promise<Artifact>; on<E extends keyof FoundryEventMap>( name: E, fn: (...args: FoundryEventMap[E]) => any ): this; once<E extends keyof FoundryEventMap>( name: E, fn: (...args: FoundryEventMap[E]) => any ): this; } type SolidityStandardJSONInput = { language: string; sources: { [cid: string]: { content: string } }; optimizer: { enabled: boolean; runs?: number; }; settings: { remappings: string[]; metadata: Record<string, any>; evmVersion: string; viaIR: boolean; libraries: { [cid: string]: { [contract: string]: string } }; }; }; type VerifyEtherscanOptions = { apiKey?: string; // foundry.toml => ETHERSCAN_API_KEY pollMs?: number; // default: 5sec retry?: number; // default: 10 }; export type Deployable = { gas: bigint; gasPrice: bigint; maxFeePerGas: bigint; maxPriorityFeePerGas: bigint; wei: bigint; eth: string; root: string; cid: string; linked: (ExternalLink & { cid: string; address: string })[]; compiler: string; decodedArgs: any[]; encodedArgs: string; address?: string; deployArgs(injectPrivateKey?: boolean): string[]; deploy(options?: { confirms?: number; }): Promise<{ contract: Contract; receipt: TransactionReceipt }>; json(): Promise<Readonly<SolidityStandardJSONInput>>; verifyEtherscan( options?: { address?: string } & VerifyEtherscanOptions ): Promise<void>; }; type FoundryDeployerOptions = FoundryBaseOptions & { infoLog?: ToConsoleLog; // default: true }; export class FoundryDeployer extends FoundryBase { static etherscanChains(): Promise<Map<bigint, string>>; static load( options?: { provider?: | JsonRpcApiProvider | "mainnet" | "sepolia" | "holesky" | "arb1" | "base" | "op" | "linea" | "polygon"; privateKey?: string | SigningKey; } & FoundryDeployerOptions ): Promise<FoundryDeployer>; readonly rpc: string; readonly chain: bigint; readonly provider: JsonRpcApiProvider; set etherscanApiKey(key: string | undefined); get etherscanApiKey(): string; set privateKey(key: SigningKey | string | undefined); get privateKey(): SigningKey | undefined; get address(): string | undefined; requireWallet(): Wallet; prepare( options: | string | ({ args?: any[]; libs?: { [cid: string]: string }; confirms?: number; } & ArtifactLike) ): Promise<Readonly<Deployable>>; verifyEtherscan( options: { address: string; // 0x... json: SolidityStandardJSONInput; cid?: string; // "src/File.sol:Contract" encodedArg?: string | Uint8Array; compiler?: string; // can be semver } & VerifyEtherscanOptions ): Promise<void>; } export class Foundry extends FoundryBase { static of(x: DevWallet | FoundryContract): Foundry; static launch( options?: { port?: number; chain?: number; anvil?: string; gasLimit?: number; blockSec?: number; accounts?: string[]; autoClose?: boolean; // default: true infoLog?: ToConsoleLog; // default: true = console.log() procLog?: ToConsoleLog; // default: off fork?: PathLike; infiniteCallGas?: boolean; // default: false genesisTimestamp?: number; // default: now backend?: Backend; // default: 'ethereum' hardfork?: string; // default: latest } & FoundryBaseOptions ): Promise<Foundry>; ensRegistry: string; readonly anvil: string; readonly proc: ChildProcess; readonly provider: WebSocketProvider; readonly wallets: Record<string, DevWallet>; readonly accounts: Map<string, DevWallet | FoundryContract>; readonly endpoint: string; readonly chain: number; readonly port: number; readonly automine: boolean; readonly backend: Backend; readonly hardfork: string; readonly started: Date; readonly fork: string | undefined; // note: these are silent fail on forks nextBlock(options?: { blocks?: number; sec?: number }): Promise<void>; setStorageValue( address: string | Contract, slot: BigNumberish, value: BigNumberish | Uint8Array | undefined ): Promise<void>; getStorageBytesLength( address: string | Contract, slot: BigNumberish ): Promise<bigint>; getStorageBytes( address: string | Contract, slot: BigNumberish, maxBytes?: number ): Promise<Uint8Array>; setStorageBytes( address: string | Contract, slot: BigNumberish, value: BytesLike | undefined, zeroBytes?: boolean | number ): Promise<void>; overrideENS( options: { owner?: string | FoundryContract | null; resolver?: string | FoundryContract | null; registry?: string | FoundryContract; } & ({ name: string } | { node: string }) ): Promise<void>; // require a wallet requireWallet(...wallets: (WalletLike | undefined)[]): DevWallet; randomWallet( options?: { prefix?: string } & WalletOptions ): Promise<DevWallet>; ensureWallet(wallet: WalletLike, options?: WalletOptions): Promise<DevWallet>; attach( options: { to: string | FoundryContract; from?: WalletLike; abis?: InterfaceLike[]; parseAllErrors?: boolean; } & ArtifactLike ): Promise<FoundryContract>; // compile and deploy a contract, returns Contract with ABI deploy( options: | string | ({ from?: WalletLike; args?: any[]; libs?: { [cid: string]: string | FoundryContract }; abis?: InterfaceLike[]; parseAllErrors?: boolean; } & ConfirmOptions & ArtifactLike) ): Promise<DeployedContract>; // send a transaction promise and get a pretty print console log confirm( call: TransactionResponse | Promise<TransactionResponse>, options?: ConfirmOptions & Record<string, any> ): Promise<TransactionReceipt>; addABI(abi: Interface): void; parseAllErrors(iface: Interface): Interface; parseArtifacts(): Promise<void>; findEvent(event: EventLike): { abi: Interface; frag: EventFragment }; getEventResults( logs: Log[] | TransactionReceipt | DeployedContract, event: EventLike ): Result[]; // kill anvil (this is a bound function) shutdown: () => Promise<void>; } export function mergeABI(...abis: InterfaceLike[]): Interface; export class Node extends Map { static root(tag?: string): Node; static create(name: string | Node): Node; readonly label: string; readonly parent: Node; readonly namehash: string; readonly labelhash: string; get dns(): Uint8Array; get name(): string; get isETH2LD(): boolean; get depth(): number; get nodeCount(): number; get root(): Node; path(includeRoot?: boolean): Node[]; find(name: string): Node | undefined; create(name: string): Node; child(label: string): Node; unique(prefix?: string): Node; scan(fn: (node: Node, level: number) => void, level?: number): void; flat(): Node[]; print(): void; } type RecordQuery = { type: "addr" | "text" | "contenthash" | "pubkey" | "name"; arg?: any; }; type RecordResult = { rec: RecordQuery; res?: any; err?: Error }; type TORPrefix = "on" | "off" | undefined; type RecordOptions = { multi?: boolean; ccip?: boolean; tor?: TORPrefix }; export class Resolver { static readonly ABI: Interface; static get(ens: Contract, node: Node): Promise<Resolver | undefined>; constructor(node: Node, contract: Contract); readonly node: Node; readonly contract: Contract; readonly base: Node; readonly drop: number; wild: boolean; tor: boolean; get address(): string; text(key: string, options?: RecordOptions): Promise<string>; addr(type?: number, options?: RecordOptions): Promise<string>; contenthash(options?: RecordOptions): Promise<string>; record(rec: RecordQuery, options?: RecordOptions): Promise<any>; records( rec: RecordQuery[], options?: RecordOptions ): Promise<[records: RecordResult[], multicalled?: boolean]>; profile( rec?: RecordQuery[], options?: RecordOptions ): Promise<{ [key: string]: any }>; } export function error_with( message: string, options: Object, cause?: any ): Error; export function to_address(x: any): string; export function is_address(x: any): boolean;