You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

solc-typed-ast编译Solidity合约字节码异常致以太坊部署失败

问题

使用Feathers后端对Next.js前端用户输入的Solidity智能合约做程序化编译,因为采用TypeScript且需要自动识别编译器版本等特性,选用了ConsenSys的solc-typed-ast包。编译看似成功,但生成的字节码和Remix手动编译结果有差异,部署到以太坊时交易失败,区块链浏览器报错:Warning! Error encountered during contract execution [invalid opcode: opcode 0x5f not defined]。

后端编译服务代码如下:

import { Application } from '../../declarations';
import {  compileSol } from 'solc-typed-ast';

/**
 * A mapping from several Smart Contracts' Name to their Solidity Code or ABI
 * @typedef {Object} CodeMap
 * @property {string} key - The name of the Smart Contract
 * @property {string} value - The Solidity Code or ABI of the Smart Contract
 * @example
 * {
 *    "Contract1": "contract Contract1 { ... }",
 *    "Contract2": "contract Contract2 { ... }",
 * }
 */

export type CodeMap = { [key: string]: string };
/**
 * Solidity Compiler Service
 * This service compiles the Solidity code for a given Project using the solc-typed-ast library.
 */
export class SolidityCompiler {
    ...
    async find(params?: Params | undefined): Promise<CompilationResult[]> {
        ...
        // Fetch the code from the database
        const codeToCompile = JSON.parse(template.code) as CodeMap;

        // Initialize the compileResults array
        const compileResults: CompilationResult[] = [];

        try {
            // Iterate over each contract in the codeToCompile map
            for (const [contractName, contractCode] of Object.entries(codeToCompile)) {
                // Create a temporary directory and file path for each contract
                const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'solidity-'));
                const filePath = path.join(tempDir, `${contractName}.sol`);

                // Write the contract's Solidity code to a file
                fs.writeFileSync(filePath, contractCode);

                try {
                    // Compile the Solidity code
                    const result = await compileSol(filePath, 'auto');

                    // Extract the compiled contract names
                    const compiledContractNames = Object.keys(result.data.contracts[filePath]);
                    // Iterate through compiled contracts
                    compiledContractNames.forEach((compiledContractName) => {
                        const compiledContract = result.data.contracts[filePath][compiledContractName];
                        // Store the result together with the compiled contract name
                        compileResults.push({
                            contractName: compiledContractName,
                            success: true,
                            abi: compiledContract.abi,
                            bytecode: compiledContract.evm.bytecode.object,
                            error: undefined,
                            compilerVersion: result.compilerVersion,
                        });

                        console.log(`Solidity Compiler Service: ${compiledContractName} compiled successfully`);
                    });
                } catch (compileError: any) {
                    // Store possible errors together with the contract name
                    compileResults.push({
                        contractName,
                        success: false,
                        abi: undefined,
                        bytecode: undefined,
                        error: compileError.message,
                    });

                    console.error(`Solidity Compiler Service: ${contractName}`, compileError.message);
                }

                // Clean up the temporary file and directory
                fs.unlinkSync(filePath);
                fs.rmdirSync(tempDir);
            }
        } catch (error: any) {
            throw new Error(`Solidity Compilation Error: ${error.message}`);
        }

        return compileResults;
    }
}

数据库中的template.code示例:

"{\"ERC20\":\"pragma solidity ^0.5.0;\\n\\n// Flat ERC20\\n// Updated Jun 7 2019\\n\\n/**\\n * @dev Wrappers over Solidity's arithmetic operations with added overflow\\n * checks.\\n *\\n * Arithmetic operations in Solidity wrap on overflow. This can easily result\\n * in bugs, because programmers usually assume that an overflow raises an\\n * error, which is the standard behavior in high level programming languages.\\n * SafeMath restores this intuition by reverting the transaction when an\\n * operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n */\\nlibrary SafeMath {\\n    /**\\n     * @dev Returns the addition of two unsigned integers, reverting on\\n     * overflow.\\n     *\\n     * Counterpart to Solidity's + operator.\\n     *\\n     * Requirements:\\\n     * - Addition cannot overflow.\\n     */\\n    function add(uint256 a, uint256 b) internal pure returns (uint256) {\\n        uint256 c = a + b;\\n        require(c >= a, \\\"SafeMath: addition overflow\\\");\\n\\n        return c;\\n    }\\n\\n    /**\\n     * @dev Returns the subtraction of two unsigned integers, reverting on\\n     * overflow (when the result is negative).\\n     *\\n     * Counterpart to Solidity's - operator.\\n     *\\n     * Requirements:\\\n     * - Subtraction cannot overflow.\\n     */\\n    function sub(uint256 a, uint256 b) internal pure returns (uint256) {\\n        require(b <= a, \\\"SafeMath: subtraction overflow\\\");\\n        uint256 c = a - b;\\n\\n        return c;\\n    }\\n\\n    /**\\n     * @dev Returns the multiplication of two unsigned integers, reverting on\\n     * overflow.\\n     *\\n     * Counterpart to Solidity's * operator.\\n     *\\n     * Requirements:\\\n     * - Multiplication cannot overflow.\\n     */\\n    function mul(uint256 a, uint256 b) internal pure returns (uint256) {\\n        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\\n        // benefit is lost if 'b' is also tested.\\n..."
原因分析与解决方案

核心原因

报错中的0x5f是PUSH0指令,这个指令是以太坊上海升级后引入的,仅被Solidity 0.8.20及以上版本的编译器支持。但你的合约声明的是pragma solidity ^0.5.0;,该版本编译器根本不会生成PUSH0指令。

问题出在compileSol(filePath, 'auto')的'auto'参数:solc-typed-ast的auto模式会自动选择最新兼容的编译器版本,而非合约pragma声明的版本范围内的合适版本。比如你的合约要求^0.5.0,但auto可能选了0.8.20+的版本,编译出的字节码包含旧链不支持的指令,导致部署失败。

另外,你提取的compiledContract.evm.bytecode.object是未链接字节码,如果合约引用了外部库(比如示例中的SafeMath),未链接字节码里的占位符也会导致部署异常,但本次问题的核心是指令版本不兼容。

解决步骤

  1. 强制匹配pragma声明的编译器版本
    不要用'auto',先解析合约代码中的pragma语句提取版本范围,再指定对应版本编译:

    const contractCode = fs.readFileSync(filePath, 'utf8');
    const pragmaMatch = contractCode.match(/pragma solidity\s+([^;]+);/);
    if (pragmaMatch) {
        const versionRange = pragmaMatch[1];
        // 使用合约声明的版本范围编译
        const result = await compileSol(filePath, versionRange);
    }
    

    这样编译器会选择符合^0.5.0的最高版本(比如0.5.17),不会生成高版本专属指令。

  2. 处理库链接问题
    如果合约使用了外部库,编译后需要完成字节码链接:

    • 部署时链接:确保部署流程先部署库合约,再将库地址替换到主合约字节码的占位符中。
    • 编译时内嵌:在编译配置中添加参数,让库代码直接内嵌到主合约字节码中,避免链接步骤。
  3. 验证字节码一致性
    在Remix中切换到合约pragma指定的版本编译,对比生成的字节码与你的服务编译结果,确认版本匹配后字节码是否一致,排除其他编译配置差异。

内容的提问来源于stack exchange,提问作者Konny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 02:34:55