Method for generating test program for testing Solidiity smart contract compiler

By generating test programs with high semantic consistency, high defect targeting, and strong specification compatibility, the problems of semantic ambiguity and low defect detection rate in Solidity compiler testing are solved, and efficient compiler defect detection and verification are achieved.

CN120631372APending Publication Date: 2025-09-12SHANDONG UNIV
View PDF 0 Cites 2 Cited by

Patent Information

Application Number
CN202510753537.3
Authority / Receiving Office
CN · China
Patent Type
Applications(China)
Current Assignee / Owner
Filing Date
2025-06-06
Publication Date
2025-09-12

AI Technical Summary

Technical Problem

Existing Solidity compiler testing tools lack sufficient coverage of language features and have difficulty ensuring test effectiveness. They also make it difficult to reproduce optimizer defects, and traditional methods are unable to effectively detect deep compiler defects.

Method used

By inserting inline assembly blocks, restricting dynamic array expansion, using placeholder counters, managing EVM stack variables at block scope, and truncating nested levels, a test program with high semantic consistency, high defect targeting, and strong specification compatibility is generated.

Benefits of technology

It significantly improved the optimizer defect detection rate from less than 5% to 98%, and increased the test program compilability rate from 62% to 98%, providing high-complexity test coverage and a reliable smart contract compiler verification solution.

✦ Generated by Eureka AI based on patent content.

Smart Images

  • Figure CN120631372A_ABST
    Figure CN120631372A_ABST
Patent Text Reader

Abstract

The invention relates to the technical field of computers, and discloses a method for generating a test program for testing a Solidiity smart contract compiler, which comprises the following steps of: defect directional triggering: inserting a Solidiity variable through an inline assembly block to expose an optimizer defect, and limiting a dynamic array / character string to be expanded to at most one element at a time; semantic consistency control: eliminating intermediate representation behavior differences by adopting a placeholder counter, and preventing border crossing through an array length check instruction; and resource conflict arbitration: managing EVM stack variables by using a block scope, and nesting hierarchies based on a depth threshold truncation function. Through triple innovative design of defect directional triggering, semantic consistency control and resource conflict arbitration, the fundamental contradiction among semantic ambiguity, low defect detection rate and resource constraint in the Solidity compiler test is systematically solved.
Need to check novelty before this filing date? Find Prior Art

Description

Technical Field

[0001] The present application relates to the field of computer technology, and in particular to a method for generating a test program for a Solidity smart contract compiler. Background Art

[0002] Current Solidity compiler testing tools face dual challenges: insufficient coverage of language features and difficulty ensuring test effectiveness. Traditional fuzz testing tools, lacking deep adaptation to Solidity language features (such as IR behavior differences and the EVM storage model), have blind spots in verifying key scenarios: IR layer: The new and old code generators have semantic differences in scenarios such as placeholder processing and expression evaluation order, but lack a dedicated verification mechanism; EVM layer: No effective test model has been established for the slot storage mechanism and storage layout mutations caused by dynamic array expansion; Use case generation: Although random strategies ensure diversity, a high proportion of programs cannot be compiled due to lack of standardization (type errors, array out-of-bounds), and undefined behavior (such as ambiguous function parameter order) makes some programs lose the ability to detect defects.

[0003] The deeper bottleneck lies in the difficulty in reproducing optimizer defects and the conflict of test constraints: 1. Optimizer defects are triggered by complex contexts (such as inline assembly variable references), and traditional methods have a very low reproducibility rate. 2. The pursuit of diversity requires relaxing syntactic constraints, but this conflicts with Solidity's strong type system, creating a dilemma where "constraints inhibit the test space, while relaxation introduces invalid noise." This conflict is exacerbated at the semantic level: critical scenarios (such as storage conflicts) require undefined behavior, while the EVM performs rollbacks on some behaviors (such as division by zero), resulting in a nonlinear relationship between testing effort and defect discovery rate.

[0004] It should be noted that the information disclosed in the above background technology section is only used to enhance the understanding of the background of this application, and therefore may include information that does not constitute prior art known to ordinary technicians in this field. Summary of the Invention

[0005] In order to provide a basic understanding of some aspects of the disclosed embodiments, a brief summary is given below. The summary is not an extensive review, nor is it intended to identify key / critical elements or delineate the scope of protection of these embodiments, but rather serves as a prelude to the detailed description that follows.

[0006] The embodiments of the present disclosure provide a method for generating a test program for a Solidity smart contract compiler, which can generate a Solidity test method with high semantic consistency, high defect targeting, and strong specification compatibility to reliably detect deep defects in the compiler.

[0007] In some embodiments, the method includes: Defect-directed triggering: Insert Solidity variables into the inline assembly block to expose optimizer defects, and limit the single expansion of dynamic arrays / strings to at most one element; Semantic consistency control: Use a placeholder counter to eliminate the behavior differences in the intermediate representation, and prevent out-of-bounds access through the array length check instruction; Resource conflict arbitration: Use block scope to manage EVM stack variables, and truncate the function nesting level based on the depth threshold.

[0008] Optionally, the defect-directed triggering includes: After the inline assembly block that writes to memory, insert a Solidity variable that reads the memory; Generate a keccak256 expression with fixed memory content but random length.

[0009] Optionally, the single expansion limit is specifically: Only allow one push() / pop() operation to be executed within the same statement; After the expansion operation, access to array elements within the same statement is prohibited.

[0010] Optionally, the semantic consistency control includes: Initialize the counter count = 0 in the modifier, and generate a placeholder _ and increment count only when count = 0; Insert a require(index < arr.length) instruction before the array index operation.

[0011] Optionally, it further includes: Wrap arithmetic operations within a loop statement with an unchecked{} block; Insert zero value replacement logic divisor = (b == 0)? 1 : b for division operations.

[0012] Optionally, the block scope management includes: Encapsulate stack variables with more than 16 layers within {}; Release the stack slots when the block exits.

[0013] Optionally, the nesting level truncation includes: Preset a maximum nesting depth N; When it is too deep, replace the innermost function parameter with a constant.

[0014] Optionally, the keccak256 generation satisfies: The starting memory address is fixed at 0; The array length is randomly generated within [1, 256].

[0015] Optionally, the method further comprises: marking the variable after the self-increment or self-decrement operation as a risk state; The risk variable is replaced by a random constant when accessed a second time.

[0016] Optionally, the method further includes: prohibiting inline assembly operations on storage array length; Disable verbatim bytecode functions.

[0017] The present disclosure provides a method for generating a test program for a Solidity smart contract compiler, which can achieve the following technical effects: This application systematically addresses the fundamental contradictions of semantic ambiguity, low defect detection rate, and resource constraints in Solidity compiler testing through three innovative designs: defect-directed triggering, semantic consistency control, and resource conflict arbitration. 1. Inline assembly variable injection and single-element expansion restrictions directly expose optimizer vulnerabilities and block undefined behavior, increasing the optimizer defect detection rate from less than 5% to 98%, completely overcoming the bottleneck of traditional methods that make it difficult to reproduce deep-seated defects. 2. Placeholder counters isolate differences in intermediate representation behavior, and dynamic instrumentation (array out-of-bounds checking and overflow control) ensure program legitimacy. This increases the compilability rate of test programs from 62% to 98%, eliminating test noise caused by semantic ambiguity. 3. Block-scoped stack management breaks through the EVM's 16-layer stack limit, and nested depth truncation achieves high-complexity test coverage with zero overflow risk, providing smart contract compilers with a verification solution that is both targeted, reliable, and feasible, and promoting the security upgrade of blockchain's underlying infrastructure.

[0018] The above general description and the following description are exemplary and explanatory only and are not intended to limit the present application. BRIEF DESCRIPTION OF THE DRAWINGS

[0019] One or more embodiments are exemplarily described by corresponding drawings. These exemplary descriptions and drawings do not limit the embodiments. Elements with the same reference numerals in the drawings are shown as similar elements. The drawings do not constitute a scale limitation. In addition, Figure 1 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 2 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 3 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 4 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 5 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 6 This is a schematic diagram of a test program generation strategy for testing a Solidity compiler provided by an embodiment of the present disclosure; Figure 7 It is a flowchart of generating a test program provided by an embodiment of the present disclosure. DETAILED DESCRIPTION

[0020] In order to be able to understand the features and technical content of the embodiments of the present disclosure in more detail, the implementation of the embodiments of the present disclosure is described in detail below in conjunction with the accompanying drawings. The accompanying drawings are for reference only and are not used to limit the embodiments of the present disclosure. In the following technical description, for the sake of convenience of explanation, a full understanding of the disclosed embodiments is provided through multiple details. However, one or more embodiments can still be implemented without these details. In other cases, to simplify the drawings, well-known structures and devices can be simplified for display.

[0021] The terms "first," "second," and the like in the embodiments of the present disclosure are used to distinguish similar objects and are not necessarily used to describe a particular order or precedence. It should be understood that the terms used in this manner are interchangeable where appropriate to facilitate the description of the embodiments of the present disclosure herein. Furthermore, the terms "including," "having," and any variations thereof are intended to cover non-exclusive inclusions.

[0022] Unless otherwise stated, the term "plurality" means two or more.

[0023] In the embodiment of the present disclosure, the character " / " indicates that the preceding and following objects are in an "or" relationship. For example, A / B means: A or B.

[0024] The term "and / or" describes an association between objects, indicating that three relationships can exist. For example, A and / or B means: A or B, or A and B.

[0025] The term "correspondence" may refer to an association relationship or a binding relationship. The correspondence between A and B means that there is an association relationship or a binding relationship between A and B.

[0026] The main goals that this application needs to meet include: consistency, diversity and standardization.

[0027] Consistency design is primarily designed to ensure that generated test programs have a single meaning. During compiler testing, only test programs with a single meaning can produce correct results. If a test program has multiple meanings, it can have unpredictable effects on the test results. The main reasons for Solidity programs having multiple meanings are: undefined behavior and behavioral differences caused by intermediate representations.

[0028] 1. Avoid undefined behavior.

[0029] In computer programming, undefined behavior refers to the results of executing computer code whose behavior in the current program state is not specified by the language standard used. These language standards specify that the semantics of certain operations are undefined, such as accessing array elements out of bounds. Undefined behavior in Solidity primarily includes length operations on storage arrays in inline assembly, function argument evaluation order, dangling references, and verbatim bytecode.

[0030] Second, avoid behavioral differences caused by intermediate representations.

[0031] An intermediate representation (IR) can construct a computer program from the input and can also be used to deduce part or all of the output program back to the input. This means that the IR retains some information about the input while allowing for further annotation and quick query capabilities. In other words, when a compiler compiles source code into target code, it first converts the source code into one or more intermediate representations to facilitate compiler optimization and generate the machine language of the target machine. Solidity can generate bytecode in two different ways: directly from Solidity to bytecode; and using encoding based on the intermediate representation. These two methods have subtle semantic differences, which lead to behavioral differences caused by the intermediate representation, resulting in non-uniform meaning in Solidity programs. The main behavioral differences caused by Solidity's intermediate representation include multiple use of placeholders and inconsistent expression evaluation order.

[0032] 3. Avoid introducing specific keywords Some undefined behaviors and behaviors that vary through intermediate representations are not easy to avoid in a structured way. In these cases, a consistent approach to test procedures is needed that avoids introducing specific keywords.

[0033] Enriching the diversity of generated test programs is to increase the coverage and depth of the tests, thereby more comprehensively detecting compiler defects. Specifically, more diverse test programs can cover more code paths and execution situations, including various boundary conditions and exceptions, and can better test situations that rarely occur in actual application scenarios. By enriching the diversity of test programs, more potential problems can be discovered. Therefore, enriching the diversity of generated test programs is crucial for testing compilers. A diverse generator needs to support a large number of language features and feature combinations. It not only includes some common syntax such as: if / else, function calls, for loops, return, break, continue, etc., but also has the following features unique to Solidity: 1. Function modifiers.

[0034] Solsmith uses Solidity-specific function modifiers payable, pure, and view in function definitions. The pure modifier is used to indicate that a function does not access or modify the contract state and does not interact with other contracts. In Solidity, if a function is declared as pure, the compiler will ensure that the function depends only on its parameters and does not change any state during execution. Similar to pure, view is also a function modifier that is used to indicate that a function does not modify the contract state. However, the difference between the view modifier and the pure modifier is that a function modified with view can access the contract state but does not modify it. Because functions modified with pure and view cannot change the contract state, no fees are incurred.

[0035] 2. Function selector.

[0036] A function selector in Solidity is a special value used to identify a function's signature. A function selector consists of the function's name and argument types, obtained through a hash process. In Solidity, a function selector can be obtained using the special syntax bytes4(keccak256("functionName(argumentType1, argumentType2,...)")). Function selectors play a crucial role in smart contracts by dynamically calling functions in other contracts. By passing a function selector to the call or delegatecall function, a contract can call a specific function in the target contract based on the selector at runtime. This dynamic calling mechanism enables inter-contract interaction and allows different functions to be called at runtime as needed.

[0037] 3. Data location.

[0038] Solidity has three types of data locations: storage, memory, and calldata. State variables in smart contracts are of the storage type by default and are stored on the blockchain. Local variables are generally of the memory type and stored in memory. Calldata, like memory, is stored in memory, but differs in that calldata variables cannot be modified and are generally used to store function parameters.

[0039] Compliance with the Solidity language specification is an important goal of generating test programs. Normative design is designed to ensure that this goal can be achieved.

[0040] The normative design is described in detail below.

[0041] 1. Avoid type errors.

[0042] Although the type of each variable is recorded in the variable list, it is possible to avoid explicit type conversion in the expression, but in order to achieve more diverse arithmetic expressions and enable variables to participate in more types of arithmetic operations, it is necessary to perform explicit type conversion on variables with mismatched types. If you simply put different types of variables in an arithmetic expression, the generated test program will not comply with the Solidity language specification. To solve this problem, this application proposes Figure 1 That is, when generating an arithmetic expression, the type of the selected variable is checked to see if it is consistent with the type of the left value. If not, an explicit type conversion is performed on the variable.

[0043] 2. Prevent division by zero errors.

[0044] In order to ensure the correct implementation of the two arithmetic operations of division and remainder, it is necessary to ensure that the divisor is not zero, otherwise the generated test program may not comply with the Solidity language specification. Therefore, when choosing the divisor for these two arithmetic operations, runtime checks are added, such as Figure 2 This approach prevents the generation of non-Solidity-compliant test programs. However, applying this protection to all division and remainder operations may hinder testing of division and remainder-related content.

[0045] 3. Prevent array index from going out of bounds.

[0046] When an array index is out of bounds or pop() is used even though the array is empty, the generated test program may not comply with the Solidity language specification. Because the test program contains conditional statements and loop statements, it is very difficult to record the size of the array. Therefore, the strategy adopted in this article is to insert explicit checks for array length at the corresponding statement positions when generating the test program to ensure that the generated test program complies with the Solidity standard. The inserted content is as follows: Figure 3 shown.

[0047] 4. Prevent integer overflow.

[0048] When performing arithmetic operations on unrestricted integers, overflow means that the resulting value exceeds the range of the result type. Before version 0.8.0 of Solidity, if an overflow occurs, the arithmetic operation will continue and the result will wrap around to the minimum or maximum value of the data type. However, starting with version 0.8.0, Solidity has changed the integer overflow handling strategy. By default, if an arithmetic overflow occurs, all arithmetic operations are restored. If in version 0.8.0, we hope that arithmetic overflow does not occur by default, we need to record the change in the value of each variable when generating the test program. However, because loop structures and conditional statements are introduced in the generation process, it makes it difficult to control integer overflow. After weighing the pros and cons, we believe that introducing loop structures and conditional statements can greatly enhance the diversity of test programs, so we decided to retain these two grammatical structures and use the unchecked keyword, such as Figure 4 As shown in the following example, arithmetic overflow is handled in the same way as before version 0.8.0. However, this approach may result in relatively weak tests for arithmetic overflow related to the compiler.

[0049] In any project, there are often different goals, constraints, and priorities, and balancing these factors is crucial. The following sections explore the choices and trade-offs faced when generating test programs.

[0050] 1. Limit the number of local variables.

[0051] Because the Ethereum Virtual Machine has limitations on referencing variables in the stack. Although the Ethereum Virtual Machine's stack can contain more than 16 variables, this error will occur if you try to reference a variable in slot 16 or higher. Limiting the number of local variables generated is the simplest way to achieve this. In addition, you can also use block scope. For example Figure 5As shown, the code before the modification has a total of 16 local variables. As mentioned above, referencing variables in slots 16 or higher will result in an error. Therefore, the code before the modification cannot be compiled. In the modified code, the scope of variable p is limited to the block between lines 18 and 20. When the scope of this block is exceeded, variable p is removed from the stack, ensuring that variable a is no longer located above slot 16 on the stack. Therefore, the modified code can be executed correctly. In this way, the above error can be effectively resolved by utilizing block scope. However, both limiting the number of local variables generated and utilizing block scope may have a certain degree of impact on the expressiveness of the test program.

[0052] 2. Selection of function parameters.

[0053] Nested functions refer to using one function as an argument to another function in certain situations. Deeply nested functions can cause problems such as stack overflow or performance degradation. Solsmith has adopted the following strategy: artificially adjust the maximum depth of nesting. This maximizes the expressiveness of the test program while meeting the first design goal. When the maximum nesting depth is set to 1, even if function g can use function f or function g as its own argument (e.g. Figure 6 (The eighth line of code shown in the figure), but since the maximum nesting depth is 1 and function g is already a parameter of function f, function g cannot select function f or function g as its own parameter.

[0054] 3. Version restrictions. The test programs currently generated by Solsmith are primarily designed for testing version 0.8.0. Therefore, some syntax from versions prior to 0.8.0 may not be supported, such as the now keyword, which has been deprecated since version 0.7.0. With the arrival of Solidity 0.8, earlier compiler versions will gradually be de-maintained. At the same time, we also want to be able to test newer compiler versions, so the Solsmith implementation in this article is not suitable for older compiler versions.

[0055] 4. Self-testing ability.

[0056] Because the output of a test program cannot be predicted before it runs, test programs generated by Solsmith lack self-testing capabilities. Differential testing does not require self-testing capabilities, so it can be used to verify the effectiveness of Solsmith. However, differential testing still has certain limitations. Some compilers have similar implementations or use common source code, so the same incorrect output may appear on compilers with different configurations. If this does happen, we cannot detect the problem because of the inherent limitations of differential testing itself, namely the lack of test language restrictions.

[0057] The overall process of code generation is as follows Figure 7 As shown, Solsmith first creates the entire test environment and then generates the function framework. Each function is a top-level block, which contains several code blocks, each of which contains a certain number of nodes. Each node represents an operation in the statement list. When the number of nodes in a code block reaches the upper limit, the current code block is created. It then continues to create the next code block in the current function. When the number of code blocks in a function reaches the upper limit, the current function is created. When the number of functions in a contract reaches the upper limit, the entire contract is created.

[0058] That is, the process includes: creating an environment; generating a function framework; and generating a code block.

[0059] Specifically, the steps create an environment to generate the content required by the smart contract, ensuring proper compilation and facilitating subsequent processes. This environment construction primarily includes the following components: a smart contract framework, event statement definitions, a set of state variables, a set of dynamic arrays, and a decorator. Each component will be described in detail below.

[0060] The smart contract framework is essential for ensuring that smart contracts compile properly. It primarily includes the SPDX (Software Package Data Exchange) license identifier and the smart contract name. SPDX is a standardized way to accurately record and communicate software license information within computer software and other related documentation.

[0061] In Solidity, events are used to publish notifications and log messages within smart contracts, allowing them to communicate with the outside world. In Solsmith, events are used to record the contents of state variables so that their values ​​can be compared during experimental verification.

[0062] In Solidity, variables are categorized into three types based on their scope: state variables, local variables, and global variables. State variables are variables whose data is stored on the blockchain and are accessible to all functions within a contract, resulting in high gas consumption. State variables are declared within a contract but outside a function. Local variables are valid only during execution and become invalid after the function exits. Local variable data is stored in memory, resulting in low gas consumption. In the test programs generated by Solsmith, state variables are of type uint256 or int256 and are initialized with a random constant value upon definition. The variable names and types of all state variables are stored in a state variable list. Some state variables are of type constant and are placed in a separate constant variable list.

[0063] Arrays generated by Solsmith have random lengths, their elements are fixed at uint256 type, and are initialized with random constants when defined. To facilitate more operations on arrays, arrays in the test program are dynamic. The array name and initial length are stored in the array variable list. These variables and arrays are then used in the generated code to form separate expressions from the function's local variables.

[0064] Decorators are used to modify and constrain function behavior. They can be used as preconditions for function execution and to reduce code redundancy. A decorator generated by Solsmith contains expressions composed of state variables and arrays, along with at most one placeholder. These decorators are randomly assigned to each function, but it's possible for no function to use any decorators at all.

[0065] During the function skeleton generation phase, Solsmith uses a random method to determine the properties of the generated function skeleton, such as whether it has a return value, the type of the return value, the visibility of the function, and whether it uses modifiers. After determining the properties of the function skeleton, Solsmith uses a statement list to randomly select each node in the function. This statement list contains the following operations: Declare a new local variable of a randomly chosen integer type and assign it a random constant value.

[0066] Assigns the result of a random arithmetic expression to an existing variable.

[0067] Starts a new conditional statement.

[0068] Starts a new loop statement.

[0069] Begins a new inline assembly statement.

[0070] Operations on arrays, such as push().

[0071] During the function framework generation phase, a previously defined event statement is called at the end of each function to record state variable information. The state variable values ​​of the same program under different compiler configurations are then compared to determine if any compiler bugs have been discovered. Only state variables are compared because they are the only ones that store their values ​​on the blockchain; other local variables remain unaffected after the program completes.

[0072] In addition, the statements in the present invention mainly include the following types: Expressions, which perform specific operations on variables; Loop statement, repeatedly executes a section of code until the exit condition is met; Conditional statements, which determine whether to execute statements in a code block based on the conditions; Inline assembly statements, insert inline assembly code directly into Solidity code to achieve more precise control.

[0073] Each statement will be described in detail below.

[0074] An expression consists of operands and operators that are used to calculate a value. It can be simple, such as a variable or a constant, or complex, such as an arithmetic or logical operation. Variables used in expressions include state variables, arrays, and local variables. Common expression types include: Arithmetic expressions: Perform arithmetic operations, such as "5 + 3 * (10 / 2)".

[0075] Logical expressions: Perform logical operations, such as "a || b".

[0076] Relational expressions: compare two values, such as "a == b".

[0077] Assignment expression: Assigns a value to a variable, such as "a = b".

[0078] Function call expression: calls a function and returns the result, such as "func_a(42)".

[0079] Index expression: Accesses an element in an array or map, such as "Arraya[0]".

[0080] Ternary conditional expression: returns different values ​​depending on the conditions, for example "x == 0 ? 1 : x".

[0081] In Solsmith, loop statements are designed to limit the nesting depth of loops and to restrict the scope of iteration counters to the loop statement itself. This allows loops of the same depth to share the same iteration counter, providing better control over the number of iterations. Loop statements come in two main forms: The iteration counter is initialized in the loop header: You can use:

[0082] In this form, the iteration counter initialization expression appears in the for loop header. This means that the iteration counter's scope is limited to the loop. In the loop condition, the iteration counter is less than a random constant. The iteration expression is used to increment the iteration counter.

[0083] The iteration counter is initialized before the loop statement:

[0084] In this form, the iteration counter initialization expression appears before the for statement, but the entire for loop is placed within a block. This is done to limit the scope of the iteration counter to only within the loop statement. Similar to the first form, the loop condition is fixed to ensure that the iteration counter is less than a random constant, and the iteration expression is used to increment the iteration counter.

[0085] Conditional statements determine whether to execute a specific block of code based on the truth of a condition. These conditions are described using the logical expressions or relational expressions mentioned above. A relational expression consists of two variables and a comparison operator, such as "a>b". Comparison operators used include greater than (>), less than (<), equal to (==), and not equal to (!=). Logical expressions, on the other hand, consist of multiple sets of relational expressions connected by logical operators. For example, "(a>b)&&(c>d)" means that the corresponding code block will be executed when both conditions are met. Logical operators used include logical OR (||), logical AND (&&), and logical NOT (!). The result of a conditional statement's execution depends on the truth of the conditional expression. If the condition is true, the code block associated with the conditional statement is executed; otherwise, execution is skipped. Similar to loop statements, the generation of conditional statements is also limited in depth.

[0086] In Solidity, the language used for inline assembly is called Yul. An inline assembly block consists of assembly{code block}, where the code within the curly braces is the Yul language code. Similar to ordinary Solidity code, each inline assembly block can contain expressions, conditionals, loops, and function-related statements that conform to the Yul specification.

[0087] In order to generate a test program that is more conducive to exposing Solidity compiler defects, only rely on Figure 7 The code generation process in [1] is insufficient. Therefore, this paper proposes a series of unique test program generation strategies. To more effectively test optimizer-related issues in the Solidity compiler, this paper includes code that frequently enables optimizations in the generated test programs. To maintain consistency across different environments, the generated code avoids undefined behavior in Solidity and ensures that there are no behavioral differences introduced by intermediate representations.

[0088] This application optimizes memory usage in inline assembly.

[0089] Specifically, the Yul optimizer examines all memory writes within an outer Yul block and removes them as unused if they are not subsequently read. This strategy applies when the outer Yul block constitutes the entire Yul program, a situation that always applies to Yul code generated through the new via-IR pipeline. In this pipeline, inline assembly blocks are not optimized individually but are optimized as part of the entire Yul input.

[0090] However, in a traditional code generation pipeline, the Yul optimizer operates on each inline assembly block independently if the inline assembly block does not reference any variables defined in the surrounding Solidity code. Therefore, if memory writes in an inline assembly block are not subsequently read in the same assembly block, those writes will be removed, even if subsequent inline assembly blocks may access that memory.

[0091] In traditional code generation pipelines, inline assembly blocks that access Solidity variables do not run the Yul optimizer, which significantly reduces the number of affected cases. Most inline assembly blocks either read or write values ​​from the surrounding Solidity code, are completely self-contained, or control program flow until the program ends. Therefore, such defects are unlikely to occur in practice. In order to make it easier to discover related problems, the present invention deliberately introduces local variables and state variables in Solidity in the inline assembly in a read manner to trigger the Yul optimizer more frequently.

[0092]

[0093] As shown in the code above, if the traditional code generation pipeline without the optimizer is used, the content in the first inline assembly code block will not be deleted, x can be correctly assigned, and the function can obtain the expected result. However, if the code generation pipeline with the optimizer is used, when the second inline assembly code block is executed, since the memory written in the first inline assembly code block has never been used, the optimizer will delete the content that has been written, resulting in the function f finally returning a value of 0.

[0094] Regarding removing storage writes, the code example is shown below.

[0095]

[0096] An error may occur if the contract contains the following steps: (1) Storage write.

[0097] (2) Call the return(...) or stop() function.

[0098] (3) Any control flow path that continues does one of the following: overwrites the store write in (1); rolls back.

[0099] If the initial storage write could be read (either directly or in any internal or external call) between (1) and (3), then it is not removed. However, note that in the presence of an optimizer, it is not always easy to determine whether a storage read at the Solidity level actually translates to a load instruction in assembly. For example, the Load Resolver step could use the contents of a value previously written by the contract, directly replacing it with sload() with the value that will be read.

[0100] In traditional code generation, all of these steps, (1) to (3), need to occur within a single inline assembly block, and the function call in (2) must be a call to a user-defined assembly function. Note that during traditional code generation, the Yul optimizer only operates on inline assembly snippets that do not reference Solidity variables, further reducing the likelihood of being affected.

[0101] However, when using code generation from an intermediate representation (IR), the entire contract is first converted to Yul and then optimized as a whole. In this case, (1) and (3) can occur in Solidity code, and only the function called in (2) needs to involve an inline assembly block that calls return(...) or stop() (the compiler will not generate return(...) or stop() instructions unless inline assembly is used). Due to inlining, this inline assembly block may also appear only within another nested function call, i.e., any call in (2) that can either return to the caller or terminate through assembly can be affected by any chain of nested calls.

[0102] The Keccak-256 function takes two parameters: m, an address, and p, a non-negative integer representing the length of the data to be read. The function calculates the hash value of the data starting at address m and ending at m+p-1. If the memory contents to be read by the keccak256 function can be determined at compile time, the resulting Keccak-256 hash value will be the same even if the second parameter (the length of the data to be calculated) is different.

[0103]

[0104] In the code shown above, the hash values ​​cannot be calculated at compile time, but the optimizer still treats them as equal. This produces the same result. Therefore, Solsmith will prefer to include keccak256 functions in inline assembly code during code generation to enable the Solidity compiler's optimizer more effectively, thereby discovering more keccak256-related bugs.

[0105] Solidity can generate Ethereum Virtual Machine (EVM) bytecode in two different ways: the old encoding, which goes directly from Solidity to EVM bytecode; and the new, or intermediate representation-based, encoding, which uses Yul's Intermediate Representation (IR). The goal of introducing an IR-based code generator is to make code generation more transparent and auditable, and to enable more powerful cross-function optimization passes. Because Solidity compiler developers prefer smart contract developers not to rely on certain behaviors, there are subtle semantic differences between the old and IR-based code generators.

[0106] For example, using the placeholder "_;" multiple times in a decorator can lead to unexpected results. In the old code generator, each function parameter and return variable has a fixed slot on the stack. If a function is executed multiple times due to multiple use of a placeholder, or if it's used in a loop, changes to the function's parameters or return value are visible in the next execution. The new code generator, however, uses actual functions to implement decorators and passes the function parameters along. This means that multiple uses of a function body will receive the same parameter values, while return variables are reset to their default values ​​(zero) on each execution. In the code shown above, using the old code generator, the return variable ret is initialized to zero before the first use of the placeholder and then set to 1 after the first use. The second use of the placeholder doesn't reinitialize ret, and its value isn't modified again (because active is set to false), so ret retains the original value. Therefore, the result of using the old code generator is 1. For the new code generator, ret is the return parameter and is reinitialized before each use of the placeholder, so the return result is 0.

[0107] To avoid changes caused by using new and old code generators, this behavior should be avoided in the generated code. The approach adopted by Solsmith is to define a counter and initialize it to 0 when generating the decorator. Every time a placeholder is randomly generated on a certain line, the counter is incremented by one. When generating the statement of each line, it is necessary to check whether the counter is zero. If it is zero, it means that no placeholder has been generated before, so this line can generate a placeholder. If the counter is not zero, it means that there has been a placeholder before, so this line cannot generate a placeholder, and the generated statement will be randomly selected again until it is no longer a placeholder.

[0108] 2. Inconsistent expression evaluation order With the old code generator, the order in which expressions are evaluated is unpredictable. With the new code generator, an attempt is made to evaluate expressions from left to right, but this order is not guaranteed. This may result in different results.

[0109] For example, in the following code, for f(1), if the old code generator is used, since the old code generator does not specify the order of expression evaluation, it may process ++a first and then +a, which is 2+2, and return 4. Alternatively, it may process the latter a first and then ++a, which is 2+1, and return 3. If the new code generator is used, it will generally process ++a first and then +a, and the result will be 2+2=4. However, the new code generator does not guarantee that it will return 4.

[0110]

[0111] To avoid this situation, Solsmith uses a hash table to record the names of currently used variables when generating each arithmetic expression. Solsmith first selects the variable used at the current position, and then randomly adds an increment or decrement operation at that position. If it is used alone, the corresponding value in the hash table is 1; if there is an increment or decrement operation, the corresponding value in the hash table is 2. If the variable is selected again at a later position, it determines what operations are allowed for the corresponding value of the variable in the hash table, and performs the allowed operations. If the variable was used alone before, it can still be used alone later. However, if the variable was previously incremented or decremented, a random constant will be generated to replace the variable when the variable is selected again.

[0112] Undefined behavior refers to the behavior of a program that is not explicitly defined in certain situations, for reasons such as performance or portability. This means that when encountering undefined behavior, the compiler will always be correct regardless of how it handles it. To prevent this from occurring when testing the Solidity compiler, the test programs generated by Solsmith must avoid undefined behavior. Searching for "undefined behavior" in the official Solidity documentation and the Solidity compiler repository on GitHub, and manually filtering the results, revealed four instances of undefined behavior in the Solidity compiler. Their details are listed below.

[0113] 1. Perform length operations on storage type arrays in inline assembly Storage slots are persistent storage locations used to store contract state. State variables declared in a smart contract are allocated to these slots. Under normal circumstances, the Solidity compiler keeps unused storage slots empty. However, if you use pop() to pop the contents of an array, the pop() operation explicitly writes zero to the slot containing the popped value. However, if you simply shorten the array length in inline assembly, the remaining storage operations become uncontrollable. To avoid this, Solsmith avoids array operations in relevant inline assembly statements.

[0114] The order in which function parameters are calculated is shown in the following table.

[0115]

[0116] This example illustrates the order in which function parameters are evaluated in Solidity. In Solidity, the require function is an assertion function used to perform conditional checks during contract execution. It verifies that certain conditions are met. If the conditions are not met, contract execution is terminated and all state changes are rolled back. Its first argument is a Boolean expression representing the condition to be checked, and the second argument is a string, providing an error message if the condition is not met. If the second argument is a function that returns a string, the function is executed even if the first argument is true. In the code shown in the table above, if function parameters are evaluated from left to right, the initial value of variable x is not 1337. Therefore, if the condition is true, the setValue function is executed to set x to 1337, and the function f returns 1337. If function parameters are evaluated from right to left, the setValue function is executed first to set variable x to 1337. This causes the condition x!=1337 to be false, resulting in contract execution being terminated and the state being rolled back. This example illustrates the order in which function parameters are evaluated. However, the official description states that function parameters are evaluated from left to right in most cases, but this order is not guaranteed to be true. Therefore, to avoid this undefined behavior, Solsmith tries to avoid relying on the order of parameter evaluation in defined functions.

[0117] Solidity offers two storage options for dynamic arrays and strings: short layout and large layout. Short layout optimizes dynamic data and string storage. In short layout, the length information for dynamic arrays and strings is stored directly in the same slot as the data itself. This saves storage space and reduces the number of slots required to store the length information, but the length is limited. In large layout, the length information for dynamic arrays and strings is stored in separate slots, while the data is stored in other slots. Large layout storage consumes more slots but allows for larger array lengths. The difference between short and large layouts is the length of the dynamic array or string. For the current version of the Solidity compiler, short layout is used when the length of a dynamic array or string does not exceed 31 characters; if it exceeds 31 characters, large layout is used.

[0118] As shown in the table above, the string x initially has a length of 30 and is stored using the short layout. When the first x.push() call is used, the string x is still stored using the short layout, so x.push() correctly returns a reference to the first element of the string x. However, when the second x.push() call is used, the string x is stored using the large layout. The element referenced by x.push() has now moved to the new data area, while the location pointed to by x.push() is now part of the data field. While the dangling reference described above does cause problems, it does not appear to be undefined behavior. This is simply how the current compiler handles dangling references, and future versions of the compiler may modify their behavior. Therefore, this situation should be avoided. Solsmith only expands dynamic arrays and strings by at most one element during assignments and avoids accessing dynamic arrays or strings in the same statement.

[0119] Regarding verbatim bytecode: Solidity includes a set of built-in functions called verbatim. These functions allow developers to create bytecode for opcodes unknown to the Yul compiler. They also allow developers to create bytecode sequences that are not modified by the optimizer. The verbatim functions have the form verbatim_i_o("",...), where n is a decimal number between 0 and 99 specifying the number of input variables or stack slots, m is a decimal number between 0 and 99 specifying the number of output variables or stack slots, and data is a string containing the bytecode. The following table shows how to use them.

[0120]

[0121] In verbatim, hex "600202" represents the hexadecimal numbers corresponding to a series of EVM opcodes. Among them, 60 represents the opcode PUSH1, which puts a one-byte value on the top of the stack. The subsequent 02 is the value to be placed on the top of the stack by the PUSH1 operation, that is, hex "6002" puts 02 on the top of the stack. The last 02 represents the opcode MUL, and the MUL operation multiplies the two elements at the top of the stack and puts the result back on the top of the stack. Therefore, the code operation in the above table is to multiply the value of the variable x by 2 and assign the result to the variable double. Since verbatim can be used to generate arbitrary opcodes, even bytecodes unknown to the Solidity compiler, care must be taken when using verbatim with the optimizer. Even if the optimizer is turned off, the code generator must ensure that the stack layout is correct, which means that, for example, using verbatim to modify the stack height may result in undefined behavior. In order to avoid undefined behavior, some restrictions are required on verbatim bytecode: (1) Control flow should not jump into or out of a verbatim block, but can jump within the same verbatim block. (2) The stack contents should not be accessed except for input and output parameters.

[0122] (3) The difference in stack heights should be exactly m - n (output slots minus input slots).

[0123] (4) Verbatim bytecode cannot make any assumptions about the surrounding bytecode. All required parameters must be passed in as stack variables.

[0124] In order to avoid undefined behavior caused by improper use of verbatim, this article adopts the approach of avoiding the use of verbatim related functions in Solsmith.

[0125] The above description and the accompanying drawings sufficiently illustrate the embodiments of the present disclosure to enable those skilled in the art to practice them. Other embodiments may include structural, logical, electrical, process and other changes. The embodiments represent only possible variations. Unless expressly required, individual components and functions are optional, and the order of operations may vary. Parts and features of some embodiments may be included in or replace parts and features of other embodiments. Moreover, the terms used in this application are only used to describe the embodiments and are not used to limit the scope of protection. As used in the description herein, unless the context clearly indicates otherwise, the singular forms "a", "an" and "the" are intended to also include the plural forms. Similarly, the term "and / or" as used in this application refers to any and all possible combinations of one or more associated listings. In addition, when used in this application, the term "comprise" and its variations "comprises" and / or comprising refer to the presence of stated features, wholes, steps, operations, elements, and / or components, but do not exclude the presence or addition of one or more other features, wholes, steps, operations, elements, components and / or groups thereof. In the absence of further restrictions, an element defined by the sentence "comprising a..." does not exclude the presence of other identical elements in the process, method or device that includes the element. In this article, each embodiment may focus on the differences from other embodiments, and the same and similar parts between the various embodiments can be referenced to each other. For the methods, products, etc. disclosed in the embodiments, if they correspond to the method part disclosed in the embodiments, then the relevant parts can be referred to the description of the method part.

[0126] Those skilled in the art will appreciate that the units and algorithm steps of each example described in conjunction with the embodiments disclosed herein can be implemented in electronic hardware, or a combination of computer software and electronic hardware. Whether these functions are performed in hardware or software may depend on the specific application and design constraints of the technical solution. The technicians may use different methods to implement the described functions for each specific application, but such implementation should not be considered to be beyond the scope of the embodiments of the present disclosure. The technicians will clearly understand that, for the convenience and brevity of description, the specific working processes of the systems, devices and units described above can refer to the corresponding processes in the aforementioned method embodiments and will not be repeated here.

[0127] In the embodiments disclosed herein, the disclosed methods and products (including but not limited to devices and equipment, etc.) can be implemented in other ways. For example, the device embodiments described above are merely illustrative. For example, the division of units may be merely a logical functional division. In actual implementation, other division methods may be used, such as combining or integrating multiple units or components into another system, or omitting or disabling some features. In addition, the coupling or direct coupling or communication connection shown or discussed between each other may be through some interface, or the indirect coupling or communication connection between devices or units may be electrical, mechanical, or other forms. The units described as separate components may or may not be physically separate, and the components shown as units may or may not be physical units, i.e., they may be located in one place or distributed across multiple network units. Some or all of these units may be selected to implement the present embodiments according to actual needs. In addition, the functional units in the embodiments of the present disclosure may be integrated into a single processing unit, each unit may exist physically separately, or two or more units may be integrated into a single unit.

[0128] The flowcharts and block diagrams in the accompanying drawings show the possible implementation architectures, functions and operations of the systems, methods and computer program products according to the embodiments of the present disclosure. In this regard, each box in the flowchart or block diagram can represent a module, program segment or part of the code, and the module, program segment or part of the code contains one or more executable instructions for implementing the specified logical functions. In some alternative implementations, the functions marked in the box can also occur in an order different from that marked in the accompanying drawings. For example, two consecutive boxes can actually be executed substantially in parallel, or they can sometimes be executed in the opposite order, which can depend on the functions involved. In the descriptions corresponding to the flowcharts and block diagrams in the accompanying drawings, the operations or steps corresponding to different boxes can also occur in an order different from that disclosed in the description, and sometimes there is no specific order between different operations or steps. For example, two consecutive operations or steps can actually be executed substantially in parallel, or they can sometimes be executed in the opposite order, which can depend on the functions involved. Each block in the block diagrams and / or flowcharts, and combinations of blocks in the block diagrams and / or flowcharts, may be implemented by a dedicated hardware-based system that performs the specified function or action, or may be implemented by a combination of dedicated hardware and computer instructions.

Claims

1. A method for generating a Solidity compiler test program, characterized in that: Include: Defect-oriented triggering: Expose the optimizer defect by inserting Solidity variables into the inline assembly block, and limit the single expansion of dynamic arrays / strings to at most one element; Semantic consistency control: Use a placeholder counter to eliminate the behavior differences in the intermediate representation, and prevent out-of-bounds access through the array length check instruction; Resource conflict arbitration: Use block scope to manage EVM stack variables, and truncate the function nesting level based on the depth threshold.

2. The method according to claim 1, characterized in that The defect-oriented triggering includes: After the inline assembly block that writes to memory, insert a Solidity variable that reads the memory; Generate a keccak256 expression with fixed memory content but random length.

3. The method according to claim 1, characterized in that The single expansion limit is specifically: Only allow one push() / pop() operation to be executed within the same statement; After the expansion operation, access to array elements within the same statement is prohibited.

4. The method according to claim 1, wherein The semantic consistency control includes: Initialize the counter count = 0 in the modifier, and only generate a placeholder _ and increment count when count = 0; Insert a require(index < arr.length) instruction before the array index operation.

5. The method according to claim 4, characterized in that Also include: Wrap arithmetic operations within a loop statement with an unchecked{} block; Insert zero value replacement logic divisor = (b == 0)? 1 : b for division operations.

6. The method according to claim 1, characterized in that The block scope management includes: Stack variables with more than 16 layers are encapsulated within {}; Release the stack slot when exiting the block.

7. The method according to claim 1, characterized in that ​ ​ ​ 8. The method according to claim 2, characterized in that ​ ​ ​ 9. The method according to claim 1, characterized in that ​ ​ ​ 10. The method according to claim 1, characterized in that ​ ​ ​

Citation Information

Cited By

  • Method and system for detecting inconsistency between Solidity compilers

    CN121636364A

  • A method and system for detecting inconsistencies between solidity compilers

    CN121636364B