UNPKG

@tevm/compiler

Version:
368 lines (326 loc) 10.7 kB
import { type ResolvedCompilerConfig, defaultConfig } from '@tevm/config' import { type MockedFunction, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { compileContractSync } from './compiler/compileContractsSync.js' import { resolveArtifactsSync } from './resolveArtifactsSync.js' import type { FileAccessObject, Logger, ModuleInfo, SolcInputDescription, SolcOutput } from './types.js' vi.mock('./compiler/compileContractsSync', () => ({ compileContractSync: vi.fn(), })) const fao: FileAccessObject = { existsSync: vi.fn() as any, readFileSync: vi.fn() as any, readFile: vi.fn() as any, exists: vi.fn() as any, } const mockModules: Record<string, ModuleInfo> = { module1: { id: 'id', rawCode: `import { TestContract } from 'module2' contract TestContract {}`, code: `import { TestContract } from 'module2' contract TestContract {}`, importedIds: ['module2'], }, } const solFile = 'test.sol' const basedir = 'basedir' const logger: Logger = { info: vi.fn(), error: vi.fn(), warn: vi.fn(), log: vi.fn(), } const config: ResolvedCompilerConfig = defaultConfig const contracts = { Test: { abi: [], evm: {}, }, } const mockCompileContractSync = compileContractSync as MockedFunction<typeof compileContractSync> describe('resolveArtifactsSync', () => { beforeEach(() => { vi.clearAllMocks() }) it('should throw an error if the file is not a solidity file', () => { expect(() => resolveArtifactsSync('test.txt', basedir, logger, config, false, false, fao, require('solc')), ).toThrowErrorMatchingInlineSnapshot('[Error: Not a solidity file]') }) it('should throw an error if the compilation failed', () => { // throw a compilation error mockCompileContractSync.mockImplementation(() => { throw new Error('Oops') }) expect(() => resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')), ).toThrowErrorMatchingInlineSnapshot('[Error: Oops]') }) it('should return the contract artifacts', () => { mockCompileContractSync.mockReturnValue({ artifacts: contracts, modules: mockModules, } as any) expect( resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')), ).toMatchInlineSnapshot(` { "artifacts": { "Test": { "abi": [], "contractName": "Test", "evm": {}, "userdoc": undefined, }, }, "asts": undefined, "modules": { "module1": { "code": "import { TestContract } from 'module2' contract TestContract {}", "id": "id", "importedIds": [ "module2", ], "rawCode": "import { TestContract } from 'module2' contract TestContract {}", }, }, "solcInput": undefined, "solcOutput": undefined, } `) }) it('should correctly transform the contract artifacts', () => { mockCompileContractSync.mockReturnValue({ artifacts: { Test: { abi: ['testAbi'] as any, evm: { bytecode: { object: 'testBytecode' } } as any, }, } as any, modules: mockModules, } as any) const { artifacts } = resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')) expect(artifacts).toEqual({ Test: { contractName: 'Test', abi: ['testAbi'], evm: { bytecode: { object: 'testBytecode' } }, }, }) }) it('should throw an error if artifacts is undefined', () => { mockCompileContractSync.mockReturnValue({ artifacts: undefined, modules: mockModules, } as any) expect(() => resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')), ).toThrowErrorMatchingInlineSnapshot('[Error: Compilation failed]') }) it('should throw an error if file doesnt end in .sol', () => { expect(() => resolveArtifactsSync('test.txt', basedir, logger, config, false, false, fao, require('solc')), ).toThrowErrorMatchingInlineSnapshot('[Error: Not a solidity file]') }) it('should correctly transform complex artifact structures', () => { const complexContracts = { ComplexContract: { abi: [ { inputs: [], name: 'getValue', outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [{ name: '_value', type: 'uint256' }], name: 'setValue', outputs: [], stateMutability: 'nonpayable', type: 'function', }, ], userdoc: { methods: { 'getValue()': { notice: 'Returns the current value', }, 'setValue(uint256)': { notice: 'Sets a new value', }, }, }, evm: { bytecode: { object: '0x608060405234801561001057600080fd5b5060f78061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806317a889c514603757806355241077146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506084565b005b60005481565b806000819055505056fea2646970667358221220223b76', }, deployedBytecode: { object: '0x6080604052348015600f57600080fd5b506004361060325760003560e01c806317a889c514603757806355241077146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506084565b005b60005481565b806000819055505056fea2646970667358221220223b76', }, }, }, } const mockModules = { 'test.sol': { id: 'test.sol', code: 'contract ComplexContract { uint value; function getValue() public view returns (uint) { return value; } function setValue(uint _value) public { value = _value; } }', importedIds: [], }, } const mockSolcInput = { language: 'Solidity', sources: { 'test.sol': { content: 'contract ComplexContract { uint value; function getValue() public view returns (uint) { return value; } function setValue(uint _value) public { value = _value; } }', }, }, settings: { outputSelection: { '*': { '*': ['abi', 'userdoc', 'evm.bytecode.object', 'evm.deployedBytecode.object'], }, }, }, } as SolcInputDescription // @ts-expect-error - This is a mock for testing purposes const mockSolcOutput = { contracts: { 'test.sol': complexContracts, }, sources: {}, } as SolcOutput mockCompileContractSync.mockReturnValue({ artifacts: complexContracts, modules: mockModules, solcInput: mockSolcInput, solcOutput: mockSolcOutput, } as any) const result = resolveArtifactsSync(solFile, basedir, logger, config, false, true, fao, require('solc')) expect(result.artifacts).toBeDefined() if ('ComplexContract' in result.artifacts) { const contract = result.artifacts['ComplexContract'] expect(contract).toBeDefined() if (contract) { expect(contract.abi).toHaveLength(2) expect(contract.userdoc).toBeDefined() if (contract.userdoc) { expect(contract.userdoc.methods).toBeDefined() } if (contract.evm?.bytecode) { expect(contract.evm.bytecode.object).toBeDefined() } } } expect(result.solcInput).toEqual(mockSolcInput) expect(result.solcOutput).toEqual(mockSolcOutput) }) it('should handle artifacts with ASTs correctly', () => { const mockAst = { nodeType: 'SourceUnit', absolutePath: 'test.sol', nodes: [ { nodeType: 'ContractDefinition', name: 'TestContract', baseContracts: [], contractDependencies: [], contractKind: 'contract', fullyImplemented: true, }, ], } mockCompileContractSync.mockReturnValue({ artifacts: contracts, modules: mockModules, asts: { 'test.sol': mockAst }, solcInput: {} as SolcInputDescription, solcOutput: {} as SolcOutput, } as any) const result = resolveArtifactsSync(solFile, basedir, logger, config, true, false, fao, require('solc')) expect(result.asts).toBeDefined() expect(result.asts?.['test.sol']).toEqual(mockAst) }) it('should handle compiler errors by propagating them', () => { mockCompileContractSync.mockImplementation(() => { throw new Error('Compilation error: invalid syntax') }) expect(() => resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc'))).toThrow( 'Compilation error: invalid syntax', ) expect(mockCompileContractSync).toHaveBeenCalledWith( solFile, basedir, config, false, false, fao, logger, require('solc'), ) }) it('should log error message before throwing if compilation fails', () => { mockCompileContractSync.mockReturnValue({ artifacts: undefined, modules: mockModules, } as any) expect(() => resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc'))).toThrow( 'Compilation failed', ) expect(logger.error).toHaveBeenCalledWith(`Compilation failed for ${solFile}`) }) it('should handle multi-contract compilation and return all artifacts', () => { const multiContractArtifacts = { Contract1: { abi: [{ name: 'function1', type: 'function' }], evm: { bytecode: { object: '0x123' } }, }, Contract2: { abi: [{ name: 'function2', type: 'function' }], evm: { bytecode: { object: '0x456' } }, }, } mockCompileContractSync.mockReturnValue({ artifacts: multiContractArtifacts, modules: mockModules, } as any) const result = resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')) expect(Object.keys(result.artifacts)).toHaveLength(2) if ('Contract1' in result.artifacts) { expect(result.artifacts['Contract1']).toBeDefined() } if ('Contract2' in result.artifacts) { expect(result.artifacts['Contract2']).toBeDefined() } if ('Contract1' in result.artifacts && result.artifacts['Contract1']) { expect(result.artifacts['Contract1'].abi).toEqual([{ name: 'function1', type: 'function' }]) } if ('Contract2' in result.artifacts && result.artifacts['Contract2']) { expect(result.artifacts['Contract2'].abi).toEqual([{ name: 'function2', type: 'function' }]) } }) it('should verify the contractName is added to each artifact', () => { const namedArtifacts = { NamedContract: { abi: [], evm: {}, }, } mockCompileContractSync.mockReturnValue({ artifacts: namedArtifacts, modules: mockModules, } as any) const result = resolveArtifactsSync(solFile, basedir, logger, config, false, false, fao, require('solc')) if ('NamedContract' in result.artifacts) { expect(result.artifacts['NamedContract'].contractName).toBe('NamedContract') } }) }) afterEach(() => { vi.clearAllMocks() })