@tevm/compiler
Version:
Utilities around compiler
142 lines (132 loc) • 3.61 kB
JavaScript
import { moduleFactory } from '@tevm/resolutions'
import { solcCompile } from '@tevm/solc'
import { Effect } from 'effect'
import { runPromise } from 'effect/Effect'
import { invariant, resolveEffect } from '../utils/index.js'
/**
* Compile the Solidity contract and return its ABI.
*
* @template TIncludeAsts
* @template TIncludeBytecode
* @param {string} filePath
* @param {string} basedir
* @param {import('@tevm/config').ResolvedCompilerConfig} config
* @param {TIncludeAsts} includeAst
* @param {TIncludeBytecode} includeBytecode
* @param {import('../types.js').FileAccessObject} fao
* @param {import('../types.js').Logger} logger
* @param {any} solc
* @returns {Promise<import('../types.js').CompiledContracts<TIncludeAsts>>}
* @example
* const { artifacts, modules } = await compileContract(
* './contracts/MyContract.sol',
* __dirname,
* config,
* true,
* await import('fs'),
* logger,
* )
*/
export const compileContract = async (filePath, basedir, config, includeAst, includeBytecode, fao, logger, solc) => {
const moduleMap = await runPromise(
moduleFactory(
filePath,
await fao
.readFile(await Effect.runPromise(resolveEffect(filePath, basedir, fao, logger)), 'utf8')
.then((code) => {
return code
}),
config.remappings,
config.libs,
fao,
false,
),
)
const entryModule = moduleMap.get(filePath)
invariant(entryModule, 'Entry module should exist')
/**
* @type {Object.<string, import('../types.js').ModuleInfo>}
*/
const modules = {}
const stack = [entryModule]
while (stack.length !== 0) {
const m = stack.pop()
invariant(m, 'Module should exist')
if (m.id in modules) {
continue
}
modules[m.id] = m
const resolutions = m.importedIds.map((id) => /** @type {import("../types.js").ModuleInfo}*/ (moduleMap.get(id)))
for (const dep of resolutions) {
stack.push(dep)
}
}
const sources = Object.fromEntries(
Object.entries(modules).map(([id, module]) => {
return [
id,
{
content:
/** @type {string} */
(module.code),
},
]
}),
)
const emptyString = ''
/**
* @type {['evm.bytecode.object', 'evm.deployedBytecode.object']}
*/
const evmBytecode = ['evm.bytecode.object', 'evm.deployedBytecode.object']
/**
* @type {import('@tevm/solc').SolcInputDescription}
*/
const input = {
language: 'Solidity',
sources,
settings: {
outputSelection: {
'*': {
'*': ['abi', 'userdoc', ...(includeBytecode ? evmBytecode : [])],
...(includeAst ? { [emptyString]: ['ast'] } : {}),
},
},
},
}
/**
* @type {import('@tevm/solc').SolcOutput}
*/
const output = solcCompile(solc, input)
const warnings = output?.errors?.filter(({ type }) => type === 'Warning')
const isErrors = (output?.errors?.length ?? 0) > (warnings?.length ?? 0)
if (isErrors) {
logger.error('Compilation errors:')
logger.error(/** @type {any} */ (output?.errors))
throw new Error('Compilation failed')
}
if (warnings?.length) {
logger.warn(/** @type {any} */ (warnings))
logger.warn('Compilation warnings:')
}
if (includeAst) {
const asts = Object.fromEntries(
Object.entries(output.sources).map(([id, source]) => {
return [id, source.ast]
}),
)
return {
artifacts: output.contracts[entryModule.id],
modules: /** @type {any} */ (modules),
asts: /** @type {any} */ (asts),
solcInput: input,
solcOutput: output,
}
}
return {
artifacts: output.contracts[entryModule.id],
modules: /** @type {any} */ (modules),
asts: /** @type {any} */ (undefined),
solcInput: input,
solcOutput: output,
}
}