diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ea100159faf..3f3ca230a1f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -239,49 +239,61 @@ jobs: contents: read steps: + - name: Prepare artifact workspace + id: artifact-workspace + shell: bash + run: | + artifact_work_dir="${RUNNER_TEMP}/solidity-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + mkdir -p "${artifact_work_dir}/github" + echo "path=${artifact_work_dir}" >> "${GITHUB_OUTPUT}" + - name: Download solc-windows uses: actions/download-artifact@v7 with: name: solc-windows - path: github + path: ${{ steps.artifact-workspace.outputs.path }}/github - name: Download solc-macos uses: actions/download-artifact@v7 with: name: solc-macos - path: github + path: ${{ steps.artifact-workspace.outputs.path }}/github - name: Download solc-linux uses: actions/download-artifact@v7 with: name: solc-linux - path: github + path: ${{ steps.artifact-workspace.outputs.path }}/github - name: Download solc-linux-arm uses: actions/download-artifact@v7 with: name: solc-linux-arm - path: github + path: ${{ steps.artifact-workspace.outputs.path }}/github - name: Download solc-ems uses: actions/download-artifact@v7 with: name: solc-ems - path: github + path: ${{ steps.artifact-workspace.outputs.path }}/github - name: Restore executable permissions + working-directory: ${{ steps.artifact-workspace.outputs.path }} run: chmod +x github/solc-static-linux github/solc-static-linux-arm github/solc-macos - name: List all artifacts + working-directory: ${{ steps.artifact-workspace.outputs.path }} run: | ls -R github/ - name: Create tarball for use on github + working-directory: ${{ steps.artifact-workspace.outputs.path }} run: | cd github tar --create --file ../github-binaries.tar * - name: Rename binaries to solc-bin naming convention + working-directory: ${{ steps.artifact-workspace.outputs.path }} run: | full_version=$( github/solc-static-linux --version | @@ -301,6 +313,7 @@ jobs: - name: Upload to S3 shell: bash + working-directory: ${{ steps.artifact-workspace.outputs.path }} env: S3_BUCKET: ${{ secrets.S3_BUCKET_PROD }} run: | @@ -313,5 +326,13 @@ jobs: aws s3 cp solc-static-linux "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors aws s3 cp solc-static-linux-arm "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors aws s3 cp soljson.js "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors - - cd .. && rm -rf github solc-bin *.tar + + - name: Clean artifact workspace + if: always() + shell: bash + env: + ARTIFACT_WORK_DIR: ${{ steps.artifact-workspace.outputs.path }} + run: | + if [[ -n "${ARTIFACT_WORK_DIR}" ]]; then + rm -rf -- "${ARTIFACT_WORK_DIR}" + fi diff --git a/docs/bugs.json b/docs/bugs.json index 06a74c3b2835..eff0a5a01b7f 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -6,7 +6,7 @@ "description": "When the compiler detects that a custom layout specifier puts contract's static storage area too close to the end of the address space, it emits a warning. To make the warning more useful, the compiler tries to point at the last storage variable in that area. For this reason it walks the linearized inheritance hierarchy in reverse (from the least to the most derived). The list is calculated once and stored in an AST annotation called ``linearizedBaseContracts``. The direct cause of the bug was the fact that the code that reverses the list was doing it in place rather than on a copy, modifying the annotation. This effectively reversed the order of base contracts seen by any component that runs after layout checks: later phases of analysis, AST export, code generator, SMTChecker, etc. The observable effect was a reversed order of state variable initialization, constructor invocation, virtual function/modifier resolution, leading either to miscompilations or internal compiler errors, depending on the specific usage. Since the source of the bug was in the analysis stage, it was independent of the codegen pipeline or optimizer settings. The main condition necessary to trigger the bug was the presence of the warning in the output. The other is presence of language constructs whose evaluation depends on the inheritance order. While potential effects are very serious, this requirement excludes the vast majority of contracts as intentionally placing the storage variables in the last 2**64 slots is highly discouraged, which was actually the main reason for adding this warning.", "link": "https://blog.soliditylang.org/2026/07/09/inheritance-order-reversal-on-storage-end-warning-bug/", "introduced": "0.8.29", - "fixed": "0.8.36", + "fixed": "0.8.30", "severity": "medium" }, { @@ -16,7 +16,7 @@ "description": "To work around the 16-slot stack access limit of the EVM, the IR-based code generator can move local variables of stack-too-deep functions to fixed memory offsets. This relocation is unsound for recursive functions: a fixed offset would be shared by all activations of the function, so a recursive call would overwrite the caller's value. The stack limit evader therefore must not relocate variables of functions that are part of a recursive call chain. To this end, the call graph was searched for cycles using a path-based depth-first search that, once a function had been fully explored and popped from the search path, short-circuited on it on any later visit. As a result, a function shared between several intersecting cycles could be reached first through a path that did not yet close a cycle through it, get marked as finished, and then be skipped when a later path would have revealed that it does lie on a cycle. Such a function was misclassified as non-recursive. When a misclassified function was complex enough for the stack limit evader to relocate some of its variables, those variables were moved to fixed memory offsets and silently corrupted on recursion, producing wrong results rather than a compile-time error. Triggering the bug requires the IR pipeline, a set of mutually recursive functions whose call graph contains intersecting cycles, at least one of the functions in an undetected part of a cycle being complex enough to require relocation to memory, and an unfortunate processing order of the functions (which depends on the hashes of their Yul names). It is independent of whether the optimizer is enabled.", "link": "https://blog.soliditylang.org/2026/07/08/unsound-spill-in-mutual-recursion-bug/", "introduced": "0.7.2", - "fixed": "0.8.36", + "fixed": "0.8.30", "severity": "medium", "conditions": { "viaIR": true @@ -29,7 +29,7 @@ "description": "The IR-based code generator provides a set of Yul helper functions for basic operations, such as clearing, copying, encoding or type conversions. Not all functions are used by every contract. The codegen appends them to the generated sources individually, only when an operation that would invoke one of them is encountered. Utility functions are often specialized for different types and locations. Since Yul does not support generic functions, specialization is done by generating multiple versions of the same function, with the information distinguishing the variants embedded in their names. However, if not all the necessary bits of distinguishing information are properly accounted for, two helpers may end up with the same name, causing a collision. In this situation the codegen includes only one of them, with calls to both variants invoking it. This happened with the ``set_to_zero`` helper used when an area of transient or persistent storage needs to be cleared. The helper name was missing the location information, which resulted in a collision between the persistent and transient storage variants for the same type. This meant that contracts clearing both locations would actually clear only one, leaving the other untouched. Which location ended up being cleared depended on the order in which the code generator processed the input. The necessary condition to trigger the bug was the use of ``delete`` operator on a transient storage variable. This was due to value types being the only types supported in transient storage and ``delete`` being the only operation invoking the helper allowed on such types. The other necessary condition was clearing of persistent storage and in this case the range of affected operations was wider: operator ``delete``, array ``pop()`` or assignment that resulted in a longer array being overwritten with a shorter one. The cleared variable itself also did not necessarily have to be of the same type. It was enough that a matching value type was nested in it. It also did not always have to be the exact same value type - clearing operations on reference types are usually performed at slot granularity, treating every slot as ``uint256`` rather than clearing every value packed into it individually. To trigger the bug both operations had to be present within the same piece of bytecode. Independent contracts, not related through inheritance, would not affect each other this way. The presence of one operation only in creation code and the other only in deployed code would not trigger the bug either.", "link": "https://blog.soliditylang.org/2026/02/18/transient-storage-clearing-helper-collision-bug/", "introduced": "0.8.28", - "fixed": "0.8.34", + "fixed": "0.8.30", "severity": "high", "conditions": { "viaIR": true, @@ -43,7 +43,7 @@ "description": "Solidity makes it possible to define variables that extend past the last (2**256-th) slot of storage, which results in wrap-around back to slot zero. Since EVM uses 256-bit integer arithmetic, most operations on such variables just work. The only situation which requires special attention is iteration against absolute slot addresses: the invariant that the last slot belonging to a variable has the highest address does not hold. When implemented incorrectly, a loop over an array will immediately terminate if the container spans the end of storage - due to the initial position already being greater than the end position. This affected storage array clearing loops generated by both evmasm and IR pipelines. Additionally, (only in the evmasm pipeline) copying operations whose source was an array straddling the end of storage were also affected. At the language level, the buggy code would be generated for array assignment, array initialization, delete operator, .pop() and .push(). Note that a clearing loop is inserted by the compiler not only for invocations of the delete operator, but also to zero storage when overwriting a longer array with a shorter one, popping an element or even pushing an empty element to a dynamic array. Since clearing is a separate loop, it is possible for the bug to only affect it and not the copy operation it follows (which is always the case in the IR pipeline). The bug is extremely unlikely to be triggered accidentally due to the probabilistic impossibility of a short dynamic array being allocated right at the storage boundary. On the other hand, scenarios in which a user may place a static array there intentionally do not seem realistic and are limited to unusual layouts, in which a contract does not place any storage variables at slot zero (otherwise they would overlap the array).", "link": "https://blog.soliditylang.org/2025/12/18/lost-storage-array-write-on-slot-overflow-bug/", "introduced": "0.1.0", - "fixed": "0.8.32", + "fixed": "0.8.30", "severity": "low" }, { diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 814bce914bdf..444f6bc73622 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -2070,53 +2070,13 @@ "released": "2021-03-23" }, "0.8.30": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion", - "TransientStorageClearingHelperCollision", - "LostStorageArrayWriteOnSlotOverflow" - ], + "bugs": [], "released": "2025-05-07" }, "0.8.31": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion", - "TransientStorageClearingHelperCollision", - "LostStorageArrayWriteOnSlotOverflow" - ], + "bugs": [], "released": "2025-12-03" }, - "0.8.32": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion", - "TransientStorageClearingHelperCollision" - ], - "released": "2025-12-18" - }, - "0.8.33": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion", - "TransientStorageClearingHelperCollision" - ], - "released": "2025-12-18" - }, - "0.8.34": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion" - ], - "released": "2026-02-18" - }, - "0.8.35": { - "bugs": [ - "InheritanceOrderReversalOnStorageEndWarning", - "UnsoundSpillInMutualRecursion" - ], - "released": "2026-04-29" - }, "0.8.4": { "bugs": [ "UnsoundSpillInMutualRecursion", diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 7cf00fc9ed41..7298fe440008 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -383,7 +383,7 @@ bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const { auto numExps = static_cast(count(_routine.begin(), _routine.end(), Instruction::EXP)); return combineGas( - simpleRunGas(_routine, m_params.evmVersion) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)), + simpleRunGas(_routine, m_params.evmVersion) + numExps * GasCosts::expByteGasInTVM, // Data gas for routine: Some bytes are zero, but we ignore them. bytesRequired(_routine, m_params.evmVersion) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), 0 diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 1f4a7ddb1844..6c2cfdb0ee11 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -20,6 +20,8 @@ #include +#include + using namespace solidity; using namespace solidity::util; using namespace solidity::evmasm; @@ -71,13 +73,13 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ m_state->storageContent().count(slot) && classes.knownNonZero(m_state->storageContent().at(slot)) )) - gas = GasCosts::totalSstoreResetGas(m_evmVersion); //@todo take refunds into account + gas = GasCosts::sstoreResetGasInTVM; //@todo take refunds into account else - gas = GasCosts::totalSstoreSetGas(m_evmVersion); + gas = GasCosts::sstoreSetGasInTVM; break; } case Instruction::SLOAD: - gas = GasCosts::sloadGas(m_evmVersion); + gas = GasCosts::sloadGasInTVM; break; case Instruction::RETURN: case Instruction::REVERT: @@ -87,17 +89,11 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::MLOAD: case Instruction::MSTORE: gas = runGas(_item.instruction(), m_evmVersion); - gas += memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(0), - classes.find(AssemblyItem(32)) - })); + gas += memoryGas(m_state->relativeStackElement(0), u256(32)); break; case Instruction::MSTORE8: gas = runGas(_item.instruction(), m_evmVersion); - gas += memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(0), - classes.find(AssemblyItem(1)) - })); + gas += memoryGas(m_state->relativeStackElement(0), u256(1)); break; case Instruction::KECCAK256: gas = GasCosts::keccak256Gas; @@ -113,22 +109,29 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; case Instruction::MCOPY: { - GasConsumption memoryGasFromRead = memoryGas(-1, -2); - GasConsumption memoryGasFromWrite = memoryGas(0, -2); - gas = runGas(_item.instruction(), m_evmVersion); - gas += (memoryGasFromRead < memoryGasFromWrite ? memoryGasFromWrite : memoryGasFromRead); + ExpressionClasses::Id sizeExpression = m_state->relativeStackElement(-2); + if (!classes.knownZero(sizeExpression)) + { + u256 const* source = classes.knownConstant(m_state->relativeStackElement(-1)); + u256 const* destination = classes.knownConstant(m_state->relativeStackElement(0)); + u256 const* size = classes.knownConstant(sizeExpression); + if (!source || !destination || !size) + gas = GasConsumption::infinite(); + else + gas += memoryGas(bigint(std::max(*source, *destination)) + *size); + } gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2)); break; } case Instruction::EXTCODESIZE: - gas = GasCosts::extCodeGas(m_evmVersion); + gas = GasCosts::extCodeSizeGasInTVM; break; case Instruction::EXTCODEHASH: - gas = GasCosts::balanceGas(m_evmVersion); + gas = GasCosts::extCodeHashGasInTVM; break; case Instruction::EXTCODECOPY: - gas = GasCosts::extCodeGas(m_evmVersion); + gas = GasCosts::extCodeCopyGasInTVM; gas += memoryGas(-1, -3); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-3)); break; @@ -157,18 +160,20 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas = GasConsumption::infinite(); else { - gas = GasCosts::callGas(m_evmVersion); + gas = GasCosts::callGasInTVM; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) gas += (*value); else gas = GasConsumption::infinite(); - if (_item.instruction() == Instruction::CALL || _item.instruction() == Instruction::CALLTOKEN) - gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. int valueSize = 1; if (_item.instruction() == Instruction::DELEGATECALL || _item.instruction() == Instruction::STATICCALL) valueSize = 0; else if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) + { gas += GasCosts::callValueTransferGas; + if (_item.instruction() == Instruction::CALL || _item.instruction() == Instruction::CALLTOKEN) + gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. + } int tokenIdSize = 0; if (_item.instruction() == Instruction::CALLTOKEN) tokenIdSize = 1; @@ -178,7 +183,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::SELFDESTRUCT: - gas = GasCosts::selfdestructGas(m_evmVersion); + gas = GasCosts::selfdestructGasInTVM; gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. break; case Instruction::CREATE: @@ -190,6 +195,8 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ { gas = GasCosts::createGas; gas += memoryGas(-1, -2); + if (_item.instruction() == Instruction::CREATE2) + gas += wordGas(GasCosts::create2WordGasInTVM, m_state->relativeStackElement(-2)); } break; case Instruction::EXP: @@ -200,36 +207,36 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ { // Note: msb() counts from 0 and throws on 0 as input. unsigned const significantByteCount = (static_cast(boost::multiprecision::msb(*value)) + 1u + 7u) / 8u; - gas += GasCosts::expByteGas(m_evmVersion) * significantByteCount; + gas += GasCosts::expByteGasInTVM * significantByteCount; } } else - gas += GasCosts::expByteGas(m_evmVersion) * 32; + gas += GasCosts::expByteGasInTVM * 32; break; case Instruction::BALANCE: case Instruction::TOKENBALANCE: case Instruction::ISCONTRACT: - gas = GasCosts::balanceGas(m_evmVersion); + gas = GasCosts::balanceGasInTVM; break; case Instruction::NATIVEFREEZE: - gas = GasCosts::freezeV1Gas; + gas = GasCosts::freezeV1GasInTVM; gas += GasCosts::callNewAccountGas; break; case Instruction::NATIVEUNFREEZE: - gas = GasCosts::freezeV1Gas; + gas = GasCosts::freezeV1GasInTVM; break; case Instruction::NATIVEFREEZEEXPIRETIME: - gas = GasCosts::expireTimeGas; + gas = GasCosts::freezeExpireTimeGasInTVM; break; case Instruction::NATIVEVOTE: - gas = GasCosts::voteGas; + gas = GasCosts::voteGasInTVM; // NATIVEVOTE reads two Solidity memory arrays. The stack length values are element // counts, not byte lengths, so include the array length slot and 32 bytes per element. gas += memoryGasForWordArray(-3, -2); gas += memoryGasForWordArray(-1, 0); break; case Instruction::NATIVEWITHDRAWREWARD: - gas = GasCosts::withdrawGas; + gas = GasCosts::withdrawRewardGasInTVM; break; case Instruction::NATIVEFREEZEBALANCEV2: case Instruction::NATIVEUNFREEZEBALANCEV2: @@ -237,7 +244,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::NATIVEWITHDRAWEXPIREUNFREEZE: case Instruction::NATIVEDELEGATERESOURCE: case Instruction::NATIVEUNDELEGATERESOURCE: - gas = GasCosts::freezeV2Gas; + gas = GasCosts::freezeV2GasInTVM; break; case Instruction::CHAINID: gas = runGas(Instruction::CHAINID, m_evmVersion); @@ -265,36 +272,53 @@ GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionCl u256 const* value = m_state->expressionClasses().knownConstant(_value); if (!value) return GasConsumption::infinite(); - return GasConsumption(_multiplier * ((*value + 31) / 32)); + bigint gas = bigint(_multiplier) * ((bigint(*value) + 31) / 32); + if (gas > std::numeric_limits::max()) + return GasConsumption::infinite(); + return GasConsumption(u256(gas)); } -GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _position) +GasMeter::GasConsumption GasMeter::memoryGas(bigint const& _position) { - u256 const* value = m_state->expressionClasses().knownConstant(_position); - if (!value) + if ( + _position < 0 || + _position > GasCosts::memorySizeLimitInTVM || + bigint(m_largestMemoryAccess) > GasCosts::memorySizeLimitInTVM + ) return GasConsumption::infinite(); - if (*value < m_largestMemoryAccess) + u256 const value = u256(_position); + if (value < m_largestMemoryAccess) return GasConsumption(0); u256 previous = m_largestMemoryAccess; - m_largestMemoryAccess = *value; + m_largestMemoryAccess = value; auto memGas = [=](u256 const& pos) -> u256 { u256 size = (pos + 31) / 32; return GasCosts::memoryGas * size + size * size / GasCosts::quadCoeffDiv; }; - return memGas(*value) - memGas(previous); + return memGas(value) - memGas(previous); +} + +GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _offset, u256 const& _size) +{ + u256 const* offset = m_state->expressionClasses().knownConstant(_offset); + if (!offset) + return GasConsumption::infinite(); + return memoryGas(bigint(*offset) + _size); } GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosSize) { ExpressionClasses& classes = m_state->expressionClasses(); - if (classes.knownZero(m_state->relativeStackElement(_stackPosSize))) + ExpressionClasses::Id offsetExpression = m_state->relativeStackElement(_stackPosOffset); + ExpressionClasses::Id sizeExpression = m_state->relativeStackElement(_stackPosSize); + if (classes.knownZero(sizeExpression)) return GasConsumption(0); - else - return memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(_stackPosOffset), - m_state->relativeStackElement(_stackPosSize) - })); + u256 const* offset = classes.knownConstant(offsetExpression); + u256 const* size = classes.knownConstant(sizeExpression); + if (!offset || !size) + return GasConsumption::infinite(); + return memoryGas(bigint(*offset) + *size); } GasMeter::GasConsumption GasMeter::memoryGasForWordArray(int _stackPosOffset, int _stackPosElementCount) @@ -302,18 +326,12 @@ GasMeter::GasConsumption GasMeter::memoryGasForWordArray(int _stackPosOffset, in ExpressionClasses& classes = m_state->expressionClasses(); // The TVM reads and charges the 32-byte length slot even for empty arrays, // so unlike memoryGas(int, int) there is no zero-size shortcut here. - ExpressionClasses::Id byteSize = classes.find(Instruction::MUL, { - m_state->relativeStackElement(_stackPosElementCount), - classes.find(u256(32)) - }); - ExpressionClasses::Id byteSizeWithLengthSlot = classes.find(Instruction::ADD, { - byteSize, - classes.find(u256(32)) - }); - return memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(_stackPosOffset), - byteSizeWithLengthSlot - })); + u256 const* offset = classes.knownConstant(m_state->relativeStackElement(_stackPosOffset)); + u256 const* elementCount = classes.knownConstant(m_state->relativeStackElement(_stackPosElementCount)); + if (!offset || !elementCount) + return GasConsumption::infinite(); + bigint const byteSizeWithLengthSlot = bigint(*elementCount) * 32 + 32; + return memoryGas(bigint(*offset) + byteSizeWithLengthSlot); } namespace diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 7f2e4f6a3d00..3d9448104b28 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -179,11 +179,26 @@ namespace GasCosts static unsigned const copyGas = 3; static unsigned const rjumpiGas = 4; - static unsigned const freezeV1Gas = 20000; - static unsigned const expireTimeGas = 50; - static unsigned const freezeV2Gas = 10000; - static unsigned const withdrawGas = 20000; - static unsigned const voteGas = 30000; + // TVM keeps fixed Energy prices for these instructions. Do not derive them + // from Ethereum hard-fork-dependent EVM prices when estimating TRON code. + static unsigned const sloadGasInTVM = 50; + static unsigned const sstoreSetGasInTVM = 20000; + static unsigned const sstoreResetGasInTVM = 5000; + static unsigned const balanceGasInTVM = 20; + static unsigned const extCodeSizeGasInTVM = 20; + static unsigned const extCodeCopyGasInTVM = 20; + static unsigned const extCodeHashGasInTVM = 400; + static unsigned const callGasInTVM = 40; + static unsigned const selfdestructGasInTVM = 5000; + static unsigned const expByteGasInTVM = 10; + static unsigned const create2WordGasInTVM = 6; + static unsigned const memorySizeLimitInTVM = 3 * 1024 * 1024; + + static unsigned const freezeV1GasInTVM = 20000; + static unsigned const freezeExpireTimeGasInTVM = 50; + static unsigned const freezeV2GasInTVM = 10000; + static unsigned const withdrawRewardGasInTVM = 20000; + static unsigned const voteGasInTVM = 30000; } /** @@ -255,11 +270,13 @@ class GasMeter static u256 dataGas(uint64_t _length, bool _inCreation, langutil::EVMVersion _evmVersion); private: - /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. + /// @returns _multiplier * ceil(_value / 32), if _value is a known constant and infinite otherwise. GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); - /// @returns the gas needed to access the given memory position. + /// @returns the gas needed to access the given memory end position. /// @todo this assumes that memory was never accessed before and thus over-estimates gas usage. - GasConsumption memoryGas(ExpressionClasses::Id _position); + GasConsumption memoryGas(bigint const& _position); + /// @returns the memory gas for a known-size access starting at an offset on the stack. + GasConsumption memoryGas(ExpressionClasses::Id _offset, u256 const& _size); /// @returns the memory gas for accessing the memory at a specific offset for a number of bytes /// given as values on the stack at the given relative positions. GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize); diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index c186ad1a0f1b..db9f8d6890a2 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -366,8 +366,8 @@ void GlobalContext::addValidateMultiSignMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::boolean()); strings parameterNames; - parameterNames.push_back("address"); - parameterNames.push_back("permissonid"); + parameterNames.push_back("account"); + parameterNames.push_back("permissionId"); parameterNames.push_back("content"); parameterNames.push_back("signatures"); strings returnParameterNames; @@ -379,7 +379,7 @@ void GlobalContext::addValidateMultiSignMethod() { parameterNames, returnParameterNames, FunctionType::Kind::ValidateMultiSign, - StateMutability::Pure, + StateMutability::View, nullptr) )); } @@ -436,7 +436,7 @@ void GlobalContext::addIsSRCandidateMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::boolean()); strings parameterNames; - parameterNames.push_back("address"); + parameterNames.push_back("srCandidate"); strings returnParameterNames; returnParameterNames.push_back("ok"); @@ -460,8 +460,8 @@ void GlobalContext::addVoteCountMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::uint256()); strings parameterNames; - parameterNames.push_back("address"); - parameterNames.push_back("address"); + parameterNames.push_back("voter"); + parameterNames.push_back("srCandidate"); strings returnParameterNames; returnParameterNames.push_back("result"); @@ -484,7 +484,7 @@ void GlobalContext::addTotalVoteCountMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::uint256()); strings parameterNames; - parameterNames.push_back("address"); + parameterNames.push_back("voter"); strings returnParameterNames; returnParameterNames.push_back("result"); @@ -507,7 +507,7 @@ void GlobalContext::addReceivedVoteCountMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::uint256()); strings parameterNames; - parameterNames.push_back("address"); + parameterNames.push_back("srCandidate"); strings returnParameterNames; returnParameterNames.push_back("result"); @@ -530,7 +530,7 @@ void GlobalContext::addUsedVoteCountMethod() { TypePointers returnParameterTypes; returnParameterTypes.push_back(TypeProvider::uint256()); strings parameterNames; - parameterNames.push_back("address"); + parameterNames.push_back("voter"); strings returnParameterNames; returnParameterNames.push_back("result"); diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 9bf784b36573..4229a81971fb 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -306,6 +306,17 @@ bool SyntaxChecker::visit(Literal const& _literal) if (_literal.token() != Token::Number) return true; + Token const subDenomination = static_cast(_literal.subDenomination()); + if (TokenTraits::isEtherSubdenomination(subDenomination)) + { + m_errorReporter.parserError( + 9999_error, + _literal.location(), + "Ether unit denomination is not supported by the compiler" + ); + return true; + } + ASTString const& value = _literal.value(); solAssert(!value.empty(), ""); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d177912abde7..d51eb3442bef 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2009,6 +2009,38 @@ void TypeChecker::typeCheckFunctionCall( "\"staticcall\" is not supported by the VM version." ); + static std::set const tronStaticCallKinds = { + FunctionType::Kind::ValidateMultiSign, + FunctionType::Kind::BatchValidateSign, + FunctionType::Kind::VerifyBurnProof, + FunctionType::Kind::VerifyTransferProof, + FunctionType::Kind::VerifyMintProof, + FunctionType::Kind::PedersenHash, + FunctionType::Kind::RewardBalance, + FunctionType::Kind::IsSrCandidate, + FunctionType::Kind::VoteCount, + FunctionType::Kind::UsedVoteCount, + FunctionType::Kind::ReceivedVoteCount, + FunctionType::Kind::TotalVoteCount, + FunctionType::Kind::GetChainParameter, + FunctionType::Kind::AvailableUnfreezeV2Size, + FunctionType::Kind::UnfreezableBalanceV2, + FunctionType::Kind::ExpireUnfreezeBalanceV2, + FunctionType::Kind::DelegatableResource, + FunctionType::Kind::ResourceV2, + FunctionType::Kind::CheckUnDelegateResource, + FunctionType::Kind::ResourceUsage, + FunctionType::Kind::TotalResource, + FunctionType::Kind::TotalDelegatedResource, + FunctionType::Kind::TotalAcquiredResource, + }; + if (!m_evmVersion.hasStaticCall() && tronStaticCallKinds.count(_functionType->kind())) + m_errorReporter.typeError( + 9137_error, + _functionCall.location(), + "This TRON builtin requires a Byzantium-compatible VM." + ); + // Perform standard function call type checking typeCheckFunctionGeneralChecks(_functionCall, _functionType); } @@ -3313,6 +3345,12 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) { if (magicType->kind() == MagicType::Kind::ABI) annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::Chain && !m_evmVersion.hasStaticCall()) + m_errorReporter.typeError( + 9137_error, + _memberAccess.location(), + "This TRON builtin requires a Byzantium-compatible VM." + ); else if (magicType->kind() == MagicType::Kind::MetaType && ( memberName == "creationCode" || memberName == "runtimeCode" )) diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 499b7df62a2a..7e61275aa1ea 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -1236,13 +1236,12 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json const& _node) std::string const subDenStr = subDen.get(); - if (subDenStr == "wei") - return Literal::SubDenomination::Wei; - else if (subDenStr == "gwei") - return Literal::SubDenomination::Gwei; - else if (subDenStr == "ether") - return Literal::SubDenomination::Ether; - else if (subDenStr == "sun") + astAssert( + subDenStr != "wei" && subDenStr != "gwei" && subDenStr != "ether", + "Ether unit denomination is not supported by the compiler" + ); + + if (subDenStr == "sun") return Literal::SubDenomination::Sun; else if (subDenStr == "trx") return Literal::SubDenomination::Trx; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e18634f2260b..6ab7423ed781 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1081,16 +1081,15 @@ std::tuple RationalNumberType::isValidLiteral(Literal const& _li } switch (_literal.subDenomination()) { - case Literal::SubDenomination::None: case Literal::SubDenomination::Wei: - case Literal::SubDenomination::Sun: - case Literal::SubDenomination::Second: - break; case Literal::SubDenomination::Gwei: - value *= bigint("1000000000"); - break; case Literal::SubDenomination::Ether: - value *= bigint("1000000000000000000"); + // These denominations are not part of the TRON language, even if a + // Literal is constructed without going through the parser or AST importer. + return std::make_tuple(false, rational(0)); + case Literal::SubDenomination::None: + case Literal::SubDenomination::Sun: + case Literal::SubDenomination::Second: break; case Literal::SubDenomination::Trx: value *= bigint("1000000"); @@ -3875,7 +3874,6 @@ bool FunctionType::isPure() const return m_kind == Kind::KECCAK256 || m_kind == Kind::ECRecover || - m_kind == Kind::ValidateMultiSign || m_kind == Kind::BatchValidateSign || m_kind == Kind::VerifyBurnProof || m_kind == Kind::VerifyTransferProof || diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 475c4d32b8b0..92db9d9589d9 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1646,6 +1646,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case FunctionType::Kind::Freeze: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1657,6 +1658,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::Unfreeze: { + solAssert(arguments.size() == 1 && function.parameterTypes().size() == 1, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1669,6 +1671,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::FreezeExpireTime: { + solAssert(arguments.size() == 1 && function.parameterTypes().size() == 1, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1679,6 +1682,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::Vote: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1692,11 +1696,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::WithdrawReward: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVEWITHDRAWREWARD; break; } case FunctionType::Kind::FreezeBalanceV2: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1708,6 +1714,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::UnfreezeBalanceV2: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1719,16 +1726,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::CancelAllUnfreezeV2: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVECANCELALLUNFREEZEV2; break; } case FunctionType::Kind::WithdrawExpireUnfreeze: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVEWITHDRAWEXPIREUNFREEZE; break; } case FunctionType::Kind::DelegateResource: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1740,6 +1750,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::UnDelegateResource: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -3256,7 +3267,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. - u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (_functionType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!existenceChecked) @@ -3319,6 +3330,7 @@ void ExpressionCompiler::appendExternalFunctionCall( switch v case 0 { v := 0x60 } default { + if mod(returndatasize(), 0x20) { revert(0, 0) } v := mload(0x40) mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) mstore(v, div(returndatasize(), 0x20)) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e55282137185..862ff7c9f592 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1658,7 +1658,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) std::string tokenId{expressionAsType(*arguments[1], *(parameterTypes[1]))}; Whiskers templ(R"( if iszero(lt(0xf4240, )) { revert(0, 0) } - if iszero(gt(exp(2, 63), )) { revert(0, 0) } + if iszero(gt(0x8000000000000000, )) { revert(0, 0) } let := 0 if iszero() { := } let := calltoken(,
, , , 0, 0, 0, 0) @@ -1682,7 +1682,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) std::string tokenId{expressionAsType(*arguments[0], *(parameterTypes[0]))}; Whiskers templ(R"( if iszero(lt(0xf4240, )) { revert(0, 0) } - if iszero(gt(exp(2, 63), )) { revert(0, 0) } + if iszero(gt(0x8000000000000000, )) { revert(0, 0) } let := tokenbalance(,
) )"); templ("address", address); @@ -1760,7 +1760,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { // @todo The value 10 is not exact and this could be fine-tuned, // but this has worked for years in the old code generator. - u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10 + evmasm::GasCosts::callNewAccountGas; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10 + evmasm::GasCosts::callNewAccountGas; templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); } @@ -2004,30 +2004,41 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) let := staticcall(gas(),
, , sub(, ), , ) if iszero() { () } + + if lt(returndatasize(), 0x20) { + mstore(, 0) + returndatacopy(, 0, returndatasize()) + } + - + + if mod(returndatasize(), 0x20) { revert(0, 0) } let := add(returndatasize(), 0x40) returndatacopy(add(, 0x40), 0, returndatasize()) mstore(, 0x20) mstore(add(, 0x20), div(returndatasize(), 0x20)) - + let := returndatasize() returndatacopy(, 0, ) - + let := - + if gt(, returndatasize()) { := returndatasize() } - + // update freeMemoryPointer according to dynamic return size (, ) - let := (, add(, )) + + let := mload() + + let := (, add(, )) + )"); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); @@ -2040,12 +2051,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) if (returnInfo.dynamicReturnSize) solAssert(m_context.evmVersion().supportsReturndata()); - templ("supportsReturnData", m_context.evmVersion().supportsReturndata()); + bool const isLegacyCompatibleMultisig = + functionType->kind() == FunctionType::Kind::ValidateMultiSign || + functionType->kind() == FunctionType::Kind::BatchValidateSign; + templ("isLegacyCompatibleMultisig", isLegacyCompatibleMultisig); + templ("strictReturnSize", m_context.evmVersion().supportsReturndata() && !isLegacyCompatibleMultisig); templ("returnDataSizeVar", m_context.newYulVariable()); templ("staticReturndataSize", std::to_string(returnInfo.estimatedReturnSize)); templ("isReturndataSizeDynamic", returnInfo.dynamicReturnSize); - templ("isMintProof",functionType->kind() == FunctionType::Kind::VerifyMintProof ||functionType->kind() == FunctionType::Kind::VerifyTransferProof); + templ("isProof", functionType->kind() == FunctionType::Kind::VerifyMintProof || functionType->kind() == FunctionType::Kind::VerifyTransferProof); templ("finalizeAllocation", m_utils.finalizeAllocationFunction()); templ("retVars", IRVariable(_functionCall).commaSeparatedList()); templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true)); @@ -3189,7 +3204,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( { // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. - u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!checkExtcodesize) @@ -3298,7 +3313,7 @@ void IRGeneratorForStatements::appendBareCall( { // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. - u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know diff --git a/libsolidity/formal/Predicate.cpp b/libsolidity/formal/Predicate.cpp index 7f742ec18d0b..10854c3ab8ef 100644 --- a/libsolidity/formal/Predicate.cpp +++ b/libsolidity/formal/Predicate.cpp @@ -253,7 +253,12 @@ std::string Predicate::formatSummaryCall( if (magicKind == MagicType::Kind::Block && memberName == "difficulty") memberName = "prevrandao"; - if (magicKind == MagicType::Kind::Block || magicKind == MagicType::Kind::Message || magicKind == MagicType::Kind::Transaction) + if ( + magicKind == MagicType::Kind::Block || + magicKind == MagicType::Kind::Chain || + magicKind == MagicType::Kind::Message || + magicKind == MagicType::Kind::Transaction + ) txVars.insert(magicType->toString(true) + "." + memberName); } return true; diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 95268032af0e..d4fbc73c734b 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -726,16 +726,68 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) " with the CHC engine." ); break; + case FunctionType::Kind::TransferToken: + case FunctionType::Kind::Freeze: + case FunctionType::Kind::Unfreeze: + case FunctionType::Kind::Vote: + case FunctionType::Kind::WithdrawReward: + case FunctionType::Kind::FreezeBalanceV2: + case FunctionType::Kind::UnfreezeBalanceV2: + case FunctionType::Kind::CancelAllUnfreezeV2: + case FunctionType::Kind::WithdrawExpireUnfreeze: + case FunctionType::Kind::DelegateResource: + case FunctionType::Kind::UnDelegateResource: + // These operations mutate TRON account or resource state. Since that state is + // not modeled explicitly, conservatively invalidate the symbolic blockchain + // state so balances and other observable state cannot remain falsely stable. + state().newState(); + if (!funType.returnParameterTypes().empty()) + setSymbolicUnknownValue(*m_context.expression(_funCall), m_context); + m_unsupportedErrors.warning( + 4588_error, + _funCall.location(), + "Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively." + ); + break; + case FunctionType::Kind::TokenBalance: + case FunctionType::Kind::FreezeExpireTime: + case FunctionType::Kind::ValidateMultiSign: + case FunctionType::Kind::BatchValidateSign: + case FunctionType::Kind::VerifyBurnProof: + case FunctionType::Kind::VerifyTransferProof: + case FunctionType::Kind::VerifyMintProof: + case FunctionType::Kind::PedersenHash: + case FunctionType::Kind::RewardBalance: + case FunctionType::Kind::IsSrCandidate: + case FunctionType::Kind::VoteCount: + case FunctionType::Kind::UsedVoteCount: + case FunctionType::Kind::ReceivedVoteCount: + case FunctionType::Kind::TotalVoteCount: + case FunctionType::Kind::GetChainParameter: + case FunctionType::Kind::AvailableUnfreezeV2Size: + case FunctionType::Kind::UnfreezableBalanceV2: + case FunctionType::Kind::ExpireUnfreezeBalanceV2: + case FunctionType::Kind::DelegatableResource: + case FunctionType::Kind::ResourceV2: + case FunctionType::Kind::CheckUnDelegateResource: + case FunctionType::Kind::ResourceUsage: + case FunctionType::Kind::TotalResource: + case FunctionType::Kind::TotalDelegatedResource: + case FunctionType::Kind::TotalAcquiredResource: + // Keep unsupported TRON queries and precompiles unconstrained, while still + // applying the range/shape constraints of their Solidity return types. + if (!funType.returnParameterTypes().empty()) + setSymbolicUnknownValue(*m_context.expression(_funCall), m_context); + m_unsupportedErrors.warning( + 4588_error, + _funCall.location(), + "Assertion checker does not yet implement this type of function call." + ); + break; case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: default: - // The TRON-specific builtins (freeze/unfreeze, vote, the various V2 calls, - // validatemultisign/batchvalidatesign, the zk-proof verifiers, pedersenhash, ...) - // are not modeled here: we only emit the warning below. Their state side effects - // are not havoc'd in this branch; soundness for state mutation across unmodeled - // calls is instead handled by the engine-level reset logic (CHC::unknownFunctionCall - // / makeOutsideFunctionCall and BMC's resetStateVariables paths). m_unsupportedErrors.warning( 4588_error, _funCall.location(), @@ -1420,7 +1472,7 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess) if (auto const* identifier = dynamic_cast(&memberExpr)) { auto const& name = identifier->name(); - solAssert(name == "block" || name == "msg" || name == "tx", ""); + solAssert(name == "block" || name == "chain" || name == "msg" || name == "tx", ""); auto memberName = _memberAccess.memberName(); // TODO remove this for 0.9.0 diff --git a/libsolidity/formal/SymbolicState.cpp b/libsolidity/formal/SymbolicState.cpp index 492e0055b008..8e55922ae573 100644 --- a/libsolidity/formal/SymbolicState.cpp +++ b/libsolidity/formal/SymbolicState.cpp @@ -234,7 +234,14 @@ smtutil::Expression SymbolicState::txTypeConstraints() const smt::symbolicUnknownConstraints(m_tx.member("block.gaslimit"), TypeProvider::uint256()) && smt::symbolicUnknownConstraints(m_tx.member("block.number"), TypeProvider::uint256()) && smt::symbolicUnknownConstraints(m_tx.member("block.timestamp"), TypeProvider::uint256()) && + smt::symbolicUnknownConstraints(m_tx.member("chain.totalEnergyCurrentLimit"), TypeProvider::uint(64)) && + smt::symbolicUnknownConstraints(m_tx.member("chain.totalEnergyWeight"), TypeProvider::uint(64)) && + smt::symbolicUnknownConstraints(m_tx.member("chain.totalNetLimit"), TypeProvider::uint(64)) && + smt::symbolicUnknownConstraints(m_tx.member("chain.totalNetWeight"), TypeProvider::uint(64)) && + smt::symbolicUnknownConstraints(m_tx.member("chain.unfreezeDelayDays"), TypeProvider::uint(64)) && smt::symbolicUnknownConstraints(m_tx.member("msg.sender"), TypeProvider::address()) && + smt::symbolicUnknownConstraints(m_tx.member("msg.tokenid"), TypeProvider::trcToken()) && + smt::symbolicUnknownConstraints(m_tx.member("msg.tokenvalue"), TypeProvider::uint256()) && smt::symbolicUnknownConstraints(m_tx.member("msg.value"), TypeProvider::uint256()) && smt::symbolicUnknownConstraints(m_tx.member("tx.origin"), TypeProvider::address()) && smt::symbolicUnknownConstraints(m_tx.member("tx.gasprice"), TypeProvider::uint256()); @@ -242,12 +249,19 @@ smtutil::Expression SymbolicState::txTypeConstraints() const smtutil::Expression SymbolicState::txNonPayableConstraint() const { - return m_tx.member("msg.value") == 0; + return + m_tx.member("msg.value") == 0 && + m_tx.member("msg.tokenid") == 0 && + m_tx.member("msg.tokenvalue") == 0; } smtutil::Expression SymbolicState::txFunctionConstraints(FunctionDefinition const& _function) const { - smtutil::Expression conj = _function.isPayable() ? smtutil::Expression(true) : txNonPayableConstraint(); + // Library functions inherit the caller's transaction values through DELEGATECALL. + smtutil::Expression conj = + (_function.isPayable() || _function.libraryFunction()) ? + smtutil::Expression(true) : + txNonPayableConstraint(); if (_function.isPartOfExternalInterface()) { auto sig = TypeProvider::function(_function)->externalIdentifier(); diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 11c92a83af40..4e04aba75eeb 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -682,9 +682,16 @@ std::map transactionMemberTypes() {"block.timestamp", TypeProvider::uint256()}, {"blobhash", TypeProvider::array(DataLocation::Memory, TypeProvider::uint256())}, {"blockhash", TypeProvider::array(DataLocation::Memory, TypeProvider::uint256())}, + {"chain.totalEnergyCurrentLimit", TypeProvider::uint(64)}, + {"chain.totalEnergyWeight", TypeProvider::uint(64)}, + {"chain.totalNetLimit", TypeProvider::uint(64)}, + {"chain.totalNetWeight", TypeProvider::uint(64)}, + {"chain.unfreezeDelayDays", TypeProvider::uint(64)}, {"msg.data", TypeProvider::bytesCalldata()}, {"msg.sender", TypeProvider::address()}, {"msg.sig", TypeProvider::fixedBytes(4)}, + {"msg.tokenid", TypeProvider::trcToken()}, + {"msg.tokenvalue", TypeProvider::uint256()}, {"msg.value", TypeProvider::uint256()}, {"tx.gasprice", TypeProvider::uint256()}, {"tx.origin", TypeProvider::address()} diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 85495a9e37e2..c1e6ae0b8dab 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -400,7 +400,7 @@ Json formatImmutableReferences(std::map checkKeys(Json const& _input, std::set const& _keys, std::string const& _name) { - if (!_input.empty() && !_input.is_object()) + if (!_input.is_object()) return formatFatalError(Error::Type::JSONError, "\"" + _name + "\" must be an object"); for (auto const& [member, _]: _input.items()) @@ -513,6 +513,8 @@ std::optional checkMetadataKeys(Json const& _input) return formatFatalError(Error::Type::JSONError, "\"settings.metadata.appendCBOR\" must be Boolean"); if (_input.contains("useLiteralContent") && !_input["useLiteralContent"].is_boolean()) return formatFatalError(Error::Type::JSONError, "\"settings.metadata.useLiteralContent\" must be Boolean"); + if (_input.contains("bytecodeHash") && !_input["bytecodeHash"].is_string()) + return formatFatalError(Error::Type::JSONError, "\"settings.metadata.bytecodeHash\" must be a string"); static std::set hashes{"ipfs", "bzzr1", "none"}; if (_input.contains("bytecodeHash") && !hashes.count(_input["bytecodeHash"].get())) @@ -524,7 +526,7 @@ std::optional checkMetadataKeys(Json const& _input) std::optional checkOutputSelection(Json const& _outputSelection) { - if (!_outputSelection.empty() && !_outputSelection.is_object()) + if (!_outputSelection.is_object()) return formatFatalError(Error::Type::JSONError, "\"settings.outputSelection\" must be an object"); for (auto const& [sourceName, sourceVal]: _outputSelection.items()) @@ -647,6 +649,8 @@ std::variant StandardCompiler::parseI if (auto result = checkRootKeys(_input)) return *result; + if (_input.contains("language") && !_input["language"].is_string()) + return formatFatalError(Error::Type::JSONError, "\"language\" must be a string."); ret.language = _input.value("language", ""); Json const& sources = _input.value("sources", Json()); @@ -770,31 +774,28 @@ std::variant StandardCompiler::parseI if (!auxInputs.empty()) { Json const& smtlib2Responses = auxInputs.value("smtlib2responses", Json::object()); - if (!smtlib2Responses.empty()) - { - if (!smtlib2Responses.is_object()) - return formatFatalError(Error::Type::JSONError, "\"auxiliaryInput.smtlib2responses\" must be an object."); + if (!smtlib2Responses.is_object()) + return formatFatalError(Error::Type::JSONError, "\"auxiliaryInput.smtlib2responses\" must be an object."); - for (auto const& [hashString, response]: smtlib2Responses.items()) + for (auto const& [hashString, response]: smtlib2Responses.items()) + { + util::h256 hash; + try { - util::h256 hash; - try - { - hash = util::h256(hashString); - } - catch (util::BadHexCharacter const&) - { - return formatFatalError(Error::Type::JSONError, "Invalid hex encoding of SMTLib2 auxiliary input."); - } + hash = util::h256(hashString); + } + catch (util::BadHexCharacter const&) + { + return formatFatalError(Error::Type::JSONError, "Invalid hex encoding of SMTLib2 auxiliary input."); + } - if (!response.is_string()) - return formatFatalError( - Error::Type::JSONError, - "\"smtlib2Responses." + hashString + "\" must be a string." - ); + if (!response.is_string()) + return formatFatalError( + Error::Type::JSONError, + "\"smtlib2Responses." + hashString + "\" must be a string." + ); - ret.smtLib2Responses[hash] = response.get(); - } + ret.smtLib2Responses[hash] = response.get(); } } @@ -870,7 +871,11 @@ std::variant StandardCompiler::parseI std::vector components; for (Json const& arrayValue: settings["debug"]["debugInfo"]) + { + if (!arrayValue.is_string()) + return formatFatalError(Error::Type::JSONError, "Every value in settings.debug.debugInfo must be a string."); components.push_back(arrayValue.get()); + } std::optional debugInfoSelection = DebugInfoSelection::fromComponents( components, @@ -1383,6 +1388,16 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu if (binariesRequested) compilerStack.compile(); } + catch (InvalidAstError const& _exc) + { + errors.emplace_back(formatErrorWithException( + compilerStack, + _exc, + Error::Type::JSONError, + "general", + "Failed to import AST" + )); + } catch (util::Exception const& _exc) { solThrow(util::Exception, "Failed to import AST: "s + _exc.what()); diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index d726139e3a73..d156b66422ac 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -59,6 +59,12 @@ namespace fs = boost::filesystem; namespace { +bool isFilesystemRoot(fs::path const& _path) +{ + auto const normalizedPath = _path.lexically_normal(); + return normalizedPath.has_root_path() && normalizedPath == normalizedPath.root_path(); +} + bool resolvesToRegularFile(boost::filesystem::path _path, int maxRecursionDepth = 10) { fs::file_status fileStatus = fs::status(_path); @@ -217,14 +223,20 @@ void LanguageServer::changeConfiguration(Json const& _settings) std::vector LanguageServer::allSolidityFilesFromProject() const { std::vector collectedPaths{}; + auto const basePath = m_fileRepository.basePath().lexically_normal(); + + // The filesystem root is used as a fallback when the client does not provide a workspace root. + // Keep full filesystem access available for explicit imports, but never scan the entire filesystem eagerly. + if (isFilesystemRoot(basePath)) + return collectedPaths; // We explicitly decided against including all files from include paths but leave the possibility // open for a future PR to enable such a feature to be optionally enabled (default disabled). // Note: Newer versions of boost have deprecated symlink_option::recurse #if (BOOST_VERSION < 107200) - auto directoryIterator = fs::recursive_directory_iterator(m_fileRepository.basePath(), fs::symlink_option::recurse); + auto directoryIterator = fs::recursive_directory_iterator(basePath, fs::symlink_option::recurse); #else - auto directoryIterator = fs::recursive_directory_iterator(m_fileRepository.basePath(), fs::directory_options::follow_directory_symlink); + auto directoryIterator = fs::recursive_directory_iterator(basePath, fs::directory_options::follow_directory_symlink); #endif for (fs::directory_entry const& dirEntry: directoryIterator) if ( @@ -412,6 +424,8 @@ void LanguageServer::handleInitialize(MessageID _id, Json const& _args) m_fileRepository = FileRepository(rootPath, {}); if (_args.contains("initializationOptions") && _args["initializationOptions"].is_object()) changeConfiguration(_args["initializationOptions"]); + if (m_fileLoadStrategy == FileLoadStrategy::ProjectDirectory && isFilesystemRoot(m_fileRepository.basePath())) + m_fileLoadStrategy = FileLoadStrategy::DirectlyOpenedAndOnImported; Json replyArgs; replyArgs["serverInfo"]["name"] = "solc"; diff --git a/libyul/backends/evm/EVMMetrics.cpp b/libyul/backends/evm/EVMMetrics.cpp index 57593fdc26da..1d4e2be5ec02 100644 --- a/libyul/backends/evm/EVMMetrics.cpp +++ b/libyul/backends/evm/EVMMetrics.cpp @@ -115,7 +115,7 @@ bigint GasMeterVisitor::singleByteDataGas() const void GasMeterVisitor::instructionCostsInternal(evmasm::Instruction _instruction) { if (_instruction == evmasm::Instruction::EXP) - m_runGas += evmasm::GasCosts::expGas + evmasm::GasCosts::expByteGas(m_dialect.evmVersion()); + m_runGas += evmasm::GasCosts::expGas + evmasm::GasCosts::expByteGasInTVM; else if (_instruction == evmasm::Instruction::KECCAK256) // Assumes that Keccak-256 is computed on a single word (rounded up). m_runGas += evmasm::GasCosts::keccak256Gas + evmasm::GasCosts::keccak256WordGas; diff --git a/scripts/regressions.py b/scripts/regressions.py index 7de108ef30f3..f9ee9ea3ce52 100755 --- a/scripts/regressions.py +++ b/scripts/regressions.py @@ -52,7 +52,7 @@ def parseCmdLine(cls, description, args): def run_cmd(command, logfile=None, env=None): """ Args: - command (str): command to run + command (list[str]): command and arguments to run logfile (str): log file name env (dict): dictionary holding key-value pairs for bash environment variables @@ -69,13 +69,33 @@ def run_cmd(command, logfile=None, env=None): env = os.environ.copy() with open(logfile, 'w', encoding='utf8') as logfh: - with subprocess.Popen(command, shell=True, executable='/bin/bash', - env=env, stdout=logfh, + with subprocess.Popen(command, env=env, stdout=logfh, stderr=subprocess.STDOUT) as proc: ret = proc.wait() logfh.close() return ret + @staticmethod + def run_corpus(fuzzer, corpus_dir, logfile, env=None): + if not env: + env = os.environ.copy() + + corpus_files = [] + for root, _, filenames in os.walk(corpus_dir): + corpus_files.extend(os.path.join(root, filename) for filename in filenames) + + with open(logfile, 'w', encoding='utf8') as logfh: + for corpus_file in sorted(corpus_files): + with subprocess.Popen( + [fuzzer, corpus_file], + env=env, + stdout=logfh, + stderr=subprocess.STDOUT + ) as proc: + if proc.wait() != 0: + return 255 + return 0 + def process_log(self, logfile): """ Args: @@ -106,8 +126,7 @@ def run(self): basename = os.path.basename(fuzzer) logfile = os.path.join(self._logpath, f"{basename}.log") corpus_dir = f"/tmp/solidity-fuzzing-corpus/{basename}_seed_corpus" - cmd = f"find {corpus_dir} -type f | xargs -n1 sh -c '{fuzzer} $0 || exit 255'" - self.run_cmd(cmd, logfile=logfile) + self.run_corpus(fuzzer, corpus_dir, logfile) ret = self.process_log(logfile) if not ret: print( diff --git a/scripts/splitSources.py b/scripts/splitSources.py index 878600b17947..cd05538a51af 100755 --- a/scripts/splitSources.py +++ b/scripts/splitSources.py @@ -10,8 +10,8 @@ # - 'false' if the file only had one source import sys -import os import traceback +from pathlib import Path def uncaught_exception_hook(exc_type, exc_value, exc_traceback): @@ -37,9 +37,20 @@ def writeSourceToFile(lines): filePath, srcName = extractSourceName(lines[0]) # print("sourceName is ", srcName) # print("filePath is", filePath) + outputRoot = Path.cwd().resolve() + sourcePath = Path(srcName) + if sourcePath.is_absolute(): + raise ValueError("Source name must be a relative path: " + srcName) + + outputPath = (outputRoot / sourcePath).resolve() + try: + outputPath.relative_to(outputRoot) + except ValueError: + raise ValueError("Source name escapes the output directory: " + srcName) + if filePath: - os.system("mkdir -p " + filePath) - with open(srcName, mode='a+', encoding='utf8', newline='') as f: + outputPath.parent.mkdir(parents=True, exist_ok=True) + with outputPath.open(mode='a+', encoding='utf8', newline='') as f: for idx, line in enumerate(lines[1:]): # write to file if not line.startswith("==== Source:"): diff --git a/test/cmdlineTests/standard_wrong_type_bytecodeHash/input.json b/test/cmdlineTests/standard_wrong_type_bytecodeHash/input.json new file mode 100644 index 000000000000..565a94738c0a --- /dev/null +++ b/test/cmdlineTests/standard_wrong_type_bytecodeHash/input.json @@ -0,0 +1,13 @@ +{ + "language": "Solidity", + "sources": { + "A.sol": { + "content": "pragma solidity >=0.0; contract A {}" + } + }, + "settings": { + "metadata": { + "bytecodeHash": true + } + } +} diff --git a/test/cmdlineTests/standard_wrong_type_bytecodeHash/output.json b/test/cmdlineTests/standard_wrong_type_bytecodeHash/output.json new file mode 100644 index 000000000000..114c86e30f92 --- /dev/null +++ b/test/cmdlineTests/standard_wrong_type_bytecodeHash/output.json @@ -0,0 +1,11 @@ +{ + "errors": [ + { + "component": "general", + "formattedMessage": "\"settings.metadata.bytecodeHash\" must be a string", + "message": "\"settings.metadata.bytecodeHash\" must be a string", + "severity": "error", + "type": "JSONError" + } + ] +} diff --git a/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp index 6d82f86552ab..53dadf325ac7 100644 --- a/test/libevmasm/GasMeter.cpp +++ b/test/libevmasm/GasMeter.cpp @@ -30,6 +30,9 @@ #include #include +#include +#include +#include using namespace solidity::evmasm; using namespace solidity::langutil; @@ -39,6 +42,32 @@ namespace solidity::frontend::test namespace { +GasMeter::GasConsumption estimateInstruction( + Instruction _instruction, + AssemblyItems _arguments, + bool _includeExternalCosts = true, + EVMVersion _evmVersion = EVMVersion{} +) +{ + _arguments.emplace_back(_instruction); + GasMeter meter(std::make_shared(), _evmVersion); + GasMeter::GasConsumption gas; + for (AssemblyItem const& item: _arguments) + gas = meter.estimateMax(item, _includeExternalCosts); + return gas; +} + +AssemblyItems zeroArguments(size_t _count) +{ + return AssemblyItems(_count, AssemblyItem{u256(0)}); +} + +u256 memoryExpansionCost(u256 const& _byteSize) +{ + u256 const wordCount = (_byteSize + 31) / 32; + return GasCosts::memoryGas * wordCount + wordCount * wordCount / GasCosts::quadCoeffDiv; +} + /// Feeds the four NATIVEVOTE arguments as constants (or CALLVALUE for an unknown /// element count) and returns the estimate for the NATIVEVOTE item itself. GasMeter::GasConsumption estimateVote( @@ -68,6 +97,261 @@ GasMeter::GasConsumption estimateVote( BOOST_AUTO_TEST_SUITE(EvmasmGasMeter) +BOOST_AUTO_TEST_CASE(tvm_fixed_instruction_prices_do_not_depend_on_evm_version) +{ + std::vector> const fixedCosts{ + {Instruction::SLOAD, 1, GasCosts::sloadGasInTVM}, + {Instruction::BALANCE, 1, GasCosts::balanceGasInTVM}, + {Instruction::TOKENBALANCE, 2, GasCosts::balanceGasInTVM}, + {Instruction::ISCONTRACT, 1, GasCosts::balanceGasInTVM}, + {Instruction::EXTCODESIZE, 1, GasCosts::extCodeSizeGasInTVM}, + {Instruction::EXTCODECOPY, 4, GasCosts::extCodeCopyGasInTVM}, + {Instruction::EXTCODEHASH, 1, GasCosts::extCodeHashGasInTVM}, + {Instruction::NATIVEFREEZE, 3, GasCosts::freezeV1GasInTVM + GasCosts::callNewAccountGas}, + {Instruction::NATIVEUNFREEZE, 2, GasCosts::freezeV1GasInTVM}, + {Instruction::NATIVEFREEZEEXPIRETIME, 2, GasCosts::freezeExpireTimeGasInTVM}, + {Instruction::NATIVEWITHDRAWREWARD, 0, GasCosts::withdrawRewardGasInTVM}, + {Instruction::NATIVEFREEZEBALANCEV2, 2, GasCosts::freezeV2GasInTVM}, + {Instruction::NATIVEUNFREEZEBALANCEV2, 2, GasCosts::freezeV2GasInTVM}, + {Instruction::NATIVECANCELALLUNFREEZEV2, 0, GasCosts::freezeV2GasInTVM}, + {Instruction::NATIVEWITHDRAWEXPIREUNFREEZE, 0, GasCosts::freezeV2GasInTVM}, + {Instruction::NATIVEDELEGATERESOURCE, 3, GasCosts::freezeV2GasInTVM}, + {Instruction::NATIVEUNDELEGATERESOURCE, 3, GasCosts::freezeV2GasInTVM} + }; + + for (EVMVersion const& evmVersion: EVMVersion::allVersions()) + for (auto const& [instruction, argumentCount, expected]: fixedCosts) + { + GasMeter::GasConsumption gas = estimateInstruction( + instruction, + zeroArguments(argumentCount), + true, + evmVersion + ); + BOOST_REQUIRE(!gas.isInfinite); + BOOST_CHECK_EQUAL(gas.value, u256(expected)); + } +} + +BOOST_AUTO_TEST_CASE(tvm_sstore_uses_java_tron_set_and_reset_prices) +{ + GasMeter::GasConsumption set = estimateInstruction(Instruction::SSTORE, {u256(1), u256(0)}); + BOOST_REQUIRE(!set.isInfinite); + BOOST_CHECK_EQUAL(set.value, u256(GasCosts::sstoreSetGasInTVM)); + + GasMeter::GasConsumption reset = estimateInstruction(Instruction::SSTORE, {u256(0), u256(0)}); + BOOST_REQUIRE(!reset.isInfinite); + BOOST_CHECK_EQUAL(reset.value, u256(GasCosts::sstoreResetGasInTVM)); +} + +BOOST_AUTO_TEST_CASE(tvm_exp_uses_fixed_byte_price_for_all_evm_versions) +{ + for (EVMVersion const& evmVersion: EVMVersion::allVersions()) + { + GasMeter::GasConsumption zeroExponent = estimateInstruction( + Instruction::EXP, + {u256(0), u256(2)}, + true, + evmVersion + ); + BOOST_REQUIRE(!zeroExponent.isInfinite); + BOOST_CHECK_EQUAL(zeroExponent.value, u256(GasCosts::expGas)); + + GasMeter::GasConsumption oneByteExponent = estimateInstruction( + Instruction::EXP, + {u256(1), u256(2)}, + true, + evmVersion + ); + BOOST_REQUIRE(!oneByteExponent.isInfinite); + BOOST_CHECK_EQUAL( + oneByteExponent.value, + u256(GasCosts::expGas + GasCosts::expByteGasInTVM) + ); + + GasMeter::GasConsumption fullWidthExponent = estimateInstruction( + Instruction::EXP, + {u256(-1), u256(2)}, + true, + evmVersion + ); + BOOST_REQUIRE(!fullWidthExponent.isInfinite); + BOOST_CHECK_EQUAL( + fullWidthExponent.value, + u256(GasCosts::expGas + 32 * GasCosts::expByteGasInTVM) + ); + } + + GasMeter::GasConsumption unknownExponent = estimateInstruction( + Instruction::EXP, + {AssemblyItem(Instruction::CALLVALUE), AssemblyItem(u256(2))} + ); + BOOST_REQUIRE(!unknownExponent.isInfinite); + BOOST_CHECK_EQUAL( + unknownExponent.value, + u256(GasCosts::expGas + 32 * GasCosts::expByteGasInTVM) + ); +} + +BOOST_AUTO_TEST_CASE(tvm_create2_charges_hash_cost_per_init_code_word) +{ + for (unsigned size: {0u, 1u, 32u, 33u}) + { + GasMeter::GasConsumption create = estimateInstruction( + Instruction::CREATE, + {u256(size), u256(0), u256(0)}, + false + ); + GasMeter::GasConsumption create2 = estimateInstruction( + Instruction::CREATE2, + {u256(0), u256(size), u256(0), u256(0)}, + false + ); + BOOST_REQUIRE(!create.isInfinite); + BOOST_REQUIRE(!create2.isInfinite); + u256 const wordCount = (u256(size) + 31) / 32; + u256 const expectedCreateCost = GasCosts::createGas + memoryExpansionCost(size); + BOOST_CHECK_EQUAL(create.value, expectedCreateCost); + BOOST_CHECK_EQUAL( + create2.value, + expectedCreateCost + GasCosts::create2WordGasInTVM * wordCount + ); + } +} + +BOOST_AUTO_TEST_CASE(tvm_memory_limit_and_address_overflow_are_unbounded) +{ + u256 const memoryLimit = GasCosts::memorySizeLimitInTVM; + GasMeter::GasConsumption atLimit = estimateInstruction( + Instruction::MSTORE8, + {u256(1), memoryLimit - 1} + ); + BOOST_REQUIRE(!atLimit.isInfinite); + BOOST_CHECK_EQUAL( + atLimit.value, + u256(GasMeter::runGas(Instruction::MSTORE8, EVMVersion{})) + memoryExpansionCost(memoryLimit) + ); + + GasMeter::GasConsumption aboveLimit = estimateInstruction( + Instruction::MSTORE8, + {u256(1), memoryLimit} + ); + BOOST_CHECK(aboveLimit.isInfinite); + + GasMeter::GasConsumption wrappedEnd = estimateInstruction( + Instruction::MSTORE8, + {u256(1), u256(-1)} + ); + BOOST_CHECK(wrappedEnd.isInfinite); + + // As in java-tron's memNeeded(), a zero-sized access does not expand memory, + // regardless of the offset value. + GasMeter::GasConsumption zeroSizeAtMaxOffset = estimateInstruction( + Instruction::RETURN, + {u256(0), u256(-1)} + ); + BOOST_REQUIRE(!zeroSizeAtMaxOffset.isInfinite); + BOOST_CHECK_EQUAL(zeroSizeAtMaxOffset.value, u256(0)); +} + +BOOST_AUTO_TEST_CASE(mcopy_charges_memory_expansion_to_the_larger_end) +{ + GasMeter::GasConsumption overlappingRanges = estimateInstruction( + Instruction::MCOPY, + {u256(64), u256(0), u256(32)}, + true, + EVMVersion::cancun() + ); + BOOST_REQUIRE(!overlappingRanges.isInfinite); + BOOST_CHECK_EQUAL( + overlappingRanges.value, + u256(GasMeter::runGas(Instruction::MCOPY, EVMVersion::cancun())) + + 2 * GasCosts::copyGas + + memoryExpansionCost(96) + ); + + GasMeter::GasConsumption zeroSizeAtMaxOffsets = estimateInstruction( + Instruction::MCOPY, + {u256(0), u256(-1), u256(-1)}, + true, + EVMVersion::cancun() + ); + BOOST_REQUIRE(!zeroSizeAtMaxOffsets.isInfinite); + BOOST_CHECK_EQUAL( + zeroSizeAtMaxOffsets.value, + u256(GasMeter::runGas(Instruction::MCOPY, EVMVersion::cancun())) + ); +} + +BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_prices) +{ + for (Instruction instruction: {Instruction::DELEGATECALL, Instruction::STATICCALL}) + { + GasMeter::GasConsumption gas = estimateInstruction(instruction, zeroArguments(6), false); + BOOST_REQUIRE(!gas.isInfinite); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::callGasInTVM)); + } + + GasMeter::GasConsumption zeroValueCall = estimateInstruction( + Instruction::CALL, + {u256(0), u256(0), u256(0), u256(0), u256(0), u256(2), u256(0)}, + false + ); + BOOST_REQUIRE(!zeroValueCall.isInfinite); + BOOST_CHECK_EQUAL(zeroValueCall.value, u256(GasCosts::callGasInTVM)); + + GasMeter::GasConsumption valueCall = estimateInstruction( + Instruction::CALL, + {u256(0), u256(0), u256(0), u256(0), u256(1), u256(2), u256(0)}, + false + ); + BOOST_REQUIRE(!valueCall.isInfinite); + BOOST_CHECK_EQUAL( + valueCall.value, + u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) + ); + + GasMeter::GasConsumption valueCallCode = estimateInstruction( + Instruction::CALLCODE, + {u256(0), u256(0), u256(0), u256(0), u256(1), u256(2), u256(0)}, + false + ); + BOOST_REQUIRE(!valueCallCode.isInfinite); + BOOST_CHECK_EQUAL(valueCallCode.value, u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas)); +} + +BOOST_AUTO_TEST_CASE(tvm_calltoken_uses_fixed_base_and_conditional_transfer_prices) +{ + GasMeter::GasConsumption zeroValue = estimateInstruction( + Instruction::CALLTOKEN, + {u256(0), u256(0), u256(0), u256(0), u256(1), u256(0), u256(2), u256(0)}, + false + ); + BOOST_REQUIRE(!zeroValue.isInfinite); + BOOST_CHECK_EQUAL(zeroValue.value, u256(GasCosts::callGasInTVM)); + + GasMeter::GasConsumption nonZeroValue = estimateInstruction( + Instruction::CALLTOKEN, + {u256(0), u256(0), u256(0), u256(0), u256(1), u256(1), u256(2), u256(0)}, + false + ); + BOOST_REQUIRE(!nonZeroValue.isInfinite); + BOOST_CHECK_EQUAL( + nonZeroValue.value, + u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) + ); +} + +BOOST_AUTO_TEST_CASE(tvm_selfdestruct_uses_fixed_price_plus_new_account_cost) +{ + // java-tron (EnergyCost.getSuicideCost3) charges SUICIDE_V2 plus + // NEW_ACCT_CALL when the inheritor is a dead account. The estimator + // conservatively always adds the new-account cost. + GasMeter::GasConsumption gas = estimateInstruction(Instruction::SELFDESTRUCT, zeroArguments(1)); + BOOST_REQUIRE(!gas.isInfinite); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::selfdestructGasInTVM + GasCosts::callNewAccountGas)); +} + BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays) { // java-tron (EnergyCost.getVoteWitnessCost2/3) charges per array @@ -76,7 +360,7 @@ BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays) // i.e. 7 and 11 words: 3 * 11 = 33 on top of the flat vote cost. GasMeter::GasConsumption gas = estimateVote(u256(128), u256(2), u256(256), u256(2)); BOOST_REQUIRE(!gas.isInfinite); - BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGas + 33)); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGasInTVM + 33)); } BOOST_AUTO_TEST_CASE(nativevote_charges_length_slot_for_empty_arrays) @@ -85,7 +369,7 @@ BOOST_AUTO_TEST_CASE(nativevote_charges_length_slot_for_empty_arrays) // ends are 128+32 and 512+32 bytes, i.e. 5 and 17 words: 3 * 17 = 51. GasMeter::GasConsumption gas = estimateVote(u256(128), u256(0), u256(512), u256(0)); BOOST_REQUIRE(!gas.isInfinite); - BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGas + 51)); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::voteGasInTVM + 51)); } BOOST_AUTO_TEST_CASE(nativevote_with_unknown_element_count_is_unbounded) @@ -94,6 +378,28 @@ BOOST_AUTO_TEST_CASE(nativevote_with_unknown_element_count_is_unbounded) BOOST_CHECK(gas.isInfinite); } +BOOST_AUTO_TEST_CASE(nativevote_checks_memory_limit_without_u256_wraparound) +{ + u256 const maxElementCount = GasCosts::memorySizeLimitInTVM / 32 - 1; + GasMeter::GasConsumption atLimit = estimateVote(u256(0), maxElementCount, u256(0), u256(0)); + BOOST_REQUIRE(!atLimit.isInfinite); + BOOST_CHECK_EQUAL( + atLimit.value, + u256(GasCosts::voteGasInTVM) + memoryExpansionCost(GasCosts::memorySizeLimitInTVM) + ); + + GasMeter::GasConsumption aboveLimit = estimateVote(u256(0), maxElementCount + 1, u256(0), u256(0)); + BOOST_CHECK(aboveLimit.isInfinite); + + GasMeter::GasConsumption wrappedProduct = estimateVote( + u256(0), + u256(1) << 251, + u256(0), + u256(0) + ); + BOOST_CHECK(wrappedProduct.isInfinite); +} + BOOST_AUTO_TEST_SUITE_END() } // end namespaces diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 01e1f9ac6c83..1a4c0ed0f506 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include @@ -48,6 +50,21 @@ namespace solidity::frontend::test namespace { + class ComputeMethodProbe: private ComputeMethod + { + public: + static bigint gasNeededFor(AssemblyItems const& _routine, EVMVersion _evmVersion) + { + u256 value = 0; + Params params{/* isCreation = */ false, /* runs = */ 1, /* multiplicity = */ 0, _evmVersion}; + ComputeMethodProbe probe(params, value); + return probe.ComputeMethod::gasNeeded(_routine); + } + + private: + ComputeMethodProbe(Params const& _params, u256 const& _value): ComputeMethod(_params, _value) {} + }; + AssemblyItems addDummyLocations(AssemblyItems const& _input) { // add dummy locations to each item so that we can check that they are not deleted @@ -157,6 +174,15 @@ namespace BOOST_AUTO_TEST_SUITE(Optimiser) +BOOST_AUTO_TEST_CASE(constant_optimizer_exp_uses_tvm_fixed_byte_cost) +{ + for (EVMVersion const& evmVersion: EVMVersion::allVersions()) + BOOST_CHECK_EQUAL( + ComputeMethodProbe::gasNeededFor({Instruction::EXP}, evmVersion), + GasCosts::expGas + GasCosts::expByteGasInTVM + ); +} + BOOST_AUTO_TEST_CASE(cse_push_immutable_same) { AssemblyItem pushImmutable{PushImmutable, 0x1234}; diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index e4523292f6da..6c3bdbe3c456 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -74,6 +74,26 @@ BOOST_AUTO_TEST_CASE(ufixed_types) } } +BOOST_AUTO_TEST_CASE(ethereum_subdenominations_are_invalid) +{ + int64_t id = 0; + for (Literal::SubDenomination subdenomination: { + Literal::SubDenomination::Wei, + Literal::SubDenomination::Gwei, + Literal::SubDenomination::Ether + }) + { + Literal literal( + ++id, + SourceLocation{}, + Token::Number, + std::make_shared("1"), + subdenomination + ); + BOOST_CHECK(TypeProvider::forLiteral(literal) == nullptr); + } +} + BOOST_AUTO_TEST_CASE(storage_layout_simple) { MemberList members(MemberList::MemberMap({ diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 7be21863abac..eced46153cad 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -324,6 +324,78 @@ BOOST_AUTO_TEST_CASE(assume_object_input) BOOST_CHECK(!containsAtMostWarnings(result)); } +BOOST_AUTO_TEST_CASE(settings_must_be_an_object) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidSettings: {Json(nullptr), Json::array()}) + { + Json input = SolidityCode().json(); + input["settings"] = invalidSettings; + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"settings\" must be an object")); + } +} + +BOOST_AUTO_TEST_CASE(metadata_settings_must_be_an_object) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidMetadataSettings: {Json(nullptr), Json::array()}) + { + Json input = SolidityCode().json(); + input["settings"]["metadata"] = invalidMetadataSettings; + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"settings.metadata\" must be an object")); + } +} + +BOOST_AUTO_TEST_CASE(output_selection_must_be_an_object) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidOutputSelection: {Json(nullptr), Json::array()}) + { + Json input = SolidityCode().json(); + input["settings"]["outputSelection"] = invalidOutputSelection; + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"settings.outputSelection\" must be an object")); + } +} + +BOOST_AUTO_TEST_CASE(language_must_be_a_string) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidLanguage: {Json(nullptr), Json::array()}) + { + Json input = SolidityCode().json(); + input["language"] = invalidLanguage; + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"language\" must be a string.")); + } +} + +BOOST_AUTO_TEST_CASE(smtlib2responses_must_be_an_object) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidResponses: {Json(nullptr), Json::array()}) + { + Json input = SolidityCode().json(); + input["auxiliaryInput"]["smtlib2responses"] = invalidResponses; + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"auxiliaryInput.smtlib2responses\" must be an object.")); + } +} + +BOOST_AUTO_TEST_CASE(debug_info_components_must_be_strings) +{ + frontend::StandardCompiler compiler; + for (Json const& invalidComponent: {Json(nullptr), Json(1)}) + { + Json input = SolidityCode().json(); + input["settings"]["debug"]["debugInfo"] = Json::array({invalidComponent}); + Json result = compiler.compile(input); + BOOST_CHECK(containsError(result, "JSONError", "Every value in settings.debug.debugInfo must be a string.")); + } +} + BOOST_AUTO_TEST_CASE(invalid_language) { char const* input = R"( @@ -529,9 +601,9 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["evm"]["bytecode"]["object"].is_string()); BOOST_CHECK_EQUAL( solidity::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].get()), - std::string("6080604052348015600e575f5ffd5b5060") + + std::string("6080604052348015600e575f5ffd5b50d380156019575f5ffd5b50d280156024575f5ffd5b5060") + (VersionIsRelease ? "3e" : util::toHex(bytes{uint8_t(60 + VersionStringStrict.size())})) + - "80601a5f395ff3fe60806040525f5ffdfe" + "8060305f395ff3fe60806040525f5ffdfe" ); BOOST_CHECK(contract["evm"]["assembly"].is_string()); BOOST_CHECK(contract["evm"]["assembly"].get().find( @@ -539,10 +611,16 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "callvalue\n dup1\n " "iszero\n tag_1\n jumpi\n " "revert(0x00, 0x00)\n" - "tag_1:\n pop\n dataSize(sub_0)\n dup1\n " + "tag_1:\n pop\n calltokenid\n dup1\n " + "iszero\n tag_2\n jumpi\n " + "revert(0x00, 0x00)\n" + "tag_2:\n pop\n calltokenvalue\n dup1\n " + "iszero\n tag_3\n jumpi\n " + "revert(0x00, 0x00)\n" + "tag_3:\n pop\n dataSize(sub_0)\n dup1\n " "dataOffset(sub_0)\n 0x00\n codecopy\n 0x00\n return\nstop\n\nsub_0: assembly {\n " "/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n " - "revert(0x00, 0x00)\n\n auxdata: 0xa26469706673582212" + "revert(0x00, 0x00)\n\n auxdata: 0xa26474726f6e582212" ) == 0); BOOST_CHECK(contract["evm"]["gasEstimates"].is_object()); BOOST_CHECK_EQUAL(contract["evm"]["gasEstimates"].size(), 1); @@ -576,6 +654,28 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "{\"begin\":0,\"end\":14,\"name\":\"tag\",\"source\":0,\"value\":\"1\"}," "{\"begin\":0,\"end\":14,\"name\":\"JUMPDEST\",\"source\":0}," "{\"begin\":0,\"end\":14,\"name\":\"POP\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"CALLTOKENID\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"DUP1\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"ISZERO\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH [tag]\",\"source\":0,\"value\":\"2\"}," + "{\"begin\":0,\"end\":14,\"name\":\"JUMPI\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," + "{\"begin\":0,\"end\":14,\"name\":\"REVERT\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"tag\",\"source\":0,\"value\":\"2\"}," + "{\"begin\":0,\"end\":14,\"name\":\"JUMPDEST\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"POP\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"CALLTOKENVALUE\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"DUP1\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"ISZERO\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH [tag]\",\"source\":0,\"value\":\"3\"}," + "{\"begin\":0,\"end\":14,\"name\":\"JUMPI\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," + "{\"begin\":0,\"end\":14,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"}," + "{\"begin\":0,\"end\":14,\"name\":\"REVERT\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"tag\",\"source\":0,\"value\":\"3\"}," + "{\"begin\":0,\"end\":14,\"name\":\"JUMPDEST\",\"source\":0}," + "{\"begin\":0,\"end\":14,\"name\":\"POP\",\"source\":0}," "{\"begin\":0,\"end\":14,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," "{\"begin\":0,\"end\":14,\"name\":\"DUP1\",\"source\":0}," "{\"begin\":0,\"end\":14,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," @@ -599,6 +699,42 @@ BOOST_AUTO_TEST_CASE(basic_compilation) ); } +BOOST_AUTO_TEST_CASE(tron_builtin_via_ir_codegen_guards) +{ + Json input = createLanguageAndSourcesSection("Solidity", {{"A.sol", R"( +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.30; +contract C { + function validate(address account, bytes32 content, bytes[] memory signatures) public view returns (bool) { + return validatemultisign(account, 0, content, signatures); + } + function transfer(address payable target, uint256 value, trcToken tokenId) public { + target.transferToken(value, tokenId); + } + function mint( + bytes32[9] memory output, + bytes32[2] memory bindingSignature, + uint64 value, + bytes32 signHash, + bytes32[33] memory frontier, + uint256 leafCount + ) public pure returns (bytes32[] memory) { + return verifyMintProof(output, bindingSignature, value, signHash, frontier, leafCount); + } +} +)"}}); + input["settings"]["viaIR"] = true; + input["settings"]["outputSelection"]["*"]["*"] = Json::array({"ir"}); + + Json const output = compile(input.dump()); + BOOST_REQUIRE(containsAtMostWarnings(output)); + std::string const ir = output["contracts"]["A.sol"]["C"]["ir"].get(); + BOOST_TEST(ir.find("if iszero(gt(0x8000000000000000") != std::string::npos); + BOOST_TEST(ir.find("exp(2, 63)") == std::string::npos); + BOOST_TEST(ir.find("if lt(returndatasize(), 0x20)") != std::string::npos); + BOOST_TEST(ir.find("if mod(returndatasize(), 0x20) { revert(0, 0) }") != std::string::npos); +} + BOOST_AUTO_TEST_CASE(compilation_error) { char const* input = R"( @@ -1781,6 +1917,56 @@ BOOST_AUTO_TEST_CASE(stopAfter_ast_output) BOOST_CHECK(result["sources"]["a.sol"]["ast"].is_object()); } +BOOST_AUTO_TEST_CASE(solidity_ast_rejects_ethereum_subdenominations) +{ + frontend::StandardCompiler compiler; + Json sourceInput = createLanguageAndSourcesSection("Solidity", {{ + "A.sol", + "contract C { function f() public pure returns (uint256) { return 1 trx; } }" + }}); + sourceInput["settings"]["outputSelection"]["*"][""] = Json::array({"ast"}); + + Json sourceResult = compiler.compile(sourceInput); + BOOST_REQUIRE(containsAtMostWarnings(sourceResult)); + BOOST_REQUIRE(sourceResult["sources"]["A.sol"]["ast"].is_object()); + + for (std::string const& subdenomination: {"wei"s, "gwei"s, "ether"s}) + { + Json ast = sourceResult["sources"]["A.sol"]["ast"]; + bool literalFound = false; + auto replaceSubdenomination = [&](auto&& _replaceSubdenomination, Json& _node) -> void { + if (_node.is_object()) + { + if (_node.value("nodeType", "") == "Literal" && _node.value("subdenomination", "") == "trx") + { + _node["subdenomination"] = subdenomination; + literalFound = true; + } + for (Json& value: _node) + _replaceSubdenomination(_replaceSubdenomination, value); + } + else if (_node.is_array()) + for (Json& value: _node) + _replaceSubdenomination(_replaceSubdenomination, value); + }; + replaceSubdenomination(replaceSubdenomination, ast); + BOOST_REQUIRE(literalFound); + + Json astInput = Json::object(); + astInput["language"] = "SolidityAST"; + astInput["sources"]["A.sol"]["ast"] = std::move(ast); + astInput["settings"]["outputSelection"]["*"]["*"] = Json::array({"evm.bytecode.object"}); + + Json astResult = compiler.compile(astInput); + BOOST_CHECK(containsError( + astResult, + "JSONError", + "Failed to import AST: Ether unit denomination is not supported by the compiler" + )); + BOOST_CHECK(!astResult.contains("contracts")); + } +} + BOOST_AUTO_TEST_CASE(dependency_tracking_of_abstract_contract) { char const* input = R"( diff --git a/test/libsolidity/smtCheckerTests/special/ether_units.sol b/test/libsolidity/smtCheckerTests/special/ether_units.sol deleted file mode 100644 index 9c0e8ea71ff0..000000000000 --- a/test/libsolidity/smtCheckerTests/special/ether_units.sol +++ /dev/null @@ -1,17 +0,0 @@ -contract D { - function f() public pure { - assert(1000000000000000000 wei == 1 ether); - assert(100000000000000000 wei == 1 ether); - assert(1000000000 wei == 1 gwei); - assert(100000000 wei == 1 gwei); - assert(1000000000 gwei == 1 ether); - assert(100000000 gwei == 1 ether); - } -} -// ==== -// SMTEngine: all -// ---- -// Warning 6328: (89-130): CHC: Assertion violation happens here. -// Warning 6328: (170-201): CHC: Assertion violation happens here. -// Warning 6328: (243-276): CHC: Assertion violation happens here. -// Info 1391: CHC: 3 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them. diff --git a/test/libsolidity/smtCheckerTests/special/trx_units.sol b/test/libsolidity/smtCheckerTests/special/trx_units.sol new file mode 100644 index 000000000000..3f0d75021d14 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/trx_units.sol @@ -0,0 +1,17 @@ +contract D { + function f() public pure { + assert(1000000 sun == 1 trx); + assert(100000 sun == 1 trx); + assert(1 sun == 1); + assert(2 sun == 1); + assert(2 trx == 2000000 sun); + assert(2 trx == 200000 sun); + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (75-102): CHC: Assertion violation happens here. +// Warning 6328: (128-146): CHC: Assertion violation happens here. +// Warning 6328: (182-209): CHC: Assertion violation happens here. +// Info 1391: CHC: 3 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them. diff --git a/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values.sol b/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values.sol new file mode 100644 index 000000000000..f8e2e9cbdfd8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values.sol @@ -0,0 +1,14 @@ +library L { + function check() public view { + assert(msg.value == 0); + assert(msg.tokenvalue == 0); + assert(msg.tokenid == 0); + } +} +// ==== +// SMTEngine: chc +// SMTIgnoreCex: yes +// ---- +// Warning 6328: (46-68): CHC: Assertion violation happens here. +// Warning 6328: (72-99): CHC: Assertion violation happens here. +// Warning 6328: (103-127): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values_bmc.sol b/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values_bmc.sol new file mode 100644 index 000000000000..2f429cc51635 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values_bmc.sol @@ -0,0 +1,14 @@ +library L { + function check() public view { + assert(msg.value == 0); + assert(msg.tokenvalue == 0); + assert(msg.tokenid == 0); + } +} +// ==== +// SMTEngine: bmc +// SMTIgnoreCex: yes +// ---- +// Warning 4661: (46-68): BMC: Assertion violation happens here. +// Warning 4661: (72-99): BMC: Assertion violation happens here. +// Warning 4661: (103-127): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/tron/magic_members.sol b/test/libsolidity/smtCheckerTests/tron/magic_members.sol new file mode 100644 index 000000000000..2490de027d66 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/magic_members.sol @@ -0,0 +1,39 @@ +contract C { + function chainParameters() external view { + assert(chain.totalNetLimit <= type(uint64).max); + assert(chain.totalNetWeight <= type(uint64).max); + assert(chain.totalEnergyCurrentLimit <= type(uint64).max); + assert(chain.totalEnergyWeight <= type(uint64).max); + assert(chain.unfreezeDelayDays <= type(uint64).max); + } + + function tokenCallParameterRanges() external payable { + assert(msg.tokenvalue <= type(uint256).max); + assert(msg.tokenid <= type(trcToken).max); + } + + function nonPayableTokenCallParameters() external view { + (uint256 trxValue, uint256 tokenValue, trcToken tokenId) = readTokenCallParameters(); + assert(trxValue == 0); + assert(tokenValue == 0); + assert(tokenId == 0); + } + + function readTokenCallParameters() internal view returns (uint256, uint256, trcToken) { + return (msg.value, msg.tokenvalue, msg.tokenid); + } +} +// ==== +// SMTEngine: chc +// SMTShowProvedSafe: yes +// ---- +// Info 9576: (59-106): CHC: Assertion violation check is safe! +// Info 9576: (110-158): CHC: Assertion violation check is safe! +// Info 9576: (162-219): CHC: Assertion violation check is safe! +// Info 9576: (223-274): CHC: Assertion violation check is safe! +// Info 9576: (278-329): CHC: Assertion violation check is safe! +// Info 9576: (393-436): CHC: Assertion violation check is safe! +// Info 9576: (440-481): CHC: Assertion violation check is safe! +// Info 9576: (635-656): CHC: Assertion violation check is safe! +// Info 9576: (660-683): CHC: Assertion violation check is safe! +// Info 9576: (687-707): CHC: Assertion violation check is safe! diff --git a/test/libsolidity/smtCheckerTests/tron/magic_members_bmc.sol b/test/libsolidity/smtCheckerTests/tron/magic_members_bmc.sol new file mode 100644 index 000000000000..c53d218872a0 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/magic_members_bmc.sol @@ -0,0 +1,39 @@ +contract C { + function chainParameters() external view { + assert(chain.totalNetLimit <= type(uint64).max); + assert(chain.totalNetWeight <= type(uint64).max); + assert(chain.totalEnergyCurrentLimit <= type(uint64).max); + assert(chain.totalEnergyWeight <= type(uint64).max); + assert(chain.unfreezeDelayDays <= type(uint64).max); + } + + function tokenCallParameterRanges() external payable { + assert(msg.tokenvalue <= type(uint256).max); + assert(msg.tokenid <= type(trcToken).max); + } + + function nonPayableTokenCallParameters() external view { + (uint256 trxValue, uint256 tokenValue, trcToken tokenId) = readTokenCallParameters(); + assert(trxValue == 0); + assert(tokenValue == 0); + assert(tokenId == 0); + } + + function readTokenCallParameters() internal view returns (uint256, uint256, trcToken) { + return (msg.value, msg.tokenvalue, msg.tokenid); + } +} +// ==== +// SMTEngine: bmc +// SMTShowProvedSafe: yes +// ---- +// Info 2961: (59-106): BMC: Assertion violation check is safe! +// Info 2961: (110-158): BMC: Assertion violation check is safe! +// Info 2961: (162-219): BMC: Assertion violation check is safe! +// Info 2961: (223-274): BMC: Assertion violation check is safe! +// Info 2961: (278-329): BMC: Assertion violation check is safe! +// Info 2961: (393-436): BMC: Assertion violation check is safe! +// Info 2961: (440-481): BMC: Assertion violation check is safe! +// Info 2961: (635-656): BMC: Assertion violation check is safe! +// Info 2961: (660-683): BMC: Assertion violation check is safe! +// Info 2961: (687-707): BMC: Assertion violation check is safe! diff --git a/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol b/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol new file mode 100644 index 000000000000..e6ef09cdc08a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol @@ -0,0 +1,13 @@ +contract C { + function f(uint256 amount) public { + uint256 balanceBefore = address(this).balance; + payable(address(this)).freeze(amount, 0); + assert(address(this).balance == balanceBefore); + } +} +// ==== +// SMTEngine: all +// SMTIgnoreCex: yes +// ---- +// Warning 4588: (116-156): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 6328: (166-212): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges.sol b/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges.sol new file mode 100644 index 000000000000..85a11e082ab8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges.sol @@ -0,0 +1,23 @@ +contract C { + function withdrawReward() external { + assert(withdrawreward() <= type(uint256).max); + } + + function cancelAllUnfreezeV2() external { + assert(cancelallunfreezev2() <= type(uint256).max); + } + + function withdrawExpireUnfreeze() external { + assert(withdrawexpireunfreeze() <= type(uint256).max); + } +} +// ==== +// SMTEngine: chc +// SMTShowProvedSafe: yes +// ---- +// Warning 4588: (60-76): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 4588: (156-177): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 4588: (260-284): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Info 9576: (53-98): CHC: Assertion violation check is safe! +// Info 9576: (149-199): CHC: Assertion violation check is safe! +// Info 9576: (253-306): CHC: Assertion violation check is safe! diff --git a/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges_bmc.sol b/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges_bmc.sol new file mode 100644 index 000000000000..e1f83a097835 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges_bmc.sol @@ -0,0 +1,23 @@ +contract C { + function withdrawReward() external { + assert(withdrawreward() <= type(uint256).max); + } + + function cancelAllUnfreezeV2() external { + assert(cancelallunfreezev2() <= type(uint256).max); + } + + function withdrawExpireUnfreeze() external { + assert(withdrawexpireunfreeze() <= type(uint256).max); + } +} +// ==== +// SMTEngine: bmc +// SMTShowProvedSafe: yes +// ---- +// Warning 4588: (60-76): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 4588: (156-177): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 4588: (260-284): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Info 2961: (53-98): BMC: Assertion violation check is safe! +// Info 2961: (149-199): BMC: Assertion violation check is safe! +// Info 2961: (253-306): BMC: Assertion violation check is safe! diff --git a/test/libsolidity/smtCheckerTests/types/address_balance.sol b/test/libsolidity/smtCheckerTests/types/address_balance.sol index a24fa32b18d2..3ca116ffb792 100644 --- a/test/libsolidity/smtCheckerTests/types/address_balance.sol +++ b/test/libsolidity/smtCheckerTests/types/address_balance.sol @@ -1,7 +1,7 @@ contract C { function f(address a, address b) public view { - uint x = b.balance + 1000 ether; + uint x = b.balance + 1000 trx; assert(a.balance > b.balance); } } @@ -10,5 +10,5 @@ contract C // SMTIgnoreCex: yes // ---- // Warning 2072: (63-69): Unused local variable. -// Warning 4984: (72-94): CHC: Overflow (resulting value larger than 2**256 - 1) happens here. -// Warning 6328: (98-127): CHC: Assertion violation happens here. +// Warning 4984: (72-92): CHC: Overflow (resulting value larger than 2**256 - 1) happens here. +// Warning 6328: (96-125): CHC: Assertion violation happens here. diff --git a/test/libsolidity/syntaxTests/tron/builtin_named_arguments.sol b/test/libsolidity/syntaxTests/tron/builtin_named_arguments.sol new file mode 100644 index 000000000000..ed9021752450 --- /dev/null +++ b/test/libsolidity/syntaxTests/tron/builtin_named_arguments.sol @@ -0,0 +1,16 @@ +contract C { + function f(bytes[] memory signatures) public view { + validatemultisign({ + account: address(this), + permissionId: 0, + content: bytes32(0), + signatures: signatures + }); + isSrCandidate({srCandidate: address(this)}); + voteCount({voter: address(this), srCandidate: address(this)}); + totalVoteCount({voter: address(this)}); + receivedVoteCount({srCandidate: address(this)}); + usedVoteCount({voter: address(this)}); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol b/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol new file mode 100644 index 000000000000..34b9a3b1587f --- /dev/null +++ b/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol @@ -0,0 +1,11 @@ +contract C { + function f() public view returns (bool, uint64) { + bool valid = validatemultisign(address(0), 0, bytes32(0), new bytes[](0)); + return (valid, chain.totalNetLimit); + } +} +// ==== +// EVMVersion: #include +#include +#include #include #include #include +#include + #include using namespace solidity::langutil; @@ -384,4 +388,21 @@ BOOST_FIXTURE_TEST_CASE(switch_statement_large_custom_weights, CustomWeightFixtu BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(YulEVMMetrics) + +BOOST_AUTO_TEST_CASE(exp_uses_tvm_fixed_byte_cost) +{ + for (EVMVersion const& evmVersion: EVMVersion::allVersions()) + { + auto const [runCost, dataCost] = GasMeterVisitor::instructionCosts( + evmasm::Instruction::EXP, + EVMDialect::strictAssemblyForEVM(evmVersion, std::nullopt) + ); + BOOST_CHECK_EQUAL(runCost, evmasm::GasCosts::expGas + evmasm::GasCosts::expByteGasInTVM); + BOOST_CHECK_EQUAL(dataCost, evmasm::GasCosts::createDataGas); + } +} + +BOOST_AUTO_TEST_SUITE_END() + } diff --git a/test/lsp.py b/test/lsp.py index 11007ae822ec..16da8a25b1ac 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -1330,6 +1330,22 @@ def user_interaction_failed_autoupdate(self, test, sub_dir): # }}} # {{{ actual tests + def test_project_directory_without_workspace_root(self, solc: JsonRpcProcess) -> None: + """ + A missing workspace root falls back to the filesystem root internally. Project-directory + loading must not interpret that fallback as a request to scan the entire filesystem. + """ + self.setup_lsp( + solc, + expose_project_root=False, + file_load_strategy=FileLoadStrategy.ProjectDirectory + ) + TEST_NAME = 'publish_diagnostics_3' + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) + + self.expect_equal(len(published_diagnostics), 1, "Only the directly opened file is analyzed") + self.expect_equal(published_diagnostics[0]['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") + def test_analyze_all_project_files_flat(self, solc: JsonRpcProcess) -> None: """ Tests the option (default) to analyze all .sol project files even when they have not been actively