From 64ca6a07ac62a31de0bc28761d1884bac0910ce3 Mon Sep 17 00:00:00 2001 From: r0qs <457348+r0qs@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:49:09 -0300 Subject: [PATCH 01/41] legacy: Fix handling of storage array boundaries (deletion, cleanup, and copy) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Kirchner Co-authored-by: clonker <1685266+clonker@users.noreply.github.com> Co-authored-by: Kamil Śliwak (cherry picked from commit 2ab47bdc5bd5c2fe6263c3656791264392ad89bd) --- libsolidity/codegen/ArrayUtils.cpp | 469 ++++------------------------- libsolidity/codegen/ArrayUtils.h | 10 +- 2 files changed, 68 insertions(+), 411 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 455cfe0842ed..841161fa91b6 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -46,256 +46,65 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // this copies source to target and also clears target if it was larger // need to leave "target_ref target_byte_off" on the stack at the end - // stack layout: [source_ref] [source length] target_ref (top) + // stack layout: source_ref [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - Type const* targetBaseType = _targetType.baseType(); - Type const* sourceBaseType = _sourceType.baseType(); - // TODO unroll loop for small sizes - bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; bool fromCalldata = _sourceType.location() == DataLocation::CallData; - bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; - bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->storageBytes() <= 16; - bool haveByteOffsetTarget = !directCopy && targetBaseType->storageBytes() <= 16; - unsigned byteOffsetSize = (haveByteOffsetSource ? 1u : 0u) + (haveByteOffsetTarget ? 1u : 0u); + bool haveSourceLengthOnStack = fromCalldata && _sourceType.isDynamicallySized(); - // stack: source_ref [source_length] target_ref - // store target_ref for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); // stack: target_ref source_ref [source_length] - if (_targetType.isByteArrayOrString()) + if (_sourceType.baseType()->category() == Type::Category::Array) { - // stack: target_ref source_ref [source_length] - if (fromCalldata && _sourceType.isDynamicallySized()) - { - // stack: target_ref source_ref source_length - m_context << Instruction::SWAP1; - // stack: target_ref source_length source_ref - m_context << Instruction::DUP3; - // stack: target_ref source_length source_ref target_ref - m_context.callYulFunction( - m_context.utilFunctions().copyByteArrayToStorageFunction(_sourceType, _targetType), - 3, - 0 - ); - } - else - { - // stack: target_ref source_ref - m_context << Instruction::DUP2; - // stack: target_ref source_ref target_ref - m_context.callYulFunction( - m_context.utilFunctions().copyByteArrayToStorageFunction(_sourceType, _targetType), - 2, - 0 - ); - } - // stack: target_ref - return; + // TODO: This limitation can now be removed since we use Yul utility functions that handle nested arrays correctly. + // The old inline assembly implementation couldn't handle nested calldata dynamic arrays, but the Yul functions + // support them through recursive calls. We keep this check temporarily for backward compatibility. + auto const& sourceBaseArrayType = dynamic_cast(*_sourceType.baseType()); + solUnimplementedAssert( + !fromCalldata || + !_sourceType.isDynamicallyEncoded() || + !sourceBaseArrayType.isDynamicallySized(), + "Copying nested calldata dynamic arrays to storage is not implemented in the old code generator." + ); } - - // retrieve source length - if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) - retrieveLength(_sourceType); // otherwise, length is already there - if (_sourceType.location() == DataLocation::Memory && _sourceType.isDynamicallySized()) + else { - // increment source pointer to point to data - m_context << Instruction::SWAP1 << u256(0x20); - m_context << Instruction::ADD << Instruction::SWAP1; + // TODO: This limitation can now be removed since we use Yul utility functions that handle non-value types correctly. + // The old inline assembly implementation couldn't handle copying arrays of non-value types from memory or calldata to storage, + // but the Yul functions support them. We keep this check temporarily for backward compatibility. + bool fromMemoryOrCalldata = _sourceType.location() == DataLocation::Memory || _sourceType.location() == DataLocation::CallData; + solUnimplementedAssert( + _sourceType.baseType()->isValueType() || !fromMemoryOrCalldata, + "Copying of type " + _sourceType.toString(false) + " to storage is not supported in legacy (only supported by the IR pipeline). " + + "Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON)." + ); } - // stack: target_ref source_ref source_length - Type const* targetType = &_targetType; - Type const* sourceType = &_sourceType; - m_context.callLowLevelFunction( - "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), - 3, - 1, - [=](CompilerContext& _context) - { - ArrayUtils utils(_context); - ArrayType const& _sourceType = dynamic_cast(*sourceType); - ArrayType const& _targetType = dynamic_cast(*targetType); - // stack: target_ref source_ref source_length - _context << Instruction::DUP3; - // stack: target_ref source_ref source_length target_ref - utils.retrieveLength(_targetType); - // stack: target_ref source_ref source_length target_ref target_length - if (_targetType.isDynamicallySized()) - { - // store new target length - solAssert(!_targetType.isByteArrayOrString()); - _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; - } - if (sourceBaseType->category() == Type::Category::Mapping) - { - solAssert(targetBaseType->category() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == DataLocation::Storage, ""); - // nothing to copy - _context - << Instruction::POP << Instruction::POP - << Instruction::POP << Instruction::POP; - return; - } - // stack: target_ref source_ref source_length target_ref target_length - // compute hashes (data positions) - _context << Instruction::SWAP1; - if (_targetType.isDynamicallySized()) - CompilerUtils(_context).computeHashStatic(); - // stack: target_ref source_ref source_length target_length target_data_pos - _context << Instruction::SWAP1; - utils.convertLengthToSize(_targetType); - _context << Instruction::DUP2 << Instruction::ADD; - // stack: target_ref source_ref source_length target_data_pos target_data_end - _context << Instruction::SWAP3; - // stack: target_ref target_data_end source_length target_data_pos source_ref - - evmasm::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); - solAssert(!_targetType.isByteArrayOrString()); - // skip copying if source length is zero - _context << Instruction::DUP3 << Instruction::ISZERO; - _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); - - if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) - CompilerUtils(_context).computeHashStatic(); - // stack: target_ref target_data_end source_length target_data_pos source_data_pos - _context << Instruction::SWAP2; - utils.convertLengthToSize(_sourceType); - _context << Instruction::DUP3 << Instruction::ADD; - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end - if (haveByteOffsetTarget) - _context << u256(0); - if (haveByteOffsetSource) - _context << u256(0); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - evmasm::AssemblyItem copyLoopStart = _context.newTag(); - _context << copyLoopStart; - // check for loop condition - _context - << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) - << Instruction::GT << Instruction::ISZERO; - evmasm::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // copy - if (sourceBaseType->category() == Type::Category::Array) - { - solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); - auto const& sourceBaseArrayType = dynamic_cast(*sourceBaseType); - - solUnimplementedAssert( - _sourceType.location() != DataLocation::CallData || - !_sourceType.isDynamicallyEncoded() || - !sourceBaseArrayType.isDynamicallySized(), - "Copying nested calldata dynamic arrays to storage is not implemented in the old code generator." - ); - _context << Instruction::DUP3; - if (sourceBaseArrayType.location() == DataLocation::Memory) - _context << Instruction::MLOAD; - _context << Instruction::DUP3; - utils.copyArrayToStorage(dynamic_cast(*targetBaseType), sourceBaseArrayType); - _context << Instruction::POP; - } - else if (directCopy) - { - solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); - _context - << Instruction::DUP3 << Instruction::SLOAD - << Instruction::DUP3 << Instruction::SSTORE; - } - else - { - // Note that we have to copy each element on its own in case conversion is involved. - // We might copy too much if there is padding at the last element, but this way end - // checking is easier. - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - _context << dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == DataLocation::Storage) - { - if (haveByteOffsetSource) - _context << Instruction::DUP2; - else - _context << u256(0); - StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); - } - else if (sourceBaseType->isValueType()) - CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - else - solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON)"); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... - assertThrow( - 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, - StackTooDeepError, - util::stackTooDeepString - ); - // fetch target storage reference - _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); - if (haveByteOffsetTarget) - _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); - else - _context << u256(0); - StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - } - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // increment source - if (haveByteOffsetSource) - utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); - else - { - _context << swapInstruction(2 + byteOffsetSize); - if (sourceIsStorage) - _context << sourceBaseType->storageSize(); - else if (_sourceType.location() == DataLocation::Memory) - _context << sourceBaseType->memoryHeadSize(); - else - _context << sourceBaseType->calldataHeadSize(); - _context - << Instruction::ADD - << swapInstruction(2 + byteOffsetSize); - } - // increment target - if (haveByteOffsetTarget) - utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - else - _context - << swapInstruction(1 + byteOffsetSize) - << targetBaseType->storageSize() - << Instruction::ADD - << swapInstruction(1 + byteOffsetSize); - _context.appendJumpTo(copyLoopStart); - _context << copyLoopEnd; - if (haveByteOffsetTarget) - { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); - _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); - utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - _context.appendJumpTo(copyLoopEnd); - - _context << copyCleanupLoopEnd; - _context << Instruction::POP; // might pop the source, but then target is popped next - } - if (haveByteOffsetSource) - _context << Instruction::POP; - _context << copyLoopEndWithoutByteOffset; + if (haveSourceLengthOnStack) + { + // stack: target_ref source_ref source_length + m_context << Instruction::SWAP1; + // stack: target_ref source_length source_ref + m_context << Instruction::DUP3; + // stack: target_ref source_length source_ref target_ref + } + else + { + // stack: target_ref source_ref + m_context << Instruction::DUP2; + // stack: target_ref source_ref target_ref + } - // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end - _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - if (targetBaseType->storageBytes() < 32) - utils.clearStorageLoop(TypeProvider::uint256()); - else - utils.clearStorageLoop(targetBaseType); - _context << Instruction::POP; - } + m_context.callYulFunction( + m_context.utilFunctions().copyArrayToStorageFunction(_sourceType, _targetType), + haveSourceLengthOnStack ? 3 : 2, + 0 ); + // stack: target_ref } void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const @@ -586,9 +395,9 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const } else { - _context << Instruction::DUP1 << _type.length(); + _context << _type.length(); ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::ADD << Instruction::SWAP1; + // stack: storage_ref slot_count if (_type.baseType()->storageBytes() < 32) ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); else @@ -623,13 +432,12 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const } // stack: ref old_length convertLengthToSize(_type); - // compute data positions + // stack: ref slot_count m_context << Instruction::SWAP1; CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos - m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD - << Instruction::SWAP1; - // stack: data_pos_end data_pos + // stack: slot_count data_pos + m_context << Instruction::SWAP1; + // stack: data_pos slot_count if (_type.storageStride() < 32) clearStorageLoop(TypeProvider::uint256()); else @@ -639,156 +447,6 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const m_context << Instruction::POP; } -void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const -{ - Type const* type = &_typeIn; - m_context.callLowLevelFunction( - "$resizeDynamicArray_" + _typeIn.identifier(), - 2, - 0, - [type](CompilerContext& _context) - { - ArrayType const& _type = dynamic_cast(*type); - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - unsigned stackHeightStart = _context.stackHeight(); - evmasm::AssemblyItem resizeEnd = _context.newTag(); - - // stack: ref new_length - // fetch old length - ArrayUtils(_context).retrieveLength(_type, 1); - // stack: ref new_length old_length - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); - - // Special case for short byte arrays, they are stored together with their length - if (_type.isByteArrayOrString()) - { - evmasm::AssemblyItem regularPath = _context.newTag(); - // We start by a large case-distinction about the old and new length of the byte array. - - _context << Instruction::DUP3 << Instruction::SLOAD; - // stack: ref new_length current_length ref_value - - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context << Instruction::DUP2 << u256(31) << Instruction::LT; - evmasm::AssemblyItem currentIsLong = _context.appendConditionalJump(); - _context << Instruction::DUP3 << u256(31) << Instruction::LT; - evmasm::AssemblyItem newIsLong = _context.appendConditionalJump(); - - // Here: short -> short - - // Compute 1 << (256 - 8 * new_size) - evmasm::AssemblyItem shortToShort = _context.newTag(); - _context << shortToShort; - _context << Instruction::DUP3 << u256(8) << Instruction::MUL; - _context << u256(0x100) << Instruction::SUB; - _context << u256(2) << Instruction::EXP; - // Divide and multiply by that value, clearing bits. - _context << Instruction::DUP1 << Instruction::SWAP2; - _context << Instruction::DIV << Instruction::MUL; - // Insert 2*length. - _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - _context << Instruction::OR; - // Store. - _context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - _context.appendJumpTo(resizeEnd); - - _context.adjustStackOffset(1); // we have to do that because of the jumps - // Here: short -> long - - _context << newIsLong; - // stack: ref new_length current_length ref_value - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - // Zero out lower-order byte. - _context << u256(0xff) << Instruction::NOT << Instruction::AND; - // Store at data location. - _context << Instruction::DUP4; - CompilerUtils(_context).computeHashStatic(); - _context << Instruction::SSTORE; - // stack: ref new_length current_length - // Store new length: Compute 2*length + 1 and store it. - _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; - _context << u256(1) << Instruction::ADD; - // stack: ref new_length current_length 2*new_length+1 - _context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - _context.appendJumpTo(resizeEnd); - - _context.adjustStackOffset(1); // we have to do that because of the jumps - - _context << currentIsLong; - _context << Instruction::DUP3 << u256(31) << Instruction::LT; - _context.appendConditionalJumpTo(regularPath); - - // Here: long -> short - // Read the first word of the data and store it on the stack. Clear the data location and - // then jump to the short -> short case. - - // stack: ref new_length current_length ref_value - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context << Instruction::POP << Instruction::DUP3; - CompilerUtils(_context).computeHashStatic(); - _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location - _context << Instruction::DUP3; - ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); - _context << Instruction::POP; - // stack: ref new_length current_length first_word - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context.appendJumpTo(shortToShort); - - _context << regularPath; - // stack: ref new_length current_length ref_value - _context << Instruction::POP; - } - - // Change of length for a regular array (i.e. length at location, data at KECCAK256(location)). - // stack: ref new_length old_length - // store new length - _context << Instruction::DUP2; - if (_type.isByteArrayOrString()) - // For a "long" byte array, store length as 2*length+1 - _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - _context << Instruction::DUP4 << Instruction::SSTORE; - // skip if size is not reduced - _context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::GT << Instruction::ISZERO; - _context.appendConditionalJumpTo(resizeEnd); - - // size reduced, clear the end of the array - // stack: ref new_length old_length - ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::DUP2; - ArrayUtils(_context).convertLengthToSize(_type); - // stack: ref new_length old_size new_size - // compute data positions - _context << Instruction::DUP4; - CompilerUtils(_context).computeHashStatic(); - // stack: ref new_length old_size new_size data_pos - _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; - // stack: ref new_length data_pos new_size delete_end - _context << Instruction::SWAP2 << Instruction::ADD; - // stack: ref new_length delete_end delete_start - if (_type.storageStride() < 32) - ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); - else - ArrayUtils(_context).clearStorageLoop(_type.baseType()); - - _context << resizeEnd; - // cleanup - _context << Instruction::POP << Instruction::POP << Instruction::POP; - solAssert(_context.stackHeight() == stackHeightStart - 2, ""); - } - ); -} - void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const { solAssert(_type.location() == DataLocation::Storage, ""); @@ -936,29 +594,32 @@ void ArrayUtils::clearStorageLoop(Type const* _type) const _context << Instruction::POP; return; } - // stack: end_pos pos - + // stack: start_pos slot_count + // Initialize loop counter i = 0 + _context << u256(0); + // stack: start_pos slot_count i evmasm::AssemblyItem loopStart = _context.appendJumpToNew(); _context << loopStart; - // check for loop condition - _context << - Instruction::DUP1 << - Instruction::DUP3 << - Instruction::GT << - Instruction::ISZERO; + // check for loop condition: !(slot_count > i) = (i >= slot_count) + _context << Instruction::DUP1 << Instruction::DUP3 << Instruction::GT << Instruction::ISZERO; evmasm::AssemblyItem zeroLoopEnd = _context.newTag(); _context.appendConditionalJumpTo(zeroLoopEnd); - // delete + // stack: start_pos slot_count i + // compute storage position: start_pos + i + _context << Instruction::DUP3 << Instruction::DUP2 << Instruction::ADD; + // stack: start_pos slot_count i (start_pos+i) + // delete storage slot _context << u256(0); - StorageItem(_context, *_type).setToZero(SourceLocation(), false); - _context << Instruction::POP; - // increment + StorageItem(_context, *_type).setToZero(SourceLocation(), /* _removeReference = */ true); + // stack: start_pos slot_count i + // increment counter: i += storageSize _context << _type->storageSize() << Instruction::ADD; _context.appendJumpTo(loopStart); // cleanup _context << zeroLoopEnd; - _context << Instruction::POP; - + // stack: start_pos slot_count i + _context << Instruction::POP << Instruction::POP; + // stack: start_pos solAssert(_context.stackHeight() == stackHeightStart - 1, ""); } ); diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index 326084cb63a8..a62df8a7b2ab 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -61,10 +61,6 @@ class ArrayUtils /// Stack pre: reference (excludes byte offset) /// Stack post: void clearDynamicArray(ArrayType const& _type) const; - /// Changes the size of a dynamic array and clears the tail if it is shortened. - /// Stack pre: reference (excludes byte offset) new_length - /// Stack post: - void resizeDynamicArray(ArrayType const& _type) const; /// Increments the size of a dynamic array by one. /// Does not touch the new data element. In case of a byte array, this might move the /// data. @@ -76,9 +72,9 @@ class ArrayUtils /// Stack pre: reference /// Stack post: void popStorageArrayElement(ArrayType const& _type) const; - /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). - /// Stack pre: end_ref start_ref - /// Stack post: end_ref + /// Appends a loop that clears a sequence of storage slots of the given type. + /// Stack pre: start_ref slot_count + /// Stack post: start_ref void clearStorageLoop(Type const* _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. From 321f5151fcf1c0ca203532ed8162950ca5fdbbdf Mon Sep 17 00:00:00 2001 From: r0qs <457348+r0qs@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:49:14 -0300 Subject: [PATCH 02/41] via-ir: Fix handling of storage array boundaries (deletion and cleanup) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Kirchner Co-authored-by: Kamil Śliwak (cherry picked from commit 44cdd8d6a948bfef0696ab664ada75c0158fd427) --- libsolidity/codegen/YulUtilFunctions.cpp | 43 +++++++++++++++--------- libsolidity/codegen/YulUtilFunctions.h | 11 +++--- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index abb2fdd100aa..e1a278494b8a 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -1419,14 +1419,13 @@ std::string YulUtilFunctions::cleanUpStorageArrayEndFunction(ArrayType const& _t let newSlotCount := (startIndex) let arrayDataStart := (array) let deleteStart := add(arrayDataStart, newSlotCount) - let deleteEnd := add(arrayDataStart, oldSlotCount) // if we are dealing with packed array and offset is greater than zero // we have to partially clear last slot that is still used, so decreasing start by one let offset := mul(mod(startIndex, ), ) if gt(offset, 0) { (sub(deleteStart, 1), offset) } - (deleteStart, deleteEnd) + (deleteStart, sub(oldSlotCount, newSlotCount)) } )") ("convertToSize", arrayConvertLengthToSize(_type)) @@ -1474,11 +1473,17 @@ std::string YulUtilFunctions::cleanUpDynamicByteArrayEndSlotsFunction(ArrayType _args = {"array", "len", "startIndex"}; return Whiskers(R"( if gt(len, 31) { - let dataArea := (array) - let deleteStart := add(dataArea, (startIndex)) - // If we are clearing array to be short byte array, we want to clear only data starting from array data area. - if lt(startIndex, 32) { deleteStart := dataArea } - (deleteStart, add(dataArea, (len))) + if gt(len, startIndex) { + let dataArea := (array) + let oldSlotCount := (len) + let newSlotCount := (startIndex) + // If we are clearing array to be short byte array, we want to clear only data starting from array data area. + if lt(startIndex, 32) { + newSlotCount := 0 + } + let deleteStart := add(dataArea, newSlotCount) + (deleteStart, sub(oldSlotCount, newSlotCount)) + } } )") ("dataLocation", arrayDataAreaFunction(_type)) @@ -1496,14 +1501,21 @@ std::string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _ty function (array, data, oldLen, newLen) { switch lt(newLen, 32) case 0 { + // NOTE: This branch (newLen >= 32) is currently unreachable from Solidity code. + // All delete operations use newLen=0, and assignment uses copyByteArrayToStorageFunction. + // However, we maintain correct logic here for future-proofing and consistency. + let newSlots := (newLen) + let oldSlots := (oldLen) let arrayDataStart := (array) - let deleteStart := add(arrayDataStart, (newLen)) + let deleteStart := add(arrayDataStart, newSlots) // we have to partially clear last slot that is still used let offset := and(newLen, 0x1f) if offset { (sub(deleteStart, 1), offset) } - (deleteStart, add(arrayDataStart, (oldLen))) + if gt(oldSlots, newSlots) { + (deleteStart, sub(oldSlots, newSlots)) + } sstore(array, or(mul(2, newLen), 1)) } @@ -1511,8 +1523,9 @@ std::string YulUtilFunctions::decreaseByteArraySizeFunction(ArrayType const& _ty switch gt(oldLen, 31) case 1 { let arrayDataStart := (array) - // clear whole old array, as we are transforming to short bytes array - (add(arrayDataStart, 1), add(arrayDataStart, (oldLen))) + // Clear whole old array, as we are transforming to short bytes array. + // () will clear the first slot after copying its content. + (add(arrayDataStart, 1), sub((oldLen), 1)) (array, newLen) } default { @@ -1826,10 +1839,10 @@ std::string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) return m_functionCollector.createFunction(functionName, [&]() { return Whiskers(R"( - function (start, end) { - for {} lt(start, end) { start := add(start, ) } + function (startSlot, slotCount) { + for { let i := 0 } lt(i, slotCount) { i := add(i, ) } { - (start, 0) + (add(startSlot, i), 0) } } )") @@ -1861,7 +1874,7 @@ std::string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type) (slot, 0) - (slot, add(slot, ())) + (slot, ()) } )") diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index c28ba855c816..f4172017b44e 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -266,8 +266,11 @@ class YulUtilFunctions std::string storageArrayPushZeroFunction(ArrayType const& _type); /// @returns the name of a function that will clear the storage area given - /// by the start and end (exclusive) parameters (slots). - /// signature: (start, end) + /// by the start position and number of slots to clear. The start position is in terms of storage slots and we + /// assume that the beginning of the clear range starts at the beginning of the start slot. + /// `slot_count` is assumed to be a multiple of `_type.storageSize()`. The function clears storage in increments + /// of `_type.storageSize()` and does not perform any runtime checks. + /// signature: (start_slot, slot_count) std::string clearStorageRangeFunction(Type const& _type); /// @returns the name of a function that will clear the given storage array @@ -297,7 +300,7 @@ class YulUtilFunctions /// The function reverts for too large lengths. std::string arrayAllocationSizeFunction(ArrayType const& _type); - /// @returns the name of a function that converts a storage slot number + /// @returns the name of a function that converts a storage slot number, /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer /// for the data position of an array which is stored in that slot / memory area / calldata area. std::string arrayDataAreaFunction(ArrayType const& _type); @@ -618,7 +621,7 @@ class YulUtilFunctions std::string cleanUpDynamicByteArrayEndSlotsFunction(ArrayType const& _type); /// @returns the name of a function that increases size of byte array - /// when we resize byte array frextractUsedSetLenom < 32 elements to >= 32 elements or we push to byte array of size 31 copying of data will occur + /// when we resize byte array from < 32 elements to >= 32 elements or we push to byte array of size 31 copying of data will occur /// signature: (array, data, oldLen, newLen) std::string increaseByteArraySizeFunction(ArrayType const& _type); From 28c6c0285efab1912431815baf99195e3e8cf6ca Mon Sep 17 00:00:00 2001 From: r0qs <457348+r0qs@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:48:21 -0300 Subject: [PATCH 03/41] Add test for arrays at storage boundaries and extend coverage of legacy and via-ir on deletion, partial assigments and copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Kirchner Co-authored-by: Kamil Śliwak (cherry picked from commit 0495910d8a559a6c08b2812e8b9941ec79400ea4) --- test/libsolidity/SolidityOptimizer.cpp | 4 +- .../abi_decode_simple_storage.sol | 4 +- .../abiEncoderV1/abi_decode_v2_storage.sol | 4 +- .../struct/struct_storage_ptr.sol | 6 +- .../calldata_overlapped_dynamic_arrays.sol | 6 +- .../abiEncoderV2/storage_array_encoding.sol | 10 +- .../arrays_complex_from_and_to_storage.sol | 6 +- .../array/bytes_length_member.sol | 4 +- .../array/constant_var_as_array_length.sol | 4 +- .../copying/array_copy_calldata_storage.sol | 6 +- .../copying/array_copy_cleanup_uint128.sol | 6 +- .../copying/array_copy_cleanup_uint40.sol | 6 +- .../copying/array_copy_clear_storage.sol | 6 +- .../array_copy_clear_storage_packed.sol | 12 +- .../copying/array_copy_different_packing.sol | 4 +- .../copying/array_copy_including_array.sol | 12 +- .../array/copying/array_copy_nested_array.sol | 6 +- ...ay_copy_storage_storage_different_base.sol | 4 +- ..._storage_storage_different_base_nested.sol | 6 +- .../array_copy_storage_storage_dyn_dyn.sol | 6 +- ...y_copy_storage_storage_dynamic_dynamic.sol | 4 +- ...ay_copy_storage_storage_static_dynamic.sol | 4 +- ...ray_copy_storage_storage_static_simple.sol | 4 +- ...ray_copy_storage_storage_static_static.sol | 6 +- .../array_copy_storage_storage_struct.sol | 6 +- .../copying/array_copy_target_leftover.sol | 6 +- .../copying/array_copy_target_leftover2.sol | 6 +- .../copying/array_copy_target_simple.sol | 6 +- .../copying/array_copy_target_simple_2.sol | 6 +- .../copying/array_elements_to_mapping.sol | 6 +- .../array_nested_calldata_to_storage.sol | 4 +- .../array_nested_memory_to_storage.sol | 14 +- ...on_external_storage_to_storage_dynamic.sol | 6 +- ...o_storage_dynamic_different_mutability.sol | 6 +- .../array_of_struct_calldata_to_storage.sol | 2 +- ..._containing_arrays_calldata_to_storage.sol | 2 +- .../array/copying/array_to_mapping.sol | 6 +- .../copying/arrays_from_and_to_storage.sol | 6 +- .../array/copying/bytes_inside_mappings.sol | 7 +- .../copying/bytes_storage_to_storage.sol | 23 +-- .../calldata_array_dynamic_to_storage.sol | 6 +- ...nup_during_multi_element_per_slot_copy.sol | 12 +- .../copy_byte_array_in_struct_to_storage.sol | 9 +- .../copying/copy_byte_array_to_storage.sol | 6 +- .../copy_function_internal_storage_array.sol | 6 +- ...opy_internal_function_array_to_storage.sol | 4 +- .../array/copying/copy_removes_bytes_data.sol | 3 +- .../copying/copying_bytes_multiassign.sol | 6 +- ...d_array_of_structs_calldata_to_storage.sol | 6 +- ...ted_array_of_structs_memory_to_storage.sol | 6 +- .../function_type_array_to_storage.sol | 12 +- .../memory_dyn_2d_bytes_to_storage.sol | 6 +- ...sted_array_element_calldata_to_storage.sol | 4 +- ...nested_array_element_memory_to_storage.sol | 4 +- ...ested_array_element_storage_to_storage.sol | 12 +- ...d_array_of_structs_calldata_to_storage.sol | 6 +- ...ted_array_of_structs_memory_to_storage.sol | 6 +- ...amic_array_element_calldata_to_storage.sol | 4 +- .../copying/storage_memory_nested_bytes.sol | 2 +- .../array/delete/bytes_delete_element.sol | 4 +- .../array/delete/delete_bytes_array.sol | 1 + .../delete/delete_storage_array_packed.sol | 6 +- .../array/fixed_array_cleanup.sol | 6 +- ...nvalid_encoding_for_storage_byte_array.sol | 6 +- .../long_byte_array_cleanup_after_delete.sol | 77 ++++++++ ...rray_cleanup_after_overwrite_with_long.sol | 105 +++++++++++ .../array/nested_calldata_storage2.sol | 2 +- .../array/pop/array_pop_array_transition.sol | 6 +- .../push/array_push_nested_from_calldata.sol | 4 +- .../array/push/array_push_struct.sol | 4 +- .../array/push/nested_bytes_push.sol | 2 +- .../copy_from_calldata_removes_bytes_data.sol | 3 +- .../cleanup/byte_array_to_storage_cleanup.sol | 13 +- .../constructor/arrays_in_constructors.sol | 4 +- .../bytes_in_constructors_packer.sol | 6 +- .../bytes_in_constructors_unpacker.sol | 6 +- .../constructor_static_array_argument.sol | 4 +- .../event_dynamic_nested_array_storage_v2.sol | 2 +- .../fallback/call_forward_bytes.sol | 1 + .../storage/empty_nonempty_empty.sol | 5 +- .../storage/static_array_copy_cleanup.sol | 94 ++++++++++ ...ray_and_partial_assignment_with_layout.sol | 54 ++++++ .../storage_boundary_array_assignment.sol | 28 +++ .../storage/storage_boundary_array_copy.sol | 61 +++++++ .../storage/storage_boundary_array_delete.sol | 34 ++++ ...dary_array_delete_overlapping_variable.sol | 40 ++++ ...array_packing_not_overlapping_variable.sol | 63 +++++++ ...rage_boundary_array_partial_assignment.sol | 41 +++++ .../storage_boundary_delete_overflow_bug.sol | 80 ++++++++ .../storage/storage_boundary_packed_array.sol | 35 ++++ ...rage_boundary_struct_array_mixed_types.sol | 172 ++++++++++++++++++ ...torage_boundary_struct_array_multislot.sol | 128 +++++++++++++ .../storage_boundary_struct_array_packed.sol | 128 +++++++++++++ .../storage/storage_packed_array_copy.sol | 35 ++++ .../state_variable_dynamic_array.sol | 6 +- .../state_variable_mapping.sol | 2 +- .../structs/copy_from_mapping.sol | 4 +- .../copy_struct_array_from_storage.sol | 4 +- .../copy_substructures_from_mapping.sol | 4 +- .../structs/copy_substructures_to_mapping.sol | 12 +- .../semanticTests/structs/copy_to_mapping.sol | 12 +- ...truct_containing_bytes_copy_and_delete.sol | 4 +- .../struct_delete_storage_nested_small.sol | 2 +- .../struct_delete_storage_with_array.sol | 12 +- ...truct_delete_storage_with_arrays_small.sol | 2 +- .../mapping/copy_from_mapping_to_mapping.sol | 4 +- .../userDefinedValueType/calldata.sol | 12 +- .../calldata_to_storage.sol | 18 +- .../memory_to_storage.sol | 12 +- .../semanticTests/various/address_code.sol | 6 +- .../semanticTests/various/create_calldata.sol | 6 +- .../various/destructuring_assignment.sol | 6 +- .../skip_dynamic_types_for_structs.sol | 4 +- ...alldata_struct_array_to_storage_legacy.sol | 16 ++ ..._memory_struct_array_to_storage_legacy.sol | 17 ++ 115 files changed, 1511 insertions(+), 286 deletions(-) create mode 100644 test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_delete.sol create mode 100644 test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_overwrite_with_long.sol create mode 100644 test/libsolidity/semanticTests/storage/static_array_copy_cleanup.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_and_partial_assignment_with_layout.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_assignment.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_copy.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_delete.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_delete_overlapping_variable.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_packing_not_overlapping_variable.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_array_partial_assignment.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_delete_overflow_bug.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_packed_array.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_struct_array_mixed_types.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_struct_array_multislot.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_boundary_struct_array_packed.sol create mode 100644 test/libsolidity/semanticTests/storage/storage_packed_array_copy.sol create mode 100644 test/libsolidity/syntaxTests/array/copy_calldata_struct_array_to_storage_legacy.sol create mode 100644 test/libsolidity/syntaxTests/array/copy_memory_struct_array_to_storage_legacy.sol diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index d52ad6b9c48a..0a0ec6a879a1 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -634,8 +634,8 @@ BOOST_AUTO_TEST_CASE(optimise_multi_stores) )"; compileBothVersions(sourceCode); compareVersions("f()"); - BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 8); - BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 7); + BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 9); + BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 6); } BOOST_AUTO_TEST_CASE(optimise_constant_to_codecopy) diff --git a/test/libsolidity/semanticTests/abiEncodeDecode/abi_decode_simple_storage.sol b/test/libsolidity/semanticTests/abiEncodeDecode/abi_decode_simple_storage.sol index 54ce693d5ca4..6c4fd52b36ed 100644 --- a/test/libsolidity/semanticTests/abiEncodeDecode/abi_decode_simple_storage.sol +++ b/test/libsolidity/semanticTests/abiEncodeDecode/abi_decode_simple_storage.sol @@ -8,6 +8,6 @@ contract C { } // ---- // f(bytes): 0x20, 0x80, 0x21, 0x40, 0x7, "abcdefg" -> 0x21, 0x40, 0x7, "abcdefg" -// gas irOptimized: 135499 +// gas irOptimized: 135441 // gas legacy: 137095 -// gas legacyOptimized: 135823 +// gas legacyOptimized: 135828 diff --git a/test/libsolidity/semanticTests/abiEncoderV1/abi_decode_v2_storage.sol b/test/libsolidity/semanticTests/abiEncoderV1/abi_decode_v2_storage.sol index 5c3bcba971c2..e5aa910ce1be 100644 --- a/test/libsolidity/semanticTests/abiEncoderV1/abi_decode_v2_storage.sol +++ b/test/libsolidity/semanticTests/abiEncoderV1/abi_decode_v2_storage.sol @@ -21,6 +21,6 @@ contract C { } // ---- // f() -> 0x20, 0x8, 0x40, 0x3, 0x9, 0xa, 0xb -// gas irOptimized: 203167 +// gas irOptimized: 203109 // gas legacy: 206263 -// gas legacyOptimized: 203172 +// gas legacyOptimized: 203177 diff --git a/test/libsolidity/semanticTests/abiEncoderV1/struct/struct_storage_ptr.sol b/test/libsolidity/semanticTests/abiEncoderV1/struct/struct_storage_ptr.sol index ed983c77b506..ff98c66ca9c6 100644 --- a/test/libsolidity/semanticTests/abiEncoderV1/struct/struct_storage_ptr.sol +++ b/test/libsolidity/semanticTests/abiEncoderV1/struct/struct_storage_ptr.sol @@ -24,6 +24,6 @@ contract C { // ---- // library: L // f() -> 8, 7, 1, 2, 7, 12 -// gas irOptimized: 166761 -// gas legacy: 169273 -// gas legacyOptimized: 167243 +// gas irOptimized: 166766 +// gas legacy: 170486 +// gas legacyOptimized: 167252 diff --git a/test/libsolidity/semanticTests/abiEncoderV2/calldata_overlapped_dynamic_arrays.sol b/test/libsolidity/semanticTests/abiEncoderV2/calldata_overlapped_dynamic_arrays.sol index aa1e494c2133..302aeb5de4d5 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/calldata_overlapped_dynamic_arrays.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/calldata_overlapped_dynamic_arrays.sol @@ -33,8 +33,8 @@ contract C { // f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1, 5, 6 -> 0x20, 0x40, 5, 2 // f_which(uint256[],uint256[2],uint256): 0x40, 1, 2, 1 -> FAILURE // f_storage(uint256[],uint256[2]): 0x20, 1, 2 -> 0x20, 0x60, 0x20, 1, 2 -// gas irOptimized: 111409 -// gas legacy: 112707 -// gas legacyOptimized: 111845 +// gas irOptimized: 111356 +// gas legacy: 113826 +// gas legacyOptimized: 111724 // f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5, 6 -> 0x20, 0x80, 0x20, 2, 5, 6 // f_storage(uint256[],uint256[2]): 0x40, 1, 2, 5 -> FAILURE diff --git a/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol index 9599af6c8815..31f759d3bfd4 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol @@ -18,10 +18,10 @@ contract C { // EVMVersion: >homestead // ---- // h(uint256[2][]): 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324 -// gas irOptimized: 180080 -// gas legacy: 184233 -// gas legacyOptimized: 180856 +// gas irOptimized: 180022 +// gas legacy: 186541 +// gas legacyOptimized: 180429 // i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224 // gas irOptimized: 112031 -// gas legacy: 115091 -// gas legacyOptimized: 112657 +// gas legacy: 116683 +// gas legacyOptimized: 112303 diff --git a/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol b/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol index a9d08ec0c0cb..ac3cd8495f73 100644 --- a/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol +++ b/test/libsolidity/semanticTests/array/arrays_complex_from_and_to_storage.sol @@ -12,9 +12,9 @@ contract Test { } // ---- // set(uint24[3][]): 0x20, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12 -> 0x06 -// gas irOptimized: 185216 -// gas legacy: 211054 -// gas legacyOptimized: 206077 +// gas irOptimized: 185221 +// gas legacy: 199970 +// gas legacyOptimized: 186342 // data(uint256,uint256): 0x02, 0x02 -> 0x09 // data(uint256,uint256): 0x05, 0x01 -> 0x11 // data(uint256,uint256): 0x06, 0x00 -> FAILURE diff --git a/test/libsolidity/semanticTests/array/bytes_length_member.sol b/test/libsolidity/semanticTests/array/bytes_length_member.sol index 5159538bc1de..85b9282c6603 100644 --- a/test/libsolidity/semanticTests/array/bytes_length_member.sol +++ b/test/libsolidity/semanticTests/array/bytes_length_member.sol @@ -13,7 +13,7 @@ contract c { // ---- // getLength() -> 0 // set(): 1, 2 -> true -// gas irOptimized: 110422 +// gas irOptimized: 110393 // gas legacy: 110951 -// gas legacyOptimized: 110576 +// gas legacyOptimized: 110577 // getLength() -> 68 diff --git a/test/libsolidity/semanticTests/array/constant_var_as_array_length.sol b/test/libsolidity/semanticTests/array/constant_var_as_array_length.sol index 342f3218fcb4..833c9dec3e42 100644 --- a/test/libsolidity/semanticTests/array/constant_var_as_array_length.sol +++ b/test/libsolidity/semanticTests/array/constant_var_as_array_length.sol @@ -10,9 +10,9 @@ contract C { // constructor(): 1, 2, 3 -> // gas irOptimized: 124991 // gas irOptimized code: 14800 -// gas legacy: 134317 +// gas legacy: 142553 // gas legacy code: 46200 -// gas legacyOptimized: 127166 +// gas legacyOptimized: 126306 // gas legacyOptimized code: 23400 // a(uint256): 0 -> 1 // a(uint256): 1 -> 2 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol b/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol index ecfe565f12cb..8131e7434d25 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_calldata_storage.sol @@ -20,7 +20,7 @@ contract c { } // ---- // store(uint256[9],uint8[3][]): 21, 22, 23, 24, 25, 26, 27, 28, 29, 0x140, 4, 1, 2, 3, 11, 12, 13, 21, 22, 23, 31, 32, 33 -> 32 -// gas irOptimized: 647725 -// gas legacy: 694354 -// gas legacyOptimized: 693849 +// gas irOptimized: 647730 +// gas legacy: 659420 +// gas legacyOptimized: 648899 // retrieve() -> 9, 28, 9, 28, 4, 3, 32 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint128.sol b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint128.sol index 761feebec9d5..ba05d8fb670d 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint128.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint128.sol @@ -21,6 +21,6 @@ contract C { } // ---- // f() -> true -// gas irOptimized: 92740 -// gas legacy: 93035 -// gas legacyOptimized: 92257 +// gas irOptimized: 73981 +// gas legacy: 75550 +// gas legacyOptimized: 73812 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol index d65f4f37c104..e4e7bf62f3a0 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_cleanup_uint40.sol @@ -46,6 +46,6 @@ contract C { } // ---- // f() -> true -// gas irOptimized: 122541 -// gas legacy: 124643 -// gas legacyOptimized: 122801 +// gas irOptimized: 122596 +// gas legacy: 125651 +// gas legacyOptimized: 121668 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage.sol b/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage.sol index e9f1448a27b1..f206b432c9a5 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage.sol @@ -13,6 +13,6 @@ contract C { } // ---- // f() -> 0 -// gas irOptimized: 108229 -// gas legacy: 108216 -// gas legacyOptimized: 107625 +// gas irOptimized: 108096 +// gas legacy: 110351 +// gas legacyOptimized: 107708 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage_packed.sol b/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage_packed.sol index 13cb9c28fd26..12d9bf1fd3c0 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage_packed.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_clear_storage_packed.sol @@ -40,11 +40,11 @@ contract C { } // ---- // f() -> 0 -// gas irOptimized: 92800 -// gas legacy: 93006 -// gas legacyOptimized: 92261 +// gas irOptimized: 74044 +// gas legacy: 75528 +// gas legacyOptimized: 73861 // g() -> 0 // h() -> 0 -// gas irOptimized: 92862 -// gas legacy: 93028 -// gas legacyOptimized: 92303 +// gas irOptimized: 74114 +// gas legacy: 75553 +// gas legacyOptimized: 73904 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol b/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol index ee564d01a2d9..1bbb541911a0 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_different_packing.sol @@ -19,5 +19,5 @@ contract c { // ---- // test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x05000000000000000000000000000000000000000000000000 // gas irOptimized: 208415 -// gas legacy: 220711 -// gas legacyOptimized: 220097 +// gas legacy: 220059 +// gas legacyOptimized: 211458 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_including_array.sol b/test/libsolidity/semanticTests/array/copying/array_copy_including_array.sol index bb621fcc84b5..ba50c7300f65 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_including_array.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_including_array.sol @@ -35,12 +35,12 @@ contract c { } // ---- // test() -> 0x02000202 -// gas irOptimized: 4549676 -// gas legacy: 4475394 -// gas legacyOptimized: 4447665 +// gas irOptimized: 4560468 +// gas legacy: 4536566 +// gas legacyOptimized: 4456759 // storageEmpty -> 1 // clear() -> 0, 0 -// gas irOptimized: 4478184 -// gas legacy: 4407185 -// gas legacyOptimized: 4381337 +// gas irOptimized: 4488719 +// gas legacy: 4407780 +// gas legacyOptimized: 4385089 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_nested_array.sol b/test/libsolidity/semanticTests/array/copying/array_copy_nested_array.sol index 2d325a804bfa..34d6c75ef8a6 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_nested_array.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_nested_array.sol @@ -12,6 +12,6 @@ contract c { } // ---- // test(uint256[2][]): 32, 3, 7, 8, 9, 10, 11, 12 -> 10 -// gas irOptimized: 689552 -// gas legacy: 686176 -// gas legacyOptimized: 685611 +// gas irOptimized: 689539 +// gas legacy: 713478 +// gas legacyOptimized: 690424 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base.sol index 54639df55d66..af31d4b8e495 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base.sol @@ -18,5 +18,5 @@ contract c { // ---- // test() -> 5, 4 // gas irOptimized: 205667 -// gas legacy: 213863 -// gas legacyOptimized: 212901 +// gas legacy: 207971 +// gas legacyOptimized: 204828 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol index ad745ff4203f..6b1dd7ff02c0 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_different_base_nested.sol @@ -21,6 +21,6 @@ contract c { } // ---- // test() -> 3, 4 -// gas irOptimized: 169669 -// gas legacy: 175415 -// gas legacyOptimized: 172533 +// gas irOptimized: 169553 +// gas legacy: 182361 +// gas legacyOptimized: 169554 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol index a38685404dc7..98e2f5adb45e 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dyn_dyn.sol @@ -15,9 +15,9 @@ contract c { // ---- // setData1(uint256,uint256,uint256): 10, 5, 4 -> // copyStorageStorage() -> -// gas irOptimized: 111350 -// gas legacy: 109272 -// gas legacyOptimized: 109262 +// gas irOptimized: 111321 +// gas legacy: 112434 +// gas legacyOptimized: 111494 // getData2(uint256): 5 -> 10, 4 // setData1(uint256,uint256,uint256): 0, 0, 0 -> // copyStorageStorage() -> diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dynamic_dynamic.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dynamic_dynamic.sol index 83df3aef8f43..5a66704b5506 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dynamic_dynamic.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_dynamic_dynamic.sol @@ -18,5 +18,5 @@ contract c { // ---- // test() -> 5, 4 // gas irOptimized: 253591 -// gas legacy: 250892 -// gas legacyOptimized: 250045 +// gas legacy: 253664 +// gas legacyOptimized: 252200 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol index 94e57c96da7b..29d8bc0ebed7 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_dynamic.sol @@ -12,5 +12,5 @@ contract c { // ---- // test() -> 9, 4 // gas irOptimized: 123180 -// gas legacy: 123566 -// gas legacyOptimized: 123202 +// gas legacy: 124642 +// gas legacyOptimized: 123345 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_simple.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_simple.sol index 2fccff774e94..29e62dd3ce8f 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_simple.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_simple.sol @@ -11,5 +11,5 @@ contract C { } // ---- // test() -> left(0x01), left(0x02) -// gas legacy: 90001 -// gas legacyOptimized: 89085 +// gas legacy: 69235 +// gas legacyOptimized: 66974 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_static.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_static.sol index d1f2cbdedcde..65ce656548eb 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_static.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_static_static.sol @@ -14,6 +14,6 @@ contract c { } // ---- // test() -> 8, 0 -// gas irOptimized: 196251 -// gas legacy: 194842 -// gas legacyOptimized: 194281 +// gas irOptimized: 196370 +// gas legacy: 210073 +// gas legacyOptimized: 196759 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_struct.sol b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_struct.sol index e638f2dad10c..f1cd76a3adc5 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_struct.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_storage_storage_struct.sol @@ -17,7 +17,7 @@ contract c { } // ---- // test() -> 4, 5 -// gas irOptimized: 190628 -// gas legacy: 190828 -// gas legacyOptimized: 189657 +// gas irOptimized: 190676 +// gas legacy: 210706 +// gas legacyOptimized: 190472 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol index 22105f6429b9..2deb372fa381 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover.sol @@ -17,6 +17,6 @@ contract c { } // ---- // test() -> 0xffffffff, 0x0000000000000000000000000a00090008000700060005000400030002000100, 0x0000000000000000000000000000000000000000000000000000000000000000 -// gas irOptimized: 100496 -// gas legacy: 158109 -// gas legacyOptimized: 141019 +// gas irOptimized: 100506 +// gas legacy: 146700 +// gas legacyOptimized: 122680 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover2.sol b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover2.sol index 0a2bcffce201..706130082fff 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover2.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_target_leftover2.sol @@ -19,6 +19,6 @@ contract c { } // ---- // test() -> 0x04000000000000000000000000000000000000000000000000, 0x0, 0x0 -// gas irOptimized: 93858 -// gas legacy: 97451 -// gas legacyOptimized: 94200 +// gas irOptimized: 91320 +// gas legacy: 97777 +// gas legacyOptimized: 91993 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_target_simple.sol b/test/libsolidity/semanticTests/array/copying/array_copy_target_simple.sol index 27fb9f0c5736..054d61c6772b 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_target_simple.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_target_simple.sol @@ -18,6 +18,6 @@ contract c { } // ---- // test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x0 -// gas irOptimized: 273543 -// gas legacy: 282601 -// gas legacyOptimized: 281510 +// gas irOptimized: 273548 +// gas legacy: 280148 +// gas legacyOptimized: 274502 diff --git a/test/libsolidity/semanticTests/array/copying/array_copy_target_simple_2.sol b/test/libsolidity/semanticTests/array/copying/array_copy_target_simple_2.sol index 980e414d1451..7ceadf481c66 100644 --- a/test/libsolidity/semanticTests/array/copying/array_copy_target_simple_2.sol +++ b/test/libsolidity/semanticTests/array/copying/array_copy_target_simple_2.sol @@ -18,6 +18,6 @@ contract c { } // ---- // test() -> 0x01000000000000000000000000000000000000000000000000, 0x02000000000000000000000000000000000000000000000000, 0x03000000000000000000000000000000000000000000000000, 0x04000000000000000000000000000000000000000000000000, 0x00 -// gas irOptimized: 232995 -// gas legacy: 235694 -// gas legacyOptimized: 235193 +// gas irOptimized: 233012 +// gas legacy: 239400 +// gas legacyOptimized: 233948 diff --git a/test/libsolidity/semanticTests/array/copying/array_elements_to_mapping.sol b/test/libsolidity/semanticTests/array/copying/array_elements_to_mapping.sol index becc3d4cdefd..a1849bd65fce 100644 --- a/test/libsolidity/semanticTests/array/copying/array_elements_to_mapping.sol +++ b/test/libsolidity/semanticTests/array/copying/array_elements_to_mapping.sol @@ -52,9 +52,9 @@ contract C { } // ---- // from_storage() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 -// gas irOptimized: 149868 -// gas legacy: 150737 -// gas legacyOptimized: 148690 +// gas irOptimized: 149856 +// gas legacy: 156721 +// gas legacyOptimized: 149000 // from_storage_ptr() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 // from_memory() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 // from_calldata(uint8[][]): 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 diff --git a/test/libsolidity/semanticTests/array/copying/array_nested_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/array_nested_calldata_to_storage.sol index 5d4e5b45e4a9..ffa0c68adcbe 100644 --- a/test/libsolidity/semanticTests/array/copying/array_nested_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_nested_calldata_to_storage.sol @@ -38,10 +38,10 @@ contract c { // compileViaYul: true // ---- // test1(uint256[][]): 0x20, 2, 0x40, 0x40, 2, 23, 42 -> 2, 65 -// gas irOptimized: 181029 +// gas irOptimized: 180918 // test2(uint256[][2]): 0x20, 0x40, 0x40, 2, 23, 42 -> 2, 65 // gas irOptimized: 157604 // test3(uint256[2][]): 0x20, 2, 23, 42, 23, 42 -> 2, 65 -// gas irOptimized: 134801 +// gas irOptimized: 134685 // test4(uint256[2][2]): 23, 42, 23, 42 -> 65 // gas irOptimized: 111177 diff --git a/test/libsolidity/semanticTests/array/copying/array_nested_memory_to_storage.sol b/test/libsolidity/semanticTests/array/copying/array_nested_memory_to_storage.sol index b892ec991ce3..933a1ab1b1c5 100644 --- a/test/libsolidity/semanticTests/array/copying/array_nested_memory_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_nested_memory_to_storage.sol @@ -38,12 +38,14 @@ contract Test { } // ---- // test() -> 24 -// gas irOptimized: 226734 -// gas legacy: 227083 -// gas legacyOptimized: 226529 +// gas irOptimized: 226647 +// gas legacy: 229060 +// gas legacyOptimized: 226495 // test1() -> 3 // test2() -> 6 +// gas irOptimized: 95905 +// gas legacy: 100519 // test3() -> 24 -// gas irOptimized: 141319 -// gas legacy: 142238 -// gas legacyOptimized: 141365 +// gas irOptimized: 141297 +// gas legacy: 146668 +// gas legacyOptimized: 141331 diff --git a/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic.sol b/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic.sol index ab3c172d3399..e3b9cdaadcb9 100644 --- a/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic.sol +++ b/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic.sol @@ -45,7 +45,7 @@ contract C { } // ---- // copyExternalStorageArrayOfFunctionType() -> true -// gas irOptimized: 104566 -// gas legacy: 108554 -// gas legacyOptimized: 102405 +// gas irOptimized: 104629 +// gas legacy: 111170 +// gas legacyOptimized: 104620 // copyInternalArrayOfFunctionType() -> true diff --git a/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic_different_mutability.sol b/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic_different_mutability.sol index 2bb3bcf333e3..92b61b9291cd 100644 --- a/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic_different_mutability.sol +++ b/test/libsolidity/semanticTests/array/copying/array_of_function_external_storage_to_storage_dynamic_different_mutability.sol @@ -49,7 +49,7 @@ contract C { // ---- // copyExternalStorageArraysOfFunctionType() -> true // gas irOptimized: 104238 -// gas legacy: 108295 -// gas legacyOptimized: 102162 +// gas legacy: 110911 +// gas legacyOptimized: 104377 // copyInternalArrayOfFunctionType() -> true -// gas legacy: 104178 +// gas legacy: 38695 diff --git a/test/libsolidity/semanticTests/array/copying/array_of_struct_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/array_of_struct_calldata_to_storage.sol index 3147401cfa1d..e4b3d14fd76b 100644 --- a/test/libsolidity/semanticTests/array/copying/array_of_struct_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_of_struct_calldata_to_storage.sol @@ -17,4 +17,4 @@ contract C { // compileViaYul: true // ---- // f((uint128,uint64,uint128)[]): 0x20, 3, 0, 0, 12, 0, 11, 0, 10, 0, 0 -> 10, 11, 12 -// gas irOptimized: 120747 +// gas irOptimized: 120751 diff --git a/test/libsolidity/semanticTests/array/copying/array_of_structs_containing_arrays_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/array_of_structs_containing_arrays_calldata_to_storage.sol index 09037719a6f0..acc968389229 100644 --- a/test/libsolidity/semanticTests/array/copying/array_of_structs_containing_arrays_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/array_of_structs_containing_arrays_calldata_to_storage.sol @@ -23,4 +23,4 @@ contract C { // compileViaYul: true // ---- // f((uint256[])[]): 0x20, 3, 0x60, 0x60, 0x60, 0x20, 3, 1, 2, 3 -> 3, 1 -// gas irOptimized: 327456 +// gas irOptimized: 327461 diff --git a/test/libsolidity/semanticTests/array/copying/array_to_mapping.sol b/test/libsolidity/semanticTests/array/copying/array_to_mapping.sol index c5519e83aa43..667685c9c83f 100644 --- a/test/libsolidity/semanticTests/array/copying/array_to_mapping.sol +++ b/test/libsolidity/semanticTests/array/copying/array_to_mapping.sol @@ -37,8 +37,8 @@ contract C { } // ---- // from_storage() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 -// gas irOptimized: 147755 -// gas legacy: 148892 -// gas legacyOptimized: 146917 +// gas irOptimized: 147749 +// gas legacy: 154218 +// gas legacyOptimized: 147020 // from_storage_ptr() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 // from_memory() -> 0x20, 2, 0x40, 0xa0, 2, 10, 11, 3, 12, 13, 14 diff --git a/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol b/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol index af2983b066bd..fea2c895d81c 100644 --- a/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/arrays_from_and_to_storage.sol @@ -10,9 +10,9 @@ contract Test { } // ---- // set(uint24[]): 0x20, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> 18 -// gas irOptimized: 99616 -// gas legacy: 103509 -// gas legacyOptimized: 101266 +// gas irOptimized: 95505 +// gas legacy: 104176 +// gas legacyOptimized: 96785 // data(uint256): 7 -> 8 // data(uint256): 15 -> 16 // data(uint256): 18 -> FAILURE diff --git a/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol b/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol index 90012eed4c77..4ab381f4e614 100644 --- a/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol +++ b/test/libsolidity/semanticTests/array/copying/bytes_inside_mappings.sol @@ -5,17 +5,20 @@ contract c { } // ---- // set(uint256): 1, 2 -> true -// gas irOptimized: 110550 +// gas irOptimized: 110560 // gas legacy: 111310 // gas legacyOptimized: 110741 // set(uint256): 2, 2, 3, 4, 5 -> true -// gas irOptimized: 177501 +// gas irOptimized: 177511 // gas legacy: 178312 // gas legacyOptimized: 177716 // storageEmpty -> 0 // copy(uint256,uint256): 1, 2 -> true +// gas irOptimized: 48421 // storageEmpty -> 0 // copy(uint256,uint256): 99, 1 -> true +// gas irOptimized: 35641 // storageEmpty -> 0 // copy(uint256,uint256): 99, 2 -> true +// gas irOptimized: 35641 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/copying/bytes_storage_to_storage.sol b/test/libsolidity/semanticTests/array/copying/bytes_storage_to_storage.sol index fe758fe8fd13..37769cb4966c 100644 --- a/test/libsolidity/semanticTests/array/copying/bytes_storage_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/bytes_storage_to_storage.sol @@ -19,23 +19,24 @@ contract c { // f(uint256): 31 -> 0x20, 0x1f, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e00 // gas irOptimized: 103268 // gas legacy: 112904 -// gas legacyOptimized: 112647 +// gas legacyOptimized: 112645 // f(uint256): 32 -> 0x20, 0x20, 1780731860627700044960722568376592200742329637303199754547598369979440671 // gas irOptimized: 117825 // gas legacy: 128964 -// gas legacyOptimized: 128854 +// gas legacyOptimized: 128859 // f(uint256): 33 -> 0x20, 33, 1780731860627700044960722568376592200742329637303199754547598369979440671, 0x2000000000000000000000000000000000000000000000000000000000000000 -// gas irOptimized: 124091 -// gas legacy: 136092 -// gas legacyOptimized: 135469 +// gas irOptimized: 123888 +// gas legacy: 135510 +// gas legacyOptimized: 135218 // f(uint256): 63 -> 0x20, 0x3f, 1780731860627700044960722568376592200742329637303199754547598369979440671, 14532552714582660066924456880521368950258152170031413196862950297402215316992 -// gas irOptimized: 127151 -// gas legacy: 148692 -// gas legacyOptimized: 148699 +// gas irOptimized: 126948 +// gas legacy: 148110 +// gas legacyOptimized: 148448 // f(uint256): 12 -> 0x20, 0x0c, 0x0102030405060708090a0b0000000000000000000000000000000000000000 -// gas legacy: 59345 -// gas legacyOptimized: 57279 +// gas irOptimized: 54204 +// gas legacy: 59832 +// gas legacyOptimized: 57268 // f(uint256): 129 -> 0x20, 0x81, 1780731860627700044960722568376592200742329637303199754547598369979440671, 0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f, 29063324697304692433803953038474361308315562010425523193971352996434451193439, 0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f, -57896044618658097711785492504343953926634992332820282019728792003956564819968 // gas irOptimized: 416918 // gas legacy: 458997 -// gas legacyOptimized: 460664 +// gas legacyOptimized: 460669 diff --git a/test/libsolidity/semanticTests/array/copying/calldata_array_dynamic_to_storage.sol b/test/libsolidity/semanticTests/array/copying/calldata_array_dynamic_to_storage.sol index 3df22c725192..400834772869 100644 --- a/test/libsolidity/semanticTests/array/copying/calldata_array_dynamic_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/calldata_array_dynamic_to_storage.sol @@ -9,6 +9,6 @@ contract C { } // ---- // f(uint256[]): 0x20, 0x03, 0x1, 0x2, 0x3 -> 0x1 -// gas irOptimized: 111084 -// gas legacy: 111548 -// gas legacyOptimized: 111321 +// gas irOptimized: 110973 +// gas legacy: 112436 +// gas legacyOptimized: 111313 diff --git a/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol b/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol index e6284072d1f9..cdc3c4f66765 100644 --- a/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol +++ b/test/libsolidity/semanticTests/array/copying/cleanup_during_multi_element_per_slot_copy.sol @@ -16,10 +16,10 @@ contract C { } // ---- // constructor() -// gas irOptimized: 90065 -// gas irOptimized code: 149000 -// gas legacy: 89553 -// gas legacy code: 126200 -// gas legacyOptimized: 83556 -// gas legacyOptimized code: 98200 +// gas irOptimized: 89349 +// gas irOptimized code: 140200 +// gas legacy: 100902 +// gas legacy code: 271400 +// gas legacyOptimized: 83062 +// gas legacyOptimized code: 91800 // f() -> 0 diff --git a/test/libsolidity/semanticTests/array/copying/copy_byte_array_in_struct_to_storage.sol b/test/libsolidity/semanticTests/array/copying/copy_byte_array_in_struct_to_storage.sol index 787973c5f0e5..9a68f88d8971 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_byte_array_in_struct_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_byte_array_in_struct_to_storage.sol @@ -37,10 +37,11 @@ contract C { // f() -> 0x40, 0x80, 6, 0x6162636465660000000000000000000000000000000000000000000000000000, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000 // gas irOptimized: 179405 // gas legacy: 180675 -// gas legacyOptimized: 179686 +// gas legacyOptimized: 179690 // g() -> 0x40, 0xc0, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000, 0x11, 0x3132333435363738393233343536373839000000000000000000000000000000 -// gas irOptimized: 106338 -// gas legacy: 109415 -// gas legacyOptimized: 106600 +// gas irOptimized: 106381 +// gas legacy: 109472 +// gas legacyOptimized: 106648 // h() -> 0x40, 0x60, 0x00, 0x00 +// gas irOptimized: 46948 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/copying/copy_byte_array_to_storage.sol b/test/libsolidity/semanticTests/array/copying/copy_byte_array_to_storage.sol index fc274bf47b28..caa0596610ad 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_byte_array_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_byte_array_to_storage.sol @@ -46,6 +46,6 @@ contract C { } // ---- // f() -> 0xff -// gas irOptimized: 143857 -// gas legacy: 153404 -// gas legacyOptimized: 146676 +// gas irOptimized: 143935 +// gas legacy: 153487 +// gas legacyOptimized: 146748 diff --git a/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol b/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol index 7db82f891dfb..5fc981340942 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_function_internal_storage_array.sol @@ -15,6 +15,6 @@ contract C { } // ---- // test() -> 7 -// gas irOptimized: 122459 -// gas legacy: 205176 -// gas legacyOptimized: 204971 +// gas irOptimized: 122575 +// gas legacy: 208558 +// gas legacyOptimized: 202409 diff --git a/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol b/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol index 95ba9d8ed8e8..6c1d061d309b 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_internal_function_array_to_storage.sol @@ -18,6 +18,6 @@ contract C { } // ---- // one() -> 3 -// gas legacy: 140253 -// gas legacyOptimized: 140093 +// gas legacy: 142687 +// gas legacyOptimized: 135991 // two() -> FAILURE, hex"4e487b71", 0x51 diff --git a/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol b/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol index 46216e912ce4..6872995d1cbb 100644 --- a/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol +++ b/test/libsolidity/semanticTests/array/copying/copy_removes_bytes_data.sol @@ -9,7 +9,8 @@ contract c { // set(): 1, 2, 3, 4, 5 -> true // gas irOptimized: 177344 // gas legacy: 177953 -// gas legacyOptimized: 177550 +// gas legacyOptimized: 177551 // storageEmpty -> 0 // reset() -> true +// gas irOptimized: 47341 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/copying/copying_bytes_multiassign.sol b/test/libsolidity/semanticTests/array/copying/copying_bytes_multiassign.sol index ce4344e871db..d29feccf421c 100644 --- a/test/libsolidity/semanticTests/array/copying/copying_bytes_multiassign.sol +++ b/test/libsolidity/semanticTests/array/copying/copying_bytes_multiassign.sol @@ -18,13 +18,15 @@ contract sender { } // ---- // (): 7 -> -// gas irOptimized: 110822 +// gas irOptimized: 110735 // gas legacy: 111388 -// gas legacyOptimized: 111065 +// gas legacyOptimized: 111066 // val() -> 0 // forward(bool): true -> true +// gas irOptimized: 49573 // val() -> 0x80 // forward(bool): false -> true +// gas irOptimized: 31410 // val() -> 0x80 // forward(bool): true -> true // val() -> 0x80 diff --git a/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage.sol index 672e7435f1a9..5c64ba63f8f8 100644 --- a/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_calldata_to_storage.sol @@ -31,8 +31,8 @@ contract C { // compileViaYul: true // ---- // test1((uint8[],uint8[2])[][][]): 0x20, 1, 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -> 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -// gas irOptimized: 327921 +// gas irOptimized: 327936 // test2((uint8[],uint8[2])[][1][]): 0x20, 2, 0x40, 0x0160, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13, 0x20, 1, 0x20, 0x60, 31, 37, 2, 23, 29 -> 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 141162 +// gas irOptimized: 141168 // test3((uint8[],uint8[2])[1][][2]): 0x20, 0x40, 0x60, 0, 2, 0x40, 288, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 2, 0x40, 288, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 188442 +// gas irOptimized: 188448 diff --git a/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_memory_to_storage.sol b/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_memory_to_storage.sol index f232d8aba9d4..12c40849fe6f 100644 --- a/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_memory_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/elements_of_nested_array_of_structs_memory_to_storage.sol @@ -31,8 +31,8 @@ contract C { // compileViaYul: true // ---- // test1((uint8[],uint8[2])[][][]): 0x20, 1, 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -> 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -// gas irOptimized: 332567 +// gas irOptimized: 332582 // test2((uint8[],uint8[2])[][1][]): 0x20, 2, 0x40, 0x0160, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13, 0x20, 1, 0x20, 0x60, 31, 37, 2, 23, 29 -> 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 145409 +// gas irOptimized: 145415 // test3((uint8[],uint8[2])[1][][2]): 0x20, 0x40, 0x60, 0, 2, 0x40, 288, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 2, 0x40, 288, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 192248 +// gas irOptimized: 192254 diff --git a/test/libsolidity/semanticTests/array/copying/function_type_array_to_storage.sol b/test/libsolidity/semanticTests/array/copying/function_type_array_to_storage.sol index 640046a11a4f..a5cf48011226 100644 --- a/test/libsolidity/semanticTests/array/copying/function_type_array_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/function_type_array_to_storage.sol @@ -46,11 +46,11 @@ contract C { } // ---- // test() -> 0x20, 0x14, "[a called][b called]" -// gas irOptimized: 116518 -// gas legacy: 118841 -// gas legacyOptimized: 116843 +// gas irOptimized: 116512 +// gas legacy: 120226 +// gas legacyOptimized: 116941 // test2() -> 0x20, 0x14, "[b called][a called]" // test3() -> 0x20, 0x14, "[b called][a called]" -// gas irOptimized: 103144 -// gas legacy: 102654 -// gas legacyOptimized: 101556 +// gas irOptimized: 103138 +// gas legacy: 105192 +// gas legacyOptimized: 103752 diff --git a/test/libsolidity/semanticTests/array/copying/memory_dyn_2d_bytes_to_storage.sol b/test/libsolidity/semanticTests/array/copying/memory_dyn_2d_bytes_to_storage.sol index 8a4d870b22f8..93eb84427d66 100644 --- a/test/libsolidity/semanticTests/array/copying/memory_dyn_2d_bytes_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/memory_dyn_2d_bytes_to_storage.sol @@ -18,6 +18,6 @@ contract C { } // ---- // f() -> 3 -// gas irOptimized: 128296 -// gas legacy: 129077 -// gas legacyOptimized: 128210 +// gas irOptimized: 128301 +// gas legacy: 129528 +// gas legacyOptimized: 128171 diff --git a/test/libsolidity/semanticTests/array/copying/nested_array_element_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_array_element_calldata_to_storage.sol index 5040a72cadb5..f9d14b59edf5 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_array_element_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_array_element_calldata_to_storage.sol @@ -25,5 +25,5 @@ contract C { // ---- // test(uint8[2][2][2]): 1, 2, 3, 4, 5, 6, 7, 8 // test2(uint8[2][2]): 1, 2, 3, 4 -// gas irOptimized: 119939 -// gas legacyOptimized: 120228 +// gas irOptimized: 72076 +// gas legacyOptimized: 72086 diff --git a/test/libsolidity/semanticTests/array/copying/nested_array_element_memory_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_array_element_memory_to_storage.sol index 2f4b4ca7c9a1..d604a11e6cd4 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_array_element_memory_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_array_element_memory_to_storage.sol @@ -25,5 +25,5 @@ contract C { // ---- // test(uint8[2][2][2]): 1, 2, 3, 4, 5, 6, 7, 8 // test2(uint8[2][2]): 1, 2, 3, 4 -// gas irOptimized: 119939 -// gas legacyOptimized: 120228 +// gas irOptimized: 73031 +// gas legacyOptimized: 73155 diff --git a/test/libsolidity/semanticTests/array/copying/nested_array_element_storage_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_array_element_storage_to_storage.sol index f3934fec0f6f..1a3fb70e0845 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_array_element_storage_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_array_element_storage_to_storage.sol @@ -71,14 +71,14 @@ contract C { // ---- // test1() -> // gas irOptimized: 150488 -// gas legacy: 150949 -// gas legacyOptimized: 150906 +// gas legacy: 156275 +// gas legacyOptimized: 151018 // test2() -> FAILURE // gas irOptimized: 150389 -// gas legacy: 150672 -// gas legacyOptimized: 150575 +// gas legacy: 155998 +// gas legacyOptimized: 150684 // test3() -> // gas irOptimized: 124300 -// gas legacy: 125333 -// gas legacyOptimized: 125127 +// gas legacy: 130649 +// gas legacyOptimized: 125142 // test4() -> FAILURE diff --git a/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_calldata_to_storage.sol index 7621b2477e03..b27fe9e06453 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_calldata_to_storage.sol @@ -29,8 +29,8 @@ contract C { // compileViaYul: true // ---- // test1((uint8[],uint8[2])[][]): 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -> 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -// gas irOptimized: 304788 +// gas irOptimized: 304750 // test2((uint8[],uint8[2])[][1]): 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 116653 +// gas irOptimized: 116659 // test3((uint8[],uint8[2])[1][]): 0x20, 2, 0x40, 0x0120, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 2, 0x40, 0x0120, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 187994 +// gas irOptimized: 187942 diff --git a/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_memory_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_memory_to_storage.sol index 8ec3f77691b5..146de2f2af6a 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_memory_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_array_of_structs_memory_to_storage.sol @@ -29,8 +29,8 @@ contract C { // compileViaYul: true // ---- // test1((uint8[],uint8[2])[][]): 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -> 0x20, 2, 0x40, 0x0140, 1, 0x20, 0x60, 3, 7, 2, 1, 2, 2, 0x40, 0x0100, 0x60, 17, 19, 2, 11, 13, 0x60, 31, 37, 2, 23, 29 -// gas irOptimized: 309072 +// gas irOptimized: 309034 // test2((uint8[],uint8[2])[][1]): 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 0x20, 1, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 118314 +// gas irOptimized: 118320 // test3((uint8[],uint8[2])[1][]): 0x20, 2, 0x40, 0x0120, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -> 0x20, 2, 0x40, 0x0120, 0x20, 0x60, 3, 7, 2, 1, 2, 0x20, 0x60, 17, 19, 2, 11, 13 -// gas irOptimized: 191045 +// gas irOptimized: 190993 diff --git a/test/libsolidity/semanticTests/array/copying/nested_dynamic_array_element_calldata_to_storage.sol b/test/libsolidity/semanticTests/array/copying/nested_dynamic_array_element_calldata_to_storage.sol index 57b3dc521f65..7583f5ab4f0d 100644 --- a/test/libsolidity/semanticTests/array/copying/nested_dynamic_array_element_calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/array/copying/nested_dynamic_array_element_calldata_to_storage.sol @@ -30,7 +30,7 @@ contract C { // compileViaYul: true // ---- // test(uint8[][][]): 0x20, 2, 0x40, 0x60, 0, 2, 0x40, 0x80, 1, 7, 2, 8, 9 -// gas irOptimized: 137897 +// gas irOptimized: 137891 // test2(uint8[][]): 0x20, 2, 0x40, 0x80, 1, 7, 2, 8, 9 -// gas irOptimized: 164482 +// gas irOptimized: 164249 // gas legacyOptimized: 120228 diff --git a/test/libsolidity/semanticTests/array/copying/storage_memory_nested_bytes.sol b/test/libsolidity/semanticTests/array/copying/storage_memory_nested_bytes.sol index e5a6ae53b2da..3f52085f4b34 100644 --- a/test/libsolidity/semanticTests/array/copying/storage_memory_nested_bytes.sol +++ b/test/libsolidity/semanticTests/array/copying/storage_memory_nested_bytes.sol @@ -13,4 +13,4 @@ contract C { // f() -> 0x20, 0x02, 0x40, 0x80, 3, 0x6162630000000000000000000000000000000000000000000000000000000000, 0x99, 44048183304486788312148433451363384677562265908331949128489393215789685032262, 32241931068525137014058842823026578386641954854143559838526554899205067598957, 49951309422467613961193228765530489307475214998374779756599339590522149884499, 0x54555658595a6162636465666768696a6b6c6d6e6f707172737475767778797a, 0x4142434445464748494a4b4c4d4e4f5051525354555658595a00000000000000 // gas irOptimized: 202083 // gas legacy: 204327 -// gas legacyOptimized: 202899 +// gas legacyOptimized: 202903 diff --git a/test/libsolidity/semanticTests/array/delete/bytes_delete_element.sol b/test/libsolidity/semanticTests/array/delete/bytes_delete_element.sol index f776626ae8e3..03c47f69b414 100644 --- a/test/libsolidity/semanticTests/array/delete/bytes_delete_element.sol +++ b/test/libsolidity/semanticTests/array/delete/bytes_delete_element.sol @@ -16,6 +16,6 @@ contract c { } // ---- // test1() -> true -// gas irOptimized: 218449 +// gas irOptimized: 218420 // gas legacy: 242263 -// gas legacyOptimized: 241182 +// gas legacyOptimized: 241187 diff --git a/test/libsolidity/semanticTests/array/delete/delete_bytes_array.sol b/test/libsolidity/semanticTests/array/delete/delete_bytes_array.sol index b735d182d555..32a96c72c310 100644 --- a/test/libsolidity/semanticTests/array/delete/delete_bytes_array.sol +++ b/test/libsolidity/semanticTests/array/delete/delete_bytes_array.sol @@ -32,3 +32,4 @@ contract C { // ---- // f() -> 0 // g() -> 0 +// gas irOptimized: 56617 diff --git a/test/libsolidity/semanticTests/array/delete/delete_storage_array_packed.sol b/test/libsolidity/semanticTests/array/delete/delete_storage_array_packed.sol index 9665201c98fd..23b0466e7530 100644 --- a/test/libsolidity/semanticTests/array/delete/delete_storage_array_packed.sol +++ b/test/libsolidity/semanticTests/array/delete/delete_storage_array_packed.sol @@ -14,6 +14,6 @@ contract C { } // ---- // f() -> 0, 0, 0 -// gas irOptimized: 90992 -// gas legacy: 111037 -// gas legacyOptimized: 109633 +// gas irOptimized: 88028 +// gas legacy: 88799 +// gas legacyOptimized: 87706 diff --git a/test/libsolidity/semanticTests/array/fixed_array_cleanup.sol b/test/libsolidity/semanticTests/array/fixed_array_cleanup.sol index ebe2186fd60f..af0ec6b7faf0 100644 --- a/test/libsolidity/semanticTests/array/fixed_array_cleanup.sol +++ b/test/libsolidity/semanticTests/array/fixed_array_cleanup.sol @@ -15,7 +15,7 @@ contract c { // gas legacyOptimized: 466238 // storageEmpty -> 0 // clear() -> -// gas irOptimized: 122148 -// gas legacy: 122440 -// gas legacyOptimized: 122259 +// gas irOptimized: 97800 +// gas legacy: 97947 +// gas legacyOptimized: 97882 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/invalid_encoding_for_storage_byte_array.sol b/test/libsolidity/semanticTests/array/invalid_encoding_for_storage_byte_array.sol index 95df74b4d77b..a53d00eaaf31 100644 --- a/test/libsolidity/semanticTests/array/invalid_encoding_for_storage_byte_array.sol +++ b/test/libsolidity/semanticTests/array/invalid_encoding_for_storage_byte_array.sol @@ -40,16 +40,18 @@ contract C { // copyFromStorageShort() // x() -> 0x20, 3, 0x6162630000000000000000000000000000000000000000000000000000000000 // copyFromStorageLong() -// gas irOptimized: 121095 +// gas irOptimized: 121092 // gas legacy: 121904 -// gas legacyOptimized: 121388 +// gas legacyOptimized: 121393 // x() -> 0x20, 0x25, 0x3132333435363738393031323334353637383930313233343536373839303132, 0x3334353637000000000000000000000000000000000000000000000000000000 // copyToStorage() // x() -> 0x20, 0x25, 0x3132333435363738393031323334353637383930313233343536373839303132, 0x3334353637000000000000000000000000000000000000000000000000000000 // y() -> 0x20, 0x25, 0x3132333435363738393031323334353637383930313233343536373839303132, 0x3334353637000000000000000000000000000000000000000000000000000000 // del() +// gas irOptimized: 29404 // x() -> 0x20, 0x00 // invalidateXLong() +// gas irOptimized: 47028 // x() -> FAILURE, hex"4e487b71", 0x22 // abiEncode() -> FAILURE, hex"4e487b71", 0x22 // abiEncodePacked() -> FAILURE, hex"4e487b71", 0x22 diff --git a/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_delete.sol b/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_delete.sol new file mode 100644 index 000000000000..b638664aaca1 --- /dev/null +++ b/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_delete.sol @@ -0,0 +1,77 @@ +contract C { + bytes testArray; + + struct Canary { + uint256 value; + } + + function getCanary() internal pure returns (Canary storage canary) { + assembly { + mstore(0, testArray.slot) + let testArrayDataArea := keccak256(0, 0x20) + // testArray's data area occupies 3 slots when filled. Canary goes right after + canary.slot := add(testArrayDataArea, 3) + } + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; + } + + function getArrayDataAreaSlot() public pure returns (uint256 slot) { + assembly { + mstore(0, testArray.slot) + slot := keccak256(0, 0x20) + } + assert(slot == 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563); + return slot; + } + + function getCanarySlot() public pure returns (uint256) { + return getArrayDataAreaSlot() + 3; + } + + function checkSlots() public view returns (uint256, uint256, uint256, uint256, uint256) { + uint256 dataSlot = getArrayDataAreaSlot(); + uint256 slot0; + uint256 slot1; + uint256 slot2; + uint256 slot3; + assembly { + slot0 := sload(dataSlot) + slot1 := sload(add(dataSlot, 1)) + slot2 := sload(add(dataSlot, 2)) + slot3 := sload(add(dataSlot, 3)) + } + return (slot0, slot1, slot2, slot3, testArray.length); + } + + function fillArray() public { + // Fill testArray to exactly 96 bytes (3 slots in its data area) + for (uint i = 0; i < 96; i++) { + testArray.push(bytes1(uint8(i + 1))); + } + } + + function deleteArray() public { + // Should clear 3 slots without touching canary + delete testArray; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } +} +// ---- +// getArrayDataAreaSlot() -> 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 +// getCanarySlot() -> 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e566 +// checkSlots() -> 0, 0, 0, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// fillArray() +// gas irOptimized: 197289 +// gas legacy: 220574 +// gas legacyOptimized: 206839 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// deleteArray() +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_overwrite_with_long.sol b/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_overwrite_with_long.sol new file mode 100644 index 000000000000..edb00809bcd3 --- /dev/null +++ b/test/libsolidity/semanticTests/array/long_byte_array_cleanup_after_overwrite_with_long.sol @@ -0,0 +1,105 @@ +contract C { + bytes testArray; + + struct Canary { + uint256 value; + } + + function getCanary() internal pure returns (Canary storage canary) { + assembly { + mstore(0, testArray.slot) + let testArrayDataArea := keccak256(0, 0x20) + // testArray's data area occupies 3 slots when filled. Canary goes right after + canary.slot := add(testArrayDataArea, 3) + } + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; // Should not be overwritten + } + + function fillArray() public { + // Fill testArray to exactly 96 bytes (3 slots in its data area) + for (uint i = 0; i < 96; i++) { + testArray.push(bytes1(uint8(i + 1))); + } + } + + function shrinkArray() public returns (uint256) { + // Shrink from 96 to 50 bytes. Should clear slot 2 without touching canary + bytes memory newData = new bytes(50); + for (uint i = 0; i < 50; i++) { + newData[i] = bytes1(uint8(i + 2)); + } + testArray = newData; + return testArray.length; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } + + function arrayLength() public view returns (uint256) { + return testArray.length; + } + + function getDataSlotContent(uint256 index) public view returns (bytes32 value) { + assembly { + mstore(0, testArray.slot) + let testArrayDataArea := keccak256(0, 0x20) + let slot := add(testArrayDataArea, index) + value := sload(slot) + } + return value; + } + + function checkSlots() public view returns (bytes32, bytes32, bytes32, uint256, uint256) { + return ( + getDataSlotContent(0), // First data slot + getDataSlotContent(1), // Second data slot (partial cleanup expected) + getDataSlotContent(2), // Third data slot (should be cleared after shrink) + canaryValue(), // Canary value (should never change) + arrayLength() // Current array length + ); + } + + function getSlot1LastBytes() public view returns (bytes14 lastBytes) { + // Get the last 14 bytes of slot 1, which should be zero after partial cleanup + bytes32 slot1 = getDataSlotContent(1); + assembly { + // Shift left by 18 bytes (144 bits) to move the last 14 bytes to the front + lastBytes := shl(144, slot1) + } + return lastBytes; + } + + function getArrayBytes(uint256 start, uint256 count) public view returns (bytes memory) { + bytes memory result = new bytes(count); + for (uint i = 0; i < count && start + i < testArray.length; i++) { + result[i] = testArray[start + i]; + } + return result; + } +} +// ---- +// arrayLength() ->0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// fillArray() +// gas irOptimized: 197352 +// gas legacy: 220574 +// gas legacyOptimized: 206839 +// arrayLength() ->96 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getArrayBytes(uint256,uint256): 0, 5 -> 0x20, 5, 0x0102030405000000000000000000000000000000000000000000000000000000 +// getArrayBytes(uint256,uint256): 32, 5 -> 0x20, 5, 0x2122232425000000000000000000000000000000000000000000000000000000 +// getArrayBytes(uint256,uint256): 64, 5 -> 0x20, 5, 0x4142434445000000000000000000000000000000000000000000000000000000 +// shrinkArray() -> 50 +// arrayLength() ->50 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getArrayBytes(uint256,uint256): 0, 5 -> 0x20, 5, 0x0203040506000000000000000000000000000000000000000000000000000000 +// getArrayBytes(uint256,uint256): 32, 5 -> 0x20, 5, 0x2223242526000000000000000000000000000000000000000000000000000000 +// getArrayBytes(uint256,uint256): 45, 5 -> 0x20, 5, 0x2f30313233000000000000000000000000000000000000000000000000000000 +// getSlot1LastBytes() -> 0 +// getDataSlotContent(uint256): 2 -> 0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libsolidity/semanticTests/array/nested_calldata_storage2.sol b/test/libsolidity/semanticTests/array/nested_calldata_storage2.sol index f8db31b54cc8..f3cb51187103 100644 --- a/test/libsolidity/semanticTests/array/nested_calldata_storage2.sol +++ b/test/libsolidity/semanticTests/array/nested_calldata_storage2.sol @@ -8,6 +8,6 @@ contract C { // compileViaYul: true // ---- // i(uint256[][]): 0x20, 2, 0x40, 0xC0, 3, 0x0A01, 0x0A02, 0x0A03, 4, 0x0B01, 0x0B02, 0x0B03, 0x0B04 -// gas irOptimized: 245506 +// gas irOptimized: 245511 // tmp_i(uint256,uint256): 0, 0 -> 0x0A01 // tmp_i(uint256,uint256): 1, 0 -> 0x0B01 diff --git a/test/libsolidity/semanticTests/array/pop/array_pop_array_transition.sol b/test/libsolidity/semanticTests/array/pop/array_pop_array_transition.sol index 96fb11a2dc94..3b96397166ea 100644 --- a/test/libsolidity/semanticTests/array/pop/array_pop_array_transition.sol +++ b/test/libsolidity/semanticTests/array/pop/array_pop_array_transition.sol @@ -23,7 +23,7 @@ contract c { } // ---- // test() -> 1, 2, 3 -// gas irOptimized: 1828226 -// gas legacy: 1822464 -// gas legacyOptimized: 1813404 +// gas irOptimized: 1828383 +// gas legacy: 1841995 +// gas legacyOptimized: 1821687 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol b/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol index 77ae8c4e23e4..83c5c1724307 100644 --- a/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol +++ b/test/libsolidity/semanticTests/array/push/array_push_nested_from_calldata.sol @@ -13,5 +13,5 @@ contract C { // ---- // f(uint120[]): 0x20, 3, 1, 2, 3 -> 1 // gas irOptimized: 112852 -// gas legacy: 113657 -// gas legacyOptimized: 113465 +// gas legacy: 114404 +// gas legacyOptimized: 113087 diff --git a/test/libsolidity/semanticTests/array/push/array_push_struct.sol b/test/libsolidity/semanticTests/array/push/array_push_struct.sol index 92f071e1fb22..4e1e8b4b3db0 100644 --- a/test/libsolidity/semanticTests/array/push/array_push_struct.sol +++ b/test/libsolidity/semanticTests/array/push/array_push_struct.sol @@ -21,5 +21,5 @@ contract c { // ---- // test() -> 2, 3, 4, 5 // gas irOptimized: 135329 -// gas legacy: 147437 -// gas legacyOptimized: 146429 +// gas legacy: 139481 +// gas legacyOptimized: 135795 diff --git a/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol b/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol index 5633199d4865..04b2a4a736c9 100644 --- a/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol +++ b/test/libsolidity/semanticTests/array/push/nested_bytes_push.sol @@ -15,4 +15,4 @@ contract C { // f() -> // gas irOptimized: 179534 // gas legacy: 181013 -// gas legacyOptimized: 180397 +// gas legacyOptimized: 180406 diff --git a/test/libsolidity/semanticTests/calldata/copy_from_calldata_removes_bytes_data.sol b/test/libsolidity/semanticTests/calldata/copy_from_calldata_removes_bytes_data.sol index 33ab18450624..c3f2e8d751e5 100644 --- a/test/libsolidity/semanticTests/calldata/copy_from_calldata_removes_bytes_data.sol +++ b/test/libsolidity/semanticTests/calldata/copy_from_calldata_removes_bytes_data.sol @@ -11,7 +11,8 @@ contract c { // (): 1, 2, 3, 4, 5 -> // gas irOptimized: 155122 // gas legacy: 155473 -// gas legacyOptimized: 155295 +// gas legacyOptimized: 155296 // checkIfDataIsEmpty() -> false // sendMessage() -> true, 0x40, 0 +// gas irOptimized: 41925 // checkIfDataIsEmpty() -> true diff --git a/test/libsolidity/semanticTests/cleanup/byte_array_to_storage_cleanup.sol b/test/libsolidity/semanticTests/cleanup/byte_array_to_storage_cleanup.sol index 238f96b7e588..4c75ad452b8d 100644 --- a/test/libsolidity/semanticTests/cleanup/byte_array_to_storage_cleanup.sol +++ b/test/libsolidity/semanticTests/cleanup/byte_array_to_storage_cleanup.sol @@ -28,13 +28,14 @@ contract C { // compileViaYul: also // ---- // constructor() -> -// gas irOptimized: 82100 -// gas irOptimized code: 357600 -// gas legacy: 101532 -// gas legacy code: 604800 -// gas legacyOptimized: 84956 -// gas legacyOptimized code: 391800 +// gas irOptimized: 82586 +// gas irOptimized code: 363600 +// gas legacy: 101811 +// gas legacy code: 608200 +// gas legacyOptimized: 85196 +// gas legacyOptimized code: 394800 // h() -> 0x20, 0x40, 0x00, 0 // ~ emit ev(uint256[],uint256): 0x40, 0x21, 0x02, 0x00, 0x00 // g() -> 0x20, 0x40, 0, 0x00 // f(bytes): 0x20, 33, 0, -1 -> 0x20, 0x22, 0, 0xff00000000000000000000000000000000000000000000000000000000000000 +// gas irOptimized: 54117 diff --git a/test/libsolidity/semanticTests/constructor/arrays_in_constructors.sol b/test/libsolidity/semanticTests/constructor/arrays_in_constructors.sol index aeeb1a0c1aa4..76a4dd961fe4 100644 --- a/test/libsolidity/semanticTests/constructor/arrays_in_constructors.sol +++ b/test/libsolidity/semanticTests/constructor/arrays_in_constructors.sol @@ -28,7 +28,7 @@ contract Creator { // f(uint256,address[]): 7, 0x40, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> 7, 8 // gas irOptimized: 327784 // gas irOptimized code: 94000 -// gas legacy: 336623 +// gas legacy: 338477 // gas legacy code: 244800 -// gas legacyOptimized: 329515 +// gas legacyOptimized: 329166 // gas legacyOptimized code: 117000 diff --git a/test/libsolidity/semanticTests/constructor/bytes_in_constructors_packer.sol b/test/libsolidity/semanticTests/constructor/bytes_in_constructors_packer.sol index 55abe4804c56..91be8e3789f3 100644 --- a/test/libsolidity/semanticTests/constructor/bytes_in_constructors_packer.sol +++ b/test/libsolidity/semanticTests/constructor/bytes_in_constructors_packer.sol @@ -26,9 +26,9 @@ contract Creator { // bytecodeFormat: legacy,>=EOFv1 // ---- // f(uint256,bytes): 7, 0x40, 78, "abcdefghijklmnopqrstuvwxyzabcdef", "ghijklmnopqrstuvwxyzabcdefghijkl", "mnopqrstuvwxyz" -> 7, "h" -// gas irOptimized: 169292 +// gas irOptimized: 169297 // gas irOptimized code: 99600 -// gas legacy: 172941 +// gas legacy: 172946 // gas legacy code: 239800 -// gas legacyOptimized: 169815 +// gas legacyOptimized: 169823 // gas legacyOptimized code: 118600 diff --git a/test/libsolidity/semanticTests/constructor/bytes_in_constructors_unpacker.sol b/test/libsolidity/semanticTests/constructor/bytes_in_constructors_unpacker.sol index 13db633b9b06..8a072f3ebed6 100644 --- a/test/libsolidity/semanticTests/constructor/bytes_in_constructors_unpacker.sol +++ b/test/libsolidity/semanticTests/constructor/bytes_in_constructors_unpacker.sol @@ -10,11 +10,11 @@ contract Test { // bytecodeFormat: legacy,>=EOFv1 // ---- // constructor(): 7, 0x40, 78, "abcdefghijklmnopqrstuvwxyzabcdef", "ghijklmnopqrstuvwxyzabcdefghijkl", "mnopqrstuvwxyz" -> -// gas irOptimized: 181465 +// gas irOptimized: 181629 // gas irOptimized code: 78400 -// gas legacy: 195212 +// gas legacy: 195484 // gas legacy code: 109400 -// gas legacyOptimized: 181608 +// gas legacyOptimized: 181853 // gas legacyOptimized code: 71400 // m_x() -> 7 // m_s() -> 0x20, 78, "abcdefghijklmnopqrstuvwxyzabcdef", "ghijklmnopqrstuvwxyzabcdefghijkl", "mnopqrstuvwxyz" diff --git a/test/libsolidity/semanticTests/constructor/constructor_static_array_argument.sol b/test/libsolidity/semanticTests/constructor/constructor_static_array_argument.sol index ed4eb6e4d15f..cc196a95c802 100644 --- a/test/libsolidity/semanticTests/constructor/constructor_static_array_argument.sol +++ b/test/libsolidity/semanticTests/constructor/constructor_static_array_argument.sol @@ -13,9 +13,9 @@ contract C { // constructor(): 1, 2, 3, 4 -> // gas irOptimized: 148129 // gas irOptimized code: 23000 -// gas legacy: 157977 +// gas legacy: 166201 // gas legacy code: 60400 -// gas legacyOptimized: 149973 +// gas legacyOptimized: 149177 // gas legacyOptimized code: 26200 // a() -> 1 // b(uint256): 0 -> 2 diff --git a/test/libsolidity/semanticTests/events/event_dynamic_nested_array_storage_v2.sol b/test/libsolidity/semanticTests/events/event_dynamic_nested_array_storage_v2.sol index 1ff58d65b566..42b9b50dd779 100644 --- a/test/libsolidity/semanticTests/events/event_dynamic_nested_array_storage_v2.sol +++ b/test/libsolidity/semanticTests/events/event_dynamic_nested_array_storage_v2.sol @@ -16,5 +16,5 @@ contract C { // createEvent(uint256): 42 -> // ~ emit E(uint256[][]): 0x20, 0x02, 0x40, 0xa0, 0x02, 0x2a, 0x2b, 0x02, 0x2c, 0x2d // gas irOptimized: 185148 -// gas legacy: 187493 +// gas legacy: 188615 // gas legacyOptimized: 184548 diff --git a/test/libsolidity/semanticTests/fallback/call_forward_bytes.sol b/test/libsolidity/semanticTests/fallback/call_forward_bytes.sol index 7bf3aee3acfe..dfd3d658fe2f 100644 --- a/test/libsolidity/semanticTests/fallback/call_forward_bytes.sol +++ b/test/libsolidity/semanticTests/fallback/call_forward_bytes.sol @@ -20,6 +20,7 @@ contract sender { // forward() -> true // val() -> 8 // clear() -> true +// gas irOptimized: 29243 // val() -> 8 // forward() -> true // val() -> 0x80 diff --git a/test/libsolidity/semanticTests/storage/empty_nonempty_empty.sol b/test/libsolidity/semanticTests/storage/empty_nonempty_empty.sol index 8a795373e061..8d72f7c04358 100644 --- a/test/libsolidity/semanticTests/storage/empty_nonempty_empty.sol +++ b/test/libsolidity/semanticTests/storage/empty_nonempty_empty.sol @@ -22,11 +22,12 @@ contract Test { // set(bytes): 0x20, 0 // storageEmpty -> 1 // set(bytes): 0x20, 66, "12345678901234567890123456789012", "12345678901234567890123456789012", "12" -// gas irOptimized: 111849 +// gas irOptimized: 111820 // gas legacy: 112734 -// gas legacyOptimized: 112084 +// gas legacyOptimized: 112089 // storageEmpty -> 0 // set(bytes): 0x20, 3, "abc" +// gas irOptimized: 33989 // storageEmpty -> 0 // set(bytes): 0x20, 0 // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/storage/static_array_copy_cleanup.sol b/test/libsolidity/semanticTests/storage/static_array_copy_cleanup.sol new file mode 100644 index 000000000000..ca471161d5e2 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/static_array_copy_cleanup.sol @@ -0,0 +1,94 @@ +contract C { + struct S { + uint64 a; + uint64 b; + uint64 c; + uint64 d; + // All fit in one slot (4 * 64 = 256 bits) + } + + S[5] source; + S[10] dest; + uint256 public canary = type(uint256).max; + + function fillSource() public { + for (uint i = 0; i < 5; i++) { + source[i] = S({ + a: uint64(1 + i * 4), + b: uint64(2 + i * 4), + c: uint64(3 + i * 4), + d: uint64(4 + i * 4) + }); + } + } + + function fillDest() public { + for (uint i = 0; i < 10; i++) { + dest[i] = S({ + a: uint64(100 + i * 4), + b: uint64(101 + i * 4), + c: uint64(102 + i * 4), + d: uint64(103 + i * 4) + }); + } + } + + function copySourceToDest() public { + dest = source; + } + + function deleteSource() public { + delete source; + } + + function deleteDest() public { + delete dest; + } + + function getSourceAsUint() public view returns (uint64[20] memory result) { + for (uint i = 0; i < 5; i++) { + result[i * 4] = source[i].a; + result[i * 4 + 1] = source[i].b; + result[i * 4 + 2] = source[i].c; + result[i * 4 + 3] = source[i].d; + } + } + + function getDestAsUint() public view returns (uint64[40] memory result) { + for (uint i = 0; i < 10; i++) { + result[i * 4] = dest[i].a; + result[i * 4 + 1] = dest[i].b; + result[i * 4 + 2] = dest[i].c; + result[i * 4 + 3] = dest[i].d; + } + } +} +// ---- +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// getDestAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillSource() +// gas irOptimized: 135018 +// gas legacy: 146851 +// gas legacyOptimized: 137549 +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 +// fillDest() +// gas irOptimized: 248706 +// gas legacy: 272468 +// gas legacyOptimized: 253871 +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 +// getDestAsUint() -> 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139 +// copySourceToDest() +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 +// getDestAsUint() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// deleteSource() +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// getDestAsUint() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// deleteDest() +// canary() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// getSourceAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// getDestAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_and_partial_assignment_with_layout.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_and_partial_assignment_with_layout.sol new file mode 100644 index 000000000000..1e2637c851f9 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_and_partial_assignment_with_layout.sol @@ -0,0 +1,54 @@ +contract C layout at 2**256 - 5 { + uint256 a; + + function getArray() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := a.slot + } + } + + function fillArray() public { + uint256[10][1] storage _x = getArray(); + for (uint i = 1; i < 10; i++) + _x[0][i] = i; + } + + function partialAssignArrayBeforeStorageBoundary() public { + uint256[10][1] storage _x = getArray(); + _x[0] = [11, 12, 13]; + } + + function partialAssignArrayCrossStorageBoundary() public { + uint256[10][1] storage _x = getArray(); + _x[0] = [14, 15, 16, 17, 18, 19, 20]; + } + + function clearArray() public { + uint256[10][1] storage _x = getArray(); + delete _x[0]; + } + + function x() public view returns (uint256[10] memory) { + return getArray()[0]; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 220749 +// gas legacy: 221473 +// gas legacyOptimized: 220915 +// partialAssignArrayBeforeStorageBoundary() +// x() -> 11, 12, 13, 0, 0, 0, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 186549 +// gas legacy: 187273 +// gas legacyOptimized: 186715 +// x() -> 11, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// partialAssignArrayCrossStorageBoundary() +// x() -> 14, 15, 16, 17, 18, 19, 20, 0, 0, 0 +// clearArray() +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 44183 +// gas legacy: 46007 +// gas legacyOptimized: 43907 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_assignment.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_assignment.sol new file mode 100644 index 000000000000..0de66a71472f --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_assignment.sol @@ -0,0 +1,28 @@ +contract C { + function getArray() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function assignArray(uint256[10] memory y) public { + uint256[10][1] storage _x = getArray(); + _x[0] = y; + } + + function x() public view returns (uint256[10] memory) { + return getArray()[0]; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// assignArray(uint256[10]): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> +// gas irOptimized: 245236 +// gas legacy: 249351 +// gas legacyOptimized: 245365 +// x() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// assignArray(uint256[10]): 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 -> +// x() -> 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 +// gas irOptimized: 44183 +// gas legacy: 46012 +// gas legacyOptimized: 43907 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_copy.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_copy.sol new file mode 100644 index 000000000000..949edc4907eb --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_copy.sol @@ -0,0 +1,61 @@ +contract C { + constructor() { + uint256[10][1] storage _x = getX(); + _x[0] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + } + + function getX() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function getY() internal pure returns (uint256[10][1] storage _y) { + assembly { + _y.slot := 5 + } + } + + function copyXToY() public { + uint256[10][1] storage _x = getX(); + uint256[10][1] storage _y = getY(); + _y[0] = _x[0]; + } + + function clearX() public { + uint256[10][1] storage _x = getX(); + delete _x[0]; + } + + function copyYToX() public { + uint256[10][1] storage _x = getX(); + uint256[10][1] storage _y = getY(); + _x[0] = _y[0]; + } + + function x() public view returns (uint256[10] memory) { + return getX()[0]; + } + + function y() public view returns (uint256[10] memory) { + return getY()[0]; + } +} +// ---- +// x() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// y() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// copyXToY() +// gas irOptimized: 264224 +// gas legacy: 265434 +// gas legacyOptimized: 264247 +// x() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// y() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// clearX() +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// y() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// copyYToX() +// gas irOptimized: 266243 +// gas legacy: 267456 +// gas legacyOptimized: 266280 +// x() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// y() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_delete.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_delete.sol new file mode 100644 index 000000000000..20d2e2ec9df5 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_delete.sol @@ -0,0 +1,34 @@ +contract C { + function getArray() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function fillArray() public { + uint256[10][1] storage _x = getArray(); + for (uint i = 1; i < 10; i++) + _x[0][i] = i; + } + + function clearArray() public { + uint256[10][1] storage _x = getArray(); + delete _x[0]; + } + + function x() public view returns (uint256[10] memory) { + return getArray()[0]; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 220705 +// gas legacy: 221434 +// gas legacyOptimized: 220871 +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// clearArray() +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 44183 +// gas legacy: 46012 +// gas legacyOptimized: 43907 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_delete_overlapping_variable.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_delete_overlapping_variable.sol new file mode 100644 index 000000000000..3bb605cc919e --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_delete_overlapping_variable.sol @@ -0,0 +1,40 @@ +contract C { + uint256 public y = 42; + + function getArray() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function fillArray() public { + uint256[10][1] storage _x = getArray(); + for (uint i = 1; i < 10; i++) + _x[0][i] = i; + } + + function clearArray() public { + uint256[10][1] storage _x = getArray(); + delete _x[0]; + } + + function x() public view returns (uint256[10] memory) { + return getArray()[0]; + } +} + +// ---- +// y() -> 42 +// x() -> 0, 0, 0, 0, 0, 42, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 203627 +// gas legacy: 204356 +// gas legacyOptimized: 203793 +// y() -> 5 +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// clearArray() +// y() -> 0 +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 44183 +// gas legacy: 46012 +// gas legacyOptimized: 43907 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_packing_not_overlapping_variable.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_packing_not_overlapping_variable.sol new file mode 100644 index 000000000000..77a6158acc84 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_packing_not_overlapping_variable.sol @@ -0,0 +1,63 @@ +contract C { + struct Canary { + uint256 value; + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; // Should not be overwritten + } + + function getArray() internal pure returns (uint64[10][1] storage _x) { + // Array of 10 * uint64 values (8 bytes each) + // Packs 4 uint64 per slot -> 3 slots total (slots -1, 0, 1) + assembly { + _x.slot := sub(0, 1) + } + } + + function getCanary() internal pure returns (Canary storage canary) { + // Canary at slot 2, right after the array ends at slot 1 + assembly { + canary.slot := 2 + } + } + + function fillArray() public { + uint64[10][1] storage _x = getArray(); + for (uint64 i = 0; i < 10; i++) + _x[0][i] = i; + } + + function shrinkTo5() public { + uint64[10][1] storage _x = getArray(); + // Resize by assigning a smaller array + // This should clear items [5..9] without touching y + _x[0] = [uint64(11), 12, 13, 14, 15]; + } + + function clearArray() public { + uint64[10][1] storage _x = getArray(); + delete _x[0]; + } + + function x() public view returns (uint64[10] memory) { + return getArray()[0]; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// fillArray() +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// shrinkTo5() +// x() -> 11, 12, 13, 14, 15, 0, 0, 0, 0, 0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// clearArray() +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_array_partial_assignment.sol b/test/libsolidity/semanticTests/storage/storage_boundary_array_partial_assignment.sol new file mode 100644 index 000000000000..c4e3ee7794c0 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_array_partial_assignment.sol @@ -0,0 +1,41 @@ +contract C { + function getArray() internal pure returns (uint256[10][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function fillArray() public { + uint256[10][1] storage _x = getArray(); + for (uint i = 1; i < 10; i++) + _x[0][i] = i; + } + + function x() public view returns (uint256[10] memory) { + return getArray()[0]; + } + + function partialAssignArrayBeforeStorageBoundary() public { + uint256[10][1] storage _x = getArray(); + _x[0] = [21, 22, 23]; + } + + function partialAssignArrayCrossStorageBoundary() public { + uint256[10][1] storage _x = getArray(); + _x[0] = [11, 12, 13, 14, 15, 16, 17]; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 220727 +// gas legacy: 221456 +// gas legacyOptimized: 220893 +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +// partialAssignArrayCrossStorageBoundary() +// x() -> 11, 12, 13, 14, 15, 16, 17, 0, 0, 0 +// partialAssignArrayBeforeStorageBoundary() +// x() -> 21, 22, 23, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 44183 +// gas legacy: 46012 +// gas legacyOptimized: 43907 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_delete_overflow_bug.sol b/test/libsolidity/semanticTests/storage/storage_boundary_delete_overflow_bug.sol new file mode 100644 index 000000000000..3ffb7774710a --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_delete_overflow_bug.sol @@ -0,0 +1,80 @@ +contract C { + mapping(string => uint256[256][2**240]) m; + + function getSlot() internal view returns (uint256) { + uint256[256][2**240] storage _x = m["v 2.2.3"]; + uint256 slot; + assembly { + slot := _x.slot + } + assert(slot == 0xffdb3f1d9f54eb0b5012935c286c508459d381405d269e01c15f4ec2826edbbf); + return slot; + } + + function getIndex() internal view returns (uint256) { + uint256 slot = getSlot(); + // Pick the largest index such that `slot + 256 * index` <= `2**256 - 1` + uint256 index = (type(uint256).max - slot) / 256; + assert(index <= type(uint240).max); + assert((type(uint256).max - slot + 1) % 256 != 0); + + return index; + } + + function getArray() internal view returns (uint256[256][2**240] storage _x) { + uint256 s = getSlot(); + assembly { + _x.slot := s + } + } + + function fillArray() public { + uint256[256][2**240] storage _x = getArray(); + for (uint i = 1; i < 256; i++) + _x[getIndex()][i] = i; + } + + function partialAssignArray() public { + uint256[256][2**240] storage _x = getArray(); + _x[getIndex()] = [11, 22, 33, 44, 55, 66, 77, 88, 99]; + } + + function clearArray() public { + uint256[256][2**240] storage _x = getArray(); + delete _x[getIndex()]; + } + + function x() public view returns (uint256[256] memory) { + return getArray()[getIndex()]; + } +} + +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 604108 +// gas legacy: 644983 +// gas legacyOptimized: 598016 +// fillArray() +// gas irOptimized: 5782148 +// gas legacy: 6044562 +// gas legacyOptimized: 5853893 +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 +// gas irOptimized: 604108 +// gas legacy: 644983 +// gas legacyOptimized: 598016 +// partialAssignArray() +// gas irOptimized: 1067376 +// gas legacy: 1177356 +// gas legacyOptimized: 1068067 +// x() -> 11, 22, 33, 44, 55, 66, 77, 88, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 604108 +// gas legacy: 644983 +// gas legacyOptimized: 598016 +// clearArray() +// gas irOptimized: 580378 +// gas legacy: 582976 +// gas legacyOptimized: 581290 +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 604108 +// gas legacy: 644983 +// gas legacyOptimized: 598016 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_packed_array.sol b/test/libsolidity/semanticTests/storage/storage_boundary_packed_array.sol new file mode 100644 index 000000000000..aa30a2bcac49 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_packed_array.sol @@ -0,0 +1,35 @@ +contract C { + function getArray() internal pure returns (uint64[40][1] storage _x) { + assembly { + _x.slot := sub(0, 5) + } + } + + function fillArray() public { + uint64[40][1] storage _x = getArray(); + for (uint64 i = 1; i < 40; i++) + _x[0][i] = i; + } + + function clearArray() public { + uint64[40][1] storage _x = getArray(); + delete _x[0]; + } + + function x() public view returns (uint64[40] memory) { + return getArray()[0]; + } +} +// ---- +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillArray() +// gas irOptimized: 254227 +// gas legacy: 258712 +// gas legacyOptimized: 257258 +// x() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 +// clearArray() +// gas irOptimized: 57426 +// x() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 48087 +// gas legacy: 64080 +// gas legacyOptimized: 56602 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_mixed_types.sol b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_mixed_types.sol new file mode 100644 index 000000000000..213dee1dae23 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_mixed_types.sol @@ -0,0 +1,172 @@ +pragma abicoder v2; + +contract C { + struct S { + uint256 a; // slot 0 (bytes 0-31) + uint128 b; // slot 1 (bytes 0-15) + uint64 c; // slot 1 (bytes 16-23) + bytes32 d; // slot 2 (bytes 0-31) + bool e; // slot 3 (byte 0) + // Total: 4 slots per struct + } + + struct Canary { + uint256 value; + } + + function getBoundaryArray() internal pure returns (S[10][1] storage arr) { + // 10 structs * 4 slots = 40 slots total + // Starts at -20, ends at slot 19 + assembly { + arr.slot := sub(0, 20) + } + } + + function getDest() internal pure returns (S[10][1] storage arr) { + assembly { + arr.slot := 21 + } + } + + function getCanary() internal pure returns (Canary storage canary) { + // Array ends at slot 19, canary at slot 20 + assembly { + canary.slot := 20 + } + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; + } + + function fillBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + for (uint i = 0; i < 10; i++) { + arr[0][i] = S({ + a: 1 + i * 5, + b: uint128(2 + i * 5), + c: uint64(3 + i * 5), + d: bytes32(uint256(4 + i * 5)), + e: true + }); + } + } + + function deleteBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + delete arr[0]; + } + + function copyFromBoundary() public { + S[10][1] storage source = getBoundaryArray(); + S[10][1] storage dest = getDest(); + dest[0] = source[0]; + } + + function copyToBoundary() public { + S[10][1] storage source = getDest(); + S[10][1] storage dest = getBoundaryArray(); + dest[0] = source[0]; + } + + function fillDestArray() public { + S[10][1] storage dest = getDest(); + for (uint i = 0; i < 10; i++) { + dest[0][i] = S({ + a: 51 + i * 5, + b: uint128(52 + i * 5), + c: uint64(53 + i * 5), + d: bytes32(uint256(54 + i * 5)), + e: true + }); + } + } + + function boundaryArray() public view returns (S[10] memory) { + return getBoundaryArray()[0]; + } + + function destArray() public view returns (S[10] memory) { + return getDest()[0]; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } +} +// ---- +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 +// fillBoundaryArray() +// gas irOptimized: 912522 +// gas legacy: 930728 +// gas legacyOptimized: 916628 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, true, 6, 7, 8, 9, true, 11, 12, 13, 14, true, 16, 17, 18, 19, true, 21, 22, 23, 24, true, 26, 27, 28, 29, true, 31, 32, 33, 34, true, 36, 37, 38, 39, true, 41, 42, 43, 44, true, 46, 47, 48, 49, true +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 +// copyFromBoundary() +// gas irOptimized: 994579 +// gas legacy: 1023407 +// gas legacyOptimized: 994746 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, true, 6, 7, 8, 9, true, 11, 12, 13, 14, true, 16, 17, 18, 19, true, 21, 22, 23, 24, true, 26, 27, 28, 29, true, 31, 32, 33, 34, true, 36, 37, 38, 39, true, 41, 42, 43, 44, true, 46, 47, 48, 49, true +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 1, 2, 3, 4, true, 6, 7, 8, 9, true, 11, 12, 13, 14, true, 16, 17, 18, 19, true, 21, 22, 23, 24, true, 26, 27, 28, 29, true, 31, 32, 33, 34, true, 36, 37, 38, 39, true, 41, 42, 43, 44, true, 46, 47, 48, 49, true +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 +// fillDestArray() +// gas irOptimized: 200426 +// gas legacy: 218746 +// gas legacyOptimized: 204648 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, true, 6, 7, 8, 9, true, 11, 12, 13, 14, true, 16, 17, 18, 19, true, 21, 22, 23, 24, true, 26, 27, 28, 29, true, 31, 32, 33, 34, true, 36, 37, 38, 39, true, 41, 42, 43, 44, true, 46, 47, 48, 49, true +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 51, 52, 53, 54, true, 56, 57, 58, 59, true, 61, 62, 63, 64, true, 66, 67, 68, 69, true, 71, 72, 73, 74, true, 76, 77, 78, 79, true, 81, 82, 83, 84, true, 86, 87, 88, 89, true, 91, 92, 93, 94, true, 96, 97, 98, 99, true +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 +// copyToBoundary() +// gas irOptimized: 282623 +// gas legacy: 311362 +// gas legacyOptimized: 282712 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 51, 52, 53, 54, true, 56, 57, 58, 59, true, 61, 62, 63, 64, true, 66, 67, 68, 69, true, 71, 72, 73, 74, true, 76, 77, 78, 79, true, 81, 82, 83, 84, true, 86, 87, 88, 89, true, 91, 92, 93, 94, true, 96, 97, 98, 99, true +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 51, 52, 53, 54, true, 56, 57, 58, 59, true, 61, 62, 63, 64, true, 66, 67, 68, 69, true, 71, 72, 73, 74, true, 76, 77, 78, 79, true, 81, 82, 83, 84, true, 86, 87, 88, 89, true, 91, 92, 93, 94, true, 96, 97, 98, 99, true +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 +// deleteBoundaryArray() +// gas irOptimized: 177968 +// gas legacy: 180997 +// gas legacyOptimized: 178175 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// gas irOptimized: 113169 +// gas legacy: 120742 +// gas legacyOptimized: 112518 +// destArray() -> 51, 52, 53, 54, true, 56, 57, 58, 59, true, 61, 62, 63, 64, true, 66, 67, 68, 69, true, 71, 72, 73, 74, true, 76, 77, 78, 79, true, 81, 82, 83, 84, true, 86, 87, 88, 89, true, 91, 92, 93, 94, true, 96, 97, 98, 99, true +// gas irOptimized: 113078 +// gas legacy: 120738 +// gas legacyOptimized: 112505 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_multislot.sol b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_multislot.sol new file mode 100644 index 000000000000..55e36cd08e06 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_multislot.sol @@ -0,0 +1,128 @@ +pragma abicoder v2; + +contract C { + struct S { + uint256 a; + uint256 b; + uint256 c; + // 3 slots per struct + } + + struct Canary { + uint256 value; + } + + function getBoundaryArray() internal pure returns (S[10][1] storage arr) { + // 10 structs, each 3 slots = 30 slots total + assembly { + arr.slot := sub(0, 15) + } + } + + function getDest() internal pure returns (S[10][1] storage arr) { + assembly { + arr.slot := 16 + } + } + + function getCanary() internal pure returns (Canary storage canary) { + assembly { + canary.slot := 15 + } + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; + } + + function fillBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + for (uint i = 0; i < 10; i++) { + arr[0][i] = S({ + a: 1 + i * 3, + b: 2 + i * 3, + c: 3 + i * 3 + }); + } + } + + function deleteBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + delete arr[0]; + } + + function copyFromBoundary() public { + S[10][1] storage source = getBoundaryArray(); + S[10][1] storage dest = getDest(); + dest[0] = source[0]; + } + + function copyToBoundary() public { + S[10][1] storage source = getDest(); + S[10][1] storage dest = getBoundaryArray(); + dest[0] = source[0]; + } + + function fillDestArray() public { + S[10][1] storage dest = getDest(); + for (uint i = 0; i < 10; i++) { + dest[0][i] = S({ + a: 31 + i * 3, + b: 32 + i * 3, + c: 33 + i * 3 + }); + } + } + + function boundaryArray() public view returns (S[10] memory) { + return getBoundaryArray()[0]; + } + + function destArray() public view returns (S[10] memory) { + return getDest()[0]; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } +} +// ---- +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillBoundaryArray() +// gas irOptimized: 688719 +// gas legacy: 700075 +// gas legacyOptimized: 691605 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// copyFromBoundary() +// gas irOptimized: 748779 +// gas legacy: 767297 +// gas legacyOptimized: 748756 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 +// destArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 +// fillDestArray() +// gas irOptimized: 175623 +// gas legacy: 187093 +// gas legacyOptimized: 178625 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 +// destArray() -> 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 +// copyToBoundary() +// gas irOptimized: 235823 +// gas legacy: 254252 +// gas legacyOptimized: 235722 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 +// destArray() -> 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 +// deleteBoundaryArray() +// gas irOptimized: 137824 +// gas legacy: 137973 +// gas legacyOptimized: 137743 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// destArray() -> 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 diff --git a/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_packed.sol b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_packed.sol new file mode 100644 index 000000000000..5b4085acf8f9 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_boundary_struct_array_packed.sol @@ -0,0 +1,128 @@ +pragma abicoder v2; + +contract C { + struct S { + uint64 a; + uint64 b; + uint64 c; + uint64 d; + // All fit in one slot (4 * 64 = 256 bits) + } + + struct Canary { + uint256 value; + } + + function getBoundaryArray() internal pure returns (S[10][1] storage arr) { + // 10 structs, each 1 slot = 10 slots total + assembly { + arr.slot := sub(0, 5) + } + } + + function getDest() internal pure returns (S[10][1] storage arr) { + assembly { + arr.slot := 6 + } + } + + function getCanary() internal pure returns (Canary storage canary) { + // Array ends at slot 4, canary at slot 5 + assembly { + canary.slot := 5 + } + } + + constructor() { + Canary storage canary = getCanary(); + canary.value = type(uint256).max; + } + + function fillBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + for (uint i = 0; i < 10; i++) { + arr[0][i] = S({ + a: uint64(1 + i * 4), + b: uint64(2 + i * 4), + c: uint64(3 + i * 4), + d: uint64(4 + i * 4) + }); + } + } + + function deleteBoundaryArray() public { + S[10][1] storage arr = getBoundaryArray(); + delete arr[0]; + } + + function copyFromBoundary() public { + S[10][1] storage source = getBoundaryArray(); + S[10][1] storage dest = getDest(); + dest[0] = source[0]; + } + + function copyToBoundary() public { + S[10][1] storage source = getDest(); + S[10][1] storage dest = getBoundaryArray(); + dest[0] = source[0]; + } + + function fillDestArray() public { + S[10][1] storage dest = getDest(); + for (uint i = 0; i < 10; i++) { + dest[0][i] = S({ + a: uint64(41 + i * 4), + b: uint64(42 + i * 4), + c: uint64(43 + i * 4), + d: uint64(44 + i * 4) + }); + } + } + + function boundaryArray() public view returns (S[10] memory) { + return getBoundaryArray()[0]; + } + + function destArray() public view returns (S[10] memory) { + return getDest()[0]; + } + + function canaryValue() public view returns (uint256) { + return getCanary().value; + } +} +// ---- +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// fillBoundaryArray() +// gas irOptimized: 248990 +// gas legacy: 272856 +// gas legacyOptimized: 253856 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 +// destArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// copyFromBoundary() +// gas irOptimized: 274279 +// gas legacy: 298927 +// gas legacyOptimized: 272256 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 +// destArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 +// fillDestArray() +// gas legacy: 101874 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 +// destArray() -> 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 +// gas legacy: 59729 +// copyToBoundary() +// gas irOptimized: 103323 +// gas legacy: 127882 +// gas legacyOptimized: 101222 +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 +// destArray() -> 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 +// deleteBoundaryArray() +// canaryValue() -> 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// boundaryArray() -> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +// destArray() -> 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 diff --git a/test/libsolidity/semanticTests/storage/storage_packed_array_copy.sol b/test/libsolidity/semanticTests/storage/storage_packed_array_copy.sol new file mode 100644 index 000000000000..d4edc5aecd53 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/storage_packed_array_copy.sol @@ -0,0 +1,35 @@ +contract C { + bytes8[9] _x; // 4 per slot + bytes17[10] _y; // 1 per slot, no offset counter + + constructor() { + for (uint256 i = 0; i < _x.length; ++i) _x[i] = bytes8(uint64(i)); + _y[8] = _y[9] = bytes8(uint64(2)); + } + + function getXAsUint() public view returns (uint64[9] memory result) { + for (uint i = 0; i < 9; i++) { + result[i] = uint64(_x[i]); + } + } + + function getYAsUint() public view returns (uint64[10] memory result) { + for (uint i = 0; i < 10; i++) { + result[i] = uint64(bytes8(_y[i])); + } + } + + function copy() public { + _y = _x; + } +} + +// ---- +// getXAsUint() -> 0, 1, 2, 3, 4, 5, 6, 7, 8 +// getYAsUint() -> 0, 0, 0, 0, 0, 0, 0, 0, 2, 2 +// copy() +// gas irOptimized: 190810 +// gas legacy: 195580 +// gas legacyOptimized: 190906 +// getXAsUint() -> 0, 1, 2, 3, 4, 5, 6, 7, 8 +// getYAsUint() -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 0 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_dynamic_array.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_dynamic_array.sol index 1737c35bb87c..bcad9609ee07 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_dynamic_array.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_dynamic_array.sol @@ -41,9 +41,9 @@ contract C is A layout at 42 { // arrayALength() -> 3 // arrayCLength() -> 0 // initCFromAInReverse() -> 3, 2, 1 -// gas irOptimized: 121276 -// gas legacy: 121213 -// gas legacyOptimized: 120843 +// gas irOptimized: 121281 +// gas legacy: 121937 +// gas legacyOptimized: 120853 // clearA() -> // arrayC(uint256): 0 -> 3 // arrayALength() -> 0 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_mapping.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_mapping.sol index e33df29ea604..47c4786c841d 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_mapping.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/state_variable_mapping.sol @@ -27,7 +27,7 @@ contract C layout at 42 is A { // setup() -> // gas irOptimized: 159082 // gas legacy: 161738 -// gas legacyOptimized: 160222 +// gas legacyOptimized: 160218 // open(uint256): 3 -> 0x20, 5, "Empty" // open(uint256): 2 -> 0x20, 6, "Locked" // open(uint256): 1 -> 0x20, 7, "Monster" diff --git a/test/libsolidity/semanticTests/structs/copy_from_mapping.sol b/test/libsolidity/semanticTests/structs/copy_from_mapping.sol index 037ff20679f0..fccdf0030ffb 100644 --- a/test/libsolidity/semanticTests/structs/copy_from_mapping.sol +++ b/test/libsolidity/semanticTests/structs/copy_from_mapping.sol @@ -37,7 +37,7 @@ contract C { // ---- // to_state() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 121282 -// gas legacy: 122977 -// gas legacyOptimized: 121652 +// gas legacy: 125480 +// gas legacyOptimized: 121695 // to_storage() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // to_memory() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 diff --git a/test/libsolidity/semanticTests/structs/copy_struct_array_from_storage.sol b/test/libsolidity/semanticTests/structs/copy_struct_array_from_storage.sol index 600eb35ab2ee..50babde562ce 100644 --- a/test/libsolidity/semanticTests/structs/copy_struct_array_from_storage.sol +++ b/test/libsolidity/semanticTests/structs/copy_struct_array_from_storage.sol @@ -88,8 +88,8 @@ contract Test { // ---- // test1() -> true // gas irOptimized: 152965 -// gas legacy: 153010 -// gas legacyOptimized: 152636 +// gas legacy: 155952 +// gas legacyOptimized: 152864 // test2() -> true // test3() -> true // test4() -> true diff --git a/test/libsolidity/semanticTests/structs/copy_substructures_from_mapping.sol b/test/libsolidity/semanticTests/structs/copy_substructures_from_mapping.sol index 7188e1befe7a..9f0b7cbe64c9 100644 --- a/test/libsolidity/semanticTests/structs/copy_substructures_from_mapping.sol +++ b/test/libsolidity/semanticTests/structs/copy_substructures_from_mapping.sol @@ -45,7 +45,7 @@ contract C { // ---- // to_state() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 121454 -// gas legacy: 123114 -// gas legacyOptimized: 121659 +// gas legacy: 125617 +// gas legacyOptimized: 121699 // to_storage() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // to_memory() -> 0x20, 0x60, 0xa0, 7, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 diff --git a/test/libsolidity/semanticTests/structs/copy_substructures_to_mapping.sol b/test/libsolidity/semanticTests/structs/copy_substructures_to_mapping.sol index 54b659ba0757..3e86495713be 100644 --- a/test/libsolidity/semanticTests/structs/copy_substructures_to_mapping.sol +++ b/test/libsolidity/semanticTests/structs/copy_substructures_to_mapping.sol @@ -53,13 +53,13 @@ contract C { // ---- // from_memory() -> 0x20, 0x60, 0xa0, 0x15, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 122720 -// gas legacy: 130131 -// gas legacyOptimized: 128648 +// gas legacy: 125558 +// gas legacyOptimized: 123322 // from_state() -> 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 121424 -// gas legacy: 123190 -// gas legacyOptimized: 121758 +// gas legacy: 125693 +// gas legacyOptimized: 121804 // from_calldata((bytes,uint16[],uint16)): 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 -> 0x20, 0x60, 0xa0, 0x15, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 114852 -// gas legacy: 122423 -// gas legacyOptimized: 120698 +// gas legacy: 117950 +// gas legacyOptimized: 115526 diff --git a/test/libsolidity/semanticTests/structs/copy_to_mapping.sol b/test/libsolidity/semanticTests/structs/copy_to_mapping.sol index d020998b47c5..b31d0e580444 100644 --- a/test/libsolidity/semanticTests/structs/copy_to_mapping.sol +++ b/test/libsolidity/semanticTests/structs/copy_to_mapping.sol @@ -46,16 +46,16 @@ contract C { // ---- // from_state() -> 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 121515 -// gas legacy: 123051 -// gas legacyOptimized: 121704 +// gas legacy: 125554 +// gas legacyOptimized: 121747 // from_storage() -> 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 121559 -// gas legacy: 123109 -// gas legacyOptimized: 121756 +// gas legacy: 125612 +// gas legacyOptimized: 121799 // from_memory() -> 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 122740 -// gas legacy: 129996 -// gas legacyOptimized: 128644 +// gas legacy: 125423 +// gas legacyOptimized: 123323 // from_calldata((bytes,uint16[],uint16)): 0x20, 0x60, 0xa0, 21, 3, 0x666F6F0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 -> 0x20, 0x60, 0xa0, 21, 3, 0x666f6f0000000000000000000000000000000000000000000000000000000000, 2, 13, 14 // gas irOptimized: 114824 // gas legacy: 118207 diff --git a/test/libsolidity/semanticTests/structs/struct_containing_bytes_copy_and_delete.sol b/test/libsolidity/semanticTests/structs/struct_containing_bytes_copy_and_delete.sol index e50265e5175d..3912a4655505 100644 --- a/test/libsolidity/semanticTests/structs/struct_containing_bytes_copy_and_delete.sol +++ b/test/libsolidity/semanticTests/structs/struct_containing_bytes_copy_and_delete.sol @@ -25,7 +25,7 @@ contract c { // set(uint256,bytes,uint256): 12, 0x60, 13, 33, "12345678901234567890123456789012", "3" -> true // gas irOptimized: 133557 // gas legacy: 134624 -// gas legacyOptimized: 133856 +// gas legacyOptimized: 133857 // test(uint256): 32 -> "3" // storageEmpty -> 0 // copy() -> true @@ -33,7 +33,7 @@ contract c { // set(uint256,bytes,uint256): 12, 0x60, 13, 33, "12345678901234567890123456789012", "3" -> true // gas irOptimized: 133557 // gas legacy: 134624 -// gas legacyOptimized: 133856 +// gas legacyOptimized: 133857 // storageEmpty -> 0 // del() -> true // storageEmpty -> 1 diff --git a/test/libsolidity/semanticTests/structs/struct_delete_storage_nested_small.sol b/test/libsolidity/semanticTests/structs/struct_delete_storage_nested_small.sol index 57bf38e52c6b..12cbf0799e04 100644 --- a/test/libsolidity/semanticTests/structs/struct_delete_storage_nested_small.sol +++ b/test/libsolidity/semanticTests/structs/struct_delete_storage_nested_small.sol @@ -33,4 +33,4 @@ contract C { // compileViaYul: true // ---- // f() -> 0, 0, 0 -// gas irOptimized: 117101 +// gas irOptimized: 93835 diff --git a/test/libsolidity/semanticTests/structs/struct_delete_storage_with_array.sol b/test/libsolidity/semanticTests/structs/struct_delete_storage_with_array.sol index cea1105669aa..cd49ea27ad0e 100644 --- a/test/libsolidity/semanticTests/structs/struct_delete_storage_with_array.sol +++ b/test/libsolidity/semanticTests/structs/struct_delete_storage_with_array.sol @@ -42,10 +42,10 @@ contract C { } // ---- // f() -> -// gas irOptimized: 113465 -// gas legacy: 113591 -// gas legacyOptimized: 113098 +// gas irOptimized: 113388 +// gas legacy: 113588 +// gas legacyOptimized: 113104 // g() -> -// gas irOptimized: 118828 -// gas legacy: 118764 -// gas legacyOptimized: 118168 +// gas irOptimized: 118768 +// gas legacy: 118766 +// gas legacyOptimized: 118188 diff --git a/test/libsolidity/semanticTests/structs/struct_delete_storage_with_arrays_small.sol b/test/libsolidity/semanticTests/structs/struct_delete_storage_with_arrays_small.sol index 2345fea3cfe2..1a047049f205 100644 --- a/test/libsolidity/semanticTests/structs/struct_delete_storage_with_arrays_small.sol +++ b/test/libsolidity/semanticTests/structs/struct_delete_storage_with_arrays_small.sol @@ -27,4 +27,4 @@ contract C { // compileViaYul: true // ---- // f() -> 0 -// gas irOptimized: 111570 +// gas irOptimized: 89468 diff --git a/test/libsolidity/semanticTests/types/mapping/copy_from_mapping_to_mapping.sol b/test/libsolidity/semanticTests/types/mapping/copy_from_mapping_to_mapping.sol index 26e8294e3ac9..47fa27fe3c28 100644 --- a/test/libsolidity/semanticTests/types/mapping/copy_from_mapping_to_mapping.sol +++ b/test/libsolidity/semanticTests/types/mapping/copy_from_mapping_to_mapping.sol @@ -30,5 +30,5 @@ contract C { // ---- // f() -> 0x20, 7, 8, 9, 0xa0, 13, 2, 0x40, 0xa0, 2, 3, 4, 2, 3, 4 // gas irOptimized: 197102 -// gas legacy: 199887 -// gas legacyOptimized: 196845 +// gas legacy: 205706 +// gas legacyOptimized: 196739 diff --git a/test/libsolidity/semanticTests/userDefinedValueType/calldata.sol b/test/libsolidity/semanticTests/userDefinedValueType/calldata.sol index 015d51c1f54b..03c6e11bcc3c 100644 --- a/test/libsolidity/semanticTests/userDefinedValueType/calldata.sol +++ b/test/libsolidity/semanticTests/userDefinedValueType/calldata.sol @@ -49,13 +49,13 @@ contract C { } // ---- // test_f() -> true -// gas irOptimized: 122201 -// gas legacy: 125333 -// gas legacyOptimized: 122693 +// gas irOptimized: 122114 +// gas legacy: 126162 +// gas legacyOptimized: 122757 // test_g() -> true -// gas irOptimized: 106408 -// gas legacy: 111133 -// gas legacyOptimized: 106925 +// gas irOptimized: 106248 +// gas legacy: 111824 +// gas legacyOptimized: 106606 // addresses(uint256): 0 -> 0x18 // addresses(uint256): 1 -> 0x19 // addresses(uint256): 3 -> 0x1b diff --git a/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol b/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol index c3d58811ae4e..0815a2da7ace 100644 --- a/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol +++ b/test/libsolidity/semanticTests/userDefinedValueType/calldata_to_storage.sol @@ -23,19 +23,19 @@ contract C { // ---- // s() -> 0, 0, 0x00, 0 // f((uint8,uint16,bytes2,uint8)): 1, 0xff, "ab", 15 -> -// gas irOptimized: 44405 -// gas legacy: 47200 -// gas legacyOptimized: 44923 +// gas irOptimized: 44237 +// gas legacy: 47154 +// gas legacyOptimized: 44982 // s() -> 1, 0xff, 0x6162000000000000000000000000000000000000000000000000000000000000, 15 // g(uint16[]): 0x20, 3, 1, 2, 3 -> 0x20, 3, 1, 2, 3 -// gas irOptimized: 69097 -// gas legacy: 75466 -// gas legacyOptimized: 74255 +// gas irOptimized: 68578 +// gas legacy: 71400 +// gas legacyOptimized: 69139 // small(uint256): 0 -> 1 // small(uint256): 1 -> 2 // h(bytes2[]): 0x20, 3, "ab", "cd", "ef" -> 0x20, 3, "ab", "cd", "ef" -// gas irOptimized: 69174 -// gas legacy: 75156 -// gas legacyOptimized: 74342 +// gas irOptimized: 68635 +// gas legacy: 71231 +// gas legacyOptimized: 69289 // l(uint256): 0 -> 0x6162000000000000000000000000000000000000000000000000000000000000 // l(uint256): 1 -> 0x6364000000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/userDefinedValueType/memory_to_storage.sol b/test/libsolidity/semanticTests/userDefinedValueType/memory_to_storage.sol index db745dce31ec..40005a035735 100644 --- a/test/libsolidity/semanticTests/userDefinedValueType/memory_to_storage.sol +++ b/test/libsolidity/semanticTests/userDefinedValueType/memory_to_storage.sol @@ -28,14 +28,14 @@ contract C { // gas legacyOptimized: 44671 // s() -> 1, 0xff, 0x6162000000000000000000000000000000000000000000000000000000000000, 15 // g(uint16[]): 0x20, 3, 1, 2, 3 -> 0x20, 3, 1, 2, 3 -// gas irOptimized: 69555 -// gas legacy: 76557 -// gas legacyOptimized: 74834 +// gas irOptimized: 68898 +// gas legacy: 72336 +// gas legacyOptimized: 69607 // small(uint256): 0 -> 1 // small(uint256): 1 -> 2 // h(bytes2[]): 0x20, 3, "ab", "cd", "ef" -> 0x20, 3, "ab", "cd", "ef" -// gas irOptimized: 69617 -// gas legacy: 76238 -// gas legacyOptimized: 74921 +// gas irOptimized: 68937 +// gas legacy: 72167 +// gas legacyOptimized: 69802 // l(uint256): 0 -> 0x6162000000000000000000000000000000000000000000000000000000000000 // l(uint256): 1 -> 0x6364000000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/various/address_code.sol b/test/libsolidity/semanticTests/various/address_code.sol index 4d810f970987..eda17b3c2775 100644 --- a/test/libsolidity/semanticTests/various/address_code.sol +++ b/test/libsolidity/semanticTests/various/address_code.sol @@ -16,11 +16,11 @@ contract C { // bytecodeFormat: legacy // ---- // constructor() -> -// gas irOptimized: 70760 +// gas irOptimized: 70924 // gas irOptimized code: 94600 -// gas legacy: 82428 +// gas legacy: 82688 // gas legacy code: 153800 -// gas legacyOptimized: 69400 +// gas legacyOptimized: 69675 // gas legacyOptimized code: 79200 // initCode() -> 0x20, 0 // f() -> true diff --git a/test/libsolidity/semanticTests/various/create_calldata.sol b/test/libsolidity/semanticTests/various/create_calldata.sol index 7e3e326228a7..c4d17d4299ca 100644 --- a/test/libsolidity/semanticTests/various/create_calldata.sol +++ b/test/libsolidity/semanticTests/various/create_calldata.sol @@ -10,10 +10,10 @@ contract C { // bytecodeFormat: legacy // ---- // constructor(): 42 -> -// gas irOptimized: 68239 +// gas irOptimized: 68387 // gas irOptimized code: 69000 -// gas legacy: 78076 +// gas legacy: 78348 // gas legacy code: 90200 -// gas legacyOptimized: 68321 +// gas legacyOptimized: 68548 // gas legacyOptimized code: 64600 // s() -> 0x20, 0 diff --git a/test/libsolidity/semanticTests/various/destructuring_assignment.sol b/test/libsolidity/semanticTests/various/destructuring_assignment.sol index a961870ee5f2..44fcd0f6aaa2 100644 --- a/test/libsolidity/semanticTests/various/destructuring_assignment.sol +++ b/test/libsolidity/semanticTests/various/destructuring_assignment.sol @@ -33,6 +33,6 @@ contract C { } // ---- // f(bytes): 0x20, 0x5, "abcde" -> 0 -// gas irOptimized: 242027 -// gas legacy: 243281 -// gas legacyOptimized: 242392 +// gas irOptimized: 242037 +// gas legacy: 246685 +// gas legacyOptimized: 242396 diff --git a/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol b/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol index 8430da4e8d12..1af8a04195a4 100644 --- a/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol +++ b/test/libsolidity/semanticTests/various/skip_dynamic_types_for_structs.sol @@ -20,5 +20,5 @@ contract C { // ---- // g() -> 2, 6 // gas irOptimized: 178195 -// gas legacy: 180653 -// gas legacyOptimized: 179144 +// gas legacy: 181357 +// gas legacyOptimized: 179055 diff --git a/test/libsolidity/syntaxTests/array/copy_calldata_struct_array_to_storage_legacy.sol b/test/libsolidity/syntaxTests/array/copy_calldata_struct_array_to_storage_legacy.sol new file mode 100644 index 000000000000..8c45b5b9f520 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/copy_calldata_struct_array_to_storage_legacy.sol @@ -0,0 +1,16 @@ +contract C { + struct S { + uint256 a; + uint256 b; + } + + S[] storageArray; + + function copyFromCalldata(S[] calldata calldataArray) public { + storageArray = calldataArray; + } +} +// ==== +// compileViaYul: false +// ---- +// UnimplementedFeatureError 1834: (0-208): Copying of type struct C.S calldata[] calldata to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON). diff --git a/test/libsolidity/syntaxTests/array/copy_memory_struct_array_to_storage_legacy.sol b/test/libsolidity/syntaxTests/array/copy_memory_struct_array_to_storage_legacy.sol new file mode 100644 index 000000000000..86e8bf1f6344 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/copy_memory_struct_array_to_storage_legacy.sol @@ -0,0 +1,17 @@ +contract C { + struct S { + uint256 a; + uint256 b; + } + + S[] storageArray; + + function copyFromMemory() public { + S[] memory memArray = new S[](3); + storageArray = memArray; + } +} +// ==== +// compileViaYul: false +// ---- +// UnimplementedFeatureError 1834: (0-217): Copying of type struct C.S memory[] memory to storage is not supported in legacy (only supported by the IR pipeline). Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON). From 3e46de97dce0b25cd2bf9989fd8f28f643cacadd Mon Sep 17 00:00:00 2001 From: Nikola Matic Date: Thu, 12 Feb 2026 15:20:02 +0100 Subject: [PATCH 04/41] Test cases (cherry picked from commit d38b746194f647f964a55d1c7072c2f3b8217d51) --- .../args | 1 + .../input.sol | 15 ++ .../output | 214 ++++++++++++++++++ ...fter_inherited_storage_same_value_type.sol | 26 +++ ...storage_mapping_delete_same_value_type.sol | 23 ++ ..._storage_struct_delete_same_value_type.sol | 28 +++ ...fore_inherited_storage_same_value_type.sol | 26 +++ ...storage_mapping_delete_same_value_type.sol | 23 ++ ..._storage_struct_delete_same_value_type.sol | 28 +++ ...storage_bool_array_transient_uint256_1.sol | 26 +++ ...storage_bool_array_transient_uint256_2.sol | 26 +++ .../delete_storage_u256_transient_u256_1.sol | 22 ++ .../delete_storage_u256_transient_u256_2.sol | 22 ++ 13 files changed, 480 insertions(+) create mode 100644 test/cmdlineTests/storage_transient_storage_collision_ir_output/args create mode 100644 test/cmdlineTests/storage_transient_storage_collision_ir_output/input.sol create mode 100644 test/cmdlineTests/storage_transient_storage_collision_ir_output/output create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol create mode 100644 test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/args b/test/cmdlineTests/storage_transient_storage_collision_ir_output/args new file mode 100644 index 000000000000..b7ec904f1024 --- /dev/null +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/args @@ -0,0 +1 @@ +--ir-optimized --evm-version cancun --debug-info none diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/input.sol b/test/cmdlineTests/storage_transient_storage_collision_ir_output/input.sol new file mode 100644 index 000000000000..e7d3930152fa --- /dev/null +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/input.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +pragma solidity >=0.0; + +contract C { + uint256 transient varTransient; + uint256 public varStorage = 0xeeeeeeeeee; + + function foo() external returns (uint256) { + varTransient = 0xffffffff; + delete varTransient; + delete varStorage; + + return varStorage; + } +} diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/output b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output new file mode 100644 index 000000000000..e3a251b2d8b0 --- /dev/null +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output @@ -0,0 +1,214 @@ +Optimized IR: +/// @use-src 0:"input.sol" +object "C_25" { + code { + { + mstore(64, memoryguard(0x80)) + if callvalue() + { + revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + } + constructor_C() + let _1 := allocate_unbounded() + codecopy(_1, dataoffset("C_25_deployed"), datasize("C_25_deployed")) + return(_1, datasize("C_25_deployed")) + } + function allocate_unbounded() -> memPtr + { memPtr := mload(64) } + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + { revert(0, 0) } + function shift_left(value) -> newValue + { newValue := shl(0, value) } + function update_byte_slice_shift(value, toInsert) -> result + { + let mask := not(0) + toInsert := shift_left(toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + function cleanup_rational_by(value) -> cleaned + { cleaned := value } + function cleanup_uint256(value) -> cleaned + { cleaned := value } + function identity(value) -> ret + { ret := value } + function convert_rational_by_to_uint256(value) -> converted + { + converted := cleanup_uint256(identity(cleanup_rational_by(value))) + } + function prepare_store_uint256(value) -> ret + { ret := value } + function update_storage_value_offset_rational_by_to_uint256(slot, value) + { + let convertedValue := convert_rational_by_to_uint256(value) + sstore(slot, update_byte_slice_shift(sload(slot), prepare_store_uint256(convertedValue))) + } + function constructor_C() + { + let expr := 0xeeeeeeeeee + update_storage_value_offset_rational_by_to_uint256(0x00, expr) + } + } + /// @use-src 0:"input.sol" + object "C_25_deployed" { + code { + { + mstore(64, memoryguard(0x80)) + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_unsigned(calldataload(0)) + switch selector + case 0xc2985578 { external_fun_foo() } + case 0xff2558ff { external_fun_varStorage() } + default { } + } + revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + } + function shift_right_unsigned(value) -> newValue + { newValue := shr(224, value) } + function allocate_unbounded() -> memPtr + { memPtr := mload(64) } + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + { revert(0, 0) } + function revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b() + { revert(0, 0) } + function abi_decode(headStart, dataEnd) + { + if slt(sub(dataEnd, headStart), 0) + { + revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b() + } + } + function cleanup_uint256(value) -> cleaned + { cleaned := value } + function abi_encode_uint256_to_uint256(value, pos) + { + mstore(pos, cleanup_uint256(value)) + } + function abi_encode_uint256(headStart, value0) -> tail + { + tail := add(headStart, 32) + abi_encode_uint256_to_uint256(value0, add(headStart, 0)) + } + function external_fun_foo() + { + if callvalue() + { + revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + } + abi_decode(4, calldatasize()) + let ret := fun_foo() + let memPos := allocate_unbounded() + let memEnd := abi_encode_uint256(memPos, ret) + return(memPos, sub(memEnd, memPos)) + } + function shift_right_unsigned_dynamic(bits, value) -> newValue + { newValue := shr(bits, value) } + function cleanup_from_storage_uint256(value) -> cleaned + { cleaned := value } + function extract_from_storage_value_dynamict_uint256(slot_value, offset) -> value + { + value := cleanup_from_storage_uint256(shift_right_unsigned_dynamic(mul(offset, 8), slot_value)) + } + function read_from_storage_split_dynamic_uint256(slot, offset) -> value + { + value := extract_from_storage_value_dynamict_uint256(sload(slot), offset) + } + function getter_fun_varStorage() -> ret + { + let slot := 0 + let offset := 0 + ret := read_from_storage_split_dynamic_uint256(slot, offset) + } + function external_fun_varStorage() + { + if callvalue() + { + revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + } + abi_decode(4, calldatasize()) + let ret := getter_fun_varStorage() + let memPos := allocate_unbounded() + let memEnd := abi_encode_uint256(memPos, ret) + return(memPos, sub(memEnd, memPos)) + } + function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + { revert(0, 0) } + function zero_value_for_split_uint256() -> ret + { ret := 0 } + function cleanup_rational_by(value) -> cleaned + { cleaned := value } + function identity(value) -> ret + { ret := value } + function convert_rational_by_to_uint256(value) -> converted + { + converted := cleanup_uint256(identity(cleanup_rational_by(value))) + } + function shift_left(value) -> newValue + { newValue := shl(0, value) } + function update_byte_slice_shift(value, toInsert) -> result + { + let mask := not(0) + toInsert := shift_left(toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + function convert_uint256_to_uint256(value) -> converted + { + converted := cleanup_uint256(identity(cleanup_uint256(value))) + } + function prepare_store_uint256(value) -> ret + { ret := value } + function update_transient_storage_value_offset_uint256_to_uint256(slot, value) + { + let convertedValue := convert_uint256_to_uint256(value) + tstore(slot, update_byte_slice_shift(tload(slot), prepare_store_uint256(convertedValue))) + } + function shift_left_dynamic(bits, value) -> newValue + { newValue := shl(bits, value) } + function update_byte_slice_dynamic32(value, shiftBytes, toInsert) -> result + { + let shiftBits := mul(shiftBytes, 8) + let mask := shift_left_dynamic(shiftBits, not(0)) + toInsert := shift_left_dynamic(shiftBits, toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + function update_transient_storage_value_uint256_to_uint256(slot, offset, value) + { + let convertedValue := convert_uint256_to_uint256(value) + tstore(slot, update_byte_slice_dynamic32(tload(slot), offset, prepare_store_uint256(convertedValue))) + } + function storage_set_to_zero_uint256(slot, offset) + { + let zero := zero_value_for_split_uint256() + update_transient_storage_value_uint256_to_uint256(slot, offset, zero) + } + function shift_right_0_unsigned(value) -> newValue + { newValue := shr(0, value) } + function extract_from_storage_value_offset_uint256(slot_value) -> value + { + value := cleanup_from_storage_uint256(shift_right_0_unsigned(slot_value)) + } + function read_from_storage_split_offset_uint256(slot) -> value + { + value := extract_from_storage_value_offset_uint256(sload(slot)) + } + function fun_foo() -> var + { + let zero_uint256 := zero_value_for_split_uint256() + var := zero_uint256 + let expr := 0xffffffff + let _1 := convert_rational_by_to_uint256(expr) + update_transient_storage_value_offset_uint256_to_uint256(0x00, _1) + storage_set_to_zero_uint256(0x00, 0) + storage_set_to_zero_uint256(0x00, 0) + let _2 := read_from_storage_split_offset_uint256(0x00) + let expr_1 := _2 + var := expr_1 + leave + } + } + data ".metadata" hex"" + } +} diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol new file mode 100644 index 000000000000..f4badbc98845 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol @@ -0,0 +1,26 @@ +contract Base { + uint256 public x; + + function deleteX() internal { + delete x; + } +} + +contract C is Base { + uint256 transient t; + + function setAndClear() external { + x = 1; + t = 2; + deleteX(); + delete t; + assert(t == 0); + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndClear() -> +// x() -> 1 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol new file mode 100644 index 000000000000..b1a38832158d --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol @@ -0,0 +1,23 @@ +contract C { + mapping(uint256 => uint256) m; + uint256 transient t; + + function setAndClear() external { + m[0] = 1; + t = 2; + delete m[0]; + delete t; + assert(t == 2); + } + + function getM() external view returns (uint256) { + return m[0]; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndClear() -> +// getM() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol new file mode 100644 index 000000000000..33a0dc2de46b --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol @@ -0,0 +1,28 @@ +contract C { + struct S { + uint256 a; + address b; + } + + S s; + uint256 transient t; + + function setAndDelete() external { + s.a = 1; + s.b = address(0x1234); + t = 2; + delete s; + delete t; + assert(t == 2); + } + + function getS() external view returns (uint256, address) { + return (s.a, s.b); + } +} +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndDelete() -> +// getS() -> 0, 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol new file mode 100644 index 000000000000..8e40422a9019 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol @@ -0,0 +1,26 @@ +contract Base { + uint256 public x; + + function deleteX() internal { + delete x; + } +} + +contract C is Base { + uint256 transient t; + + function setAndClear() external { + x = 1; + t = 2; + delete t; + assert(t == 0); + deleteX(); + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndClear() -> +// x() -> 1 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol new file mode 100644 index 000000000000..129fafad7507 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol @@ -0,0 +1,23 @@ +contract C { + mapping(uint256 => uint256) m; + uint256 transient t; + + function setAndClear() external { + m[0] = 1; + t = 2; + delete t; + assert(t == 0); + delete m[0]; + } + + function getM() external view returns (uint256) { + return m[0]; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndClear() -> +// getM() -> 1 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol new file mode 100644 index 000000000000..d499d0618374 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol @@ -0,0 +1,28 @@ +contract C { + struct S { + uint256 a; + address b; + } + + S s; + uint256 transient t; + + function setAndDelete() external { + s.a = 1; + s.b = address(0x1234); + t = 2; + delete t; + assert(t == 0); + delete s; + } + + function getS() external view returns (uint256, address) { + return (s.a, s.b); + } +} +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// setAndDelete() -> +// getS() -> 1, 0 diff --git a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol b/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol new file mode 100644 index 000000000000..4d84b23f70e7 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol @@ -0,0 +1,26 @@ +pragma solidity >=0.0; + +contract C { + bool[3] flags = [true, true, true]; + uint256 transient temp; + + function f() external { + temp = 0xffffffff; + assert(temp == 0xffffffff); + delete temp; + delete flags; + } + + function getFlags() external returns(bool[3] memory) + { + return flags; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// getFlags() -> true, true, true +// f() -> +// getFlags() -> true, true, true diff --git a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol b/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol new file mode 100644 index 000000000000..f95b191c108c --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol @@ -0,0 +1,26 @@ +pragma solidity >=0.0; + +contract C { + bool[3] flags = [true, true, true]; + uint256 transient temp; + + function f() external { + temp = 0xffffffff; + assert(temp == 0xffffffff); + delete flags; + delete temp; + } + + function getFlags() external returns(bool[3] memory) + { + return flags; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// getFlags() -> true, true, true +// f() -> +// getFlags() -> false, false, false diff --git a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol b/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol new file mode 100644 index 000000000000..08f8120be0ce --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +pragma solidity >=0.0; + +contract C { + uint256 transient varTransient; + uint256 public varStorage = 0xeeeeeeeeee; + + function foo() external { + varTransient = 0xffffffff; + assert(varTransient == 0xffffffff); + delete varTransient; + delete varStorage; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// varStorage() -> 0xeeeeeeeeee +// foo() -> +// varStorage() -> 0xeeeeeeeeee diff --git a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol b/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol new file mode 100644 index 000000000000..03e06747af1a --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +pragma solidity >=0.0; + +contract C { + uint256 transient varTransient; + uint256 public varStorage = 0xeeeeeeeeee; + + function foo() external { + varTransient = 0xffffffff; + assert(varTransient == 0xffffffff); + delete varStorage; + delete varTransient; + } +} + +// ==== +// EVMVersion: >=cancun +// compileViaYul: true +// ---- +// varStorage() -> 0xeeeeeeeeee +// foo() -> +// varStorage() -> 0 From 06cf704ae433a0dcda6a570a844f0e3bdcebdb1e Mon Sep 17 00:00:00 2001 From: Nikola Matic Date: Thu, 12 Feb 2026 15:22:09 +0100 Subject: [PATCH 05/41] Fix transient/storage collision when deleting (cherry picked from commit c4dd9e407e01020c2bc1d1c70f4ffcf282ba7fd2) --- libsolidity/codegen/YulUtilFunctions.cpp | 17 ++++++++++- .../output | 14 +++++++-- ...fter_inherited_storage_same_value_type.sol | 3 +- ...rage_array_delete_different_base_type.sol} | 9 ++---- ...after_storage_array_pop_same_base_type.sol | 30 +++++++++++++++++++ ..._after_storage_delete_same_value_type.sol} | 10 ++----- ...storage_mapping_delete_same_value_type.sol | 3 +- ..._storage_struct_delete_same_value_type.sol | 8 ++--- ...fore_inherited_storage_same_value_type.sol | 3 +- ...rage_array_delete_different_base_type.sol} | 11 +++---- ...rray_partial_assignment_same_base_type.sol | 27 +++++++++++++++++ ...before_storage_delete_same_value_type.sol} | 12 +++----- ...storage_mapping_delete_same_value_type.sol | 3 +- ..._storage_struct_delete_same_value_type.sol | 8 ++--- 14 files changed, 109 insertions(+), 49 deletions(-) rename test/libsolidity/semanticTests/storage/{delete_storage_bool_array_transient_uint256_2.sol => delete_overlapping_transient_after_storage_array_delete_different_base_type.sol} (75%) create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_pop_same_base_type.sol rename test/libsolidity/semanticTests/storage/{delete_storage_u256_transient_u256_2.sol => delete_overlapping_transient_after_storage_delete_same_value_type.sol} (63%) rename test/libsolidity/semanticTests/storage/{delete_storage_bool_array_transient_uint256_1.sol => delete_overlapping_transient_before_storage_array_delete_different_base_type.sol} (67%) create mode 100644 test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_partial_assignment_same_base_type.sol rename test/libsolidity/semanticTests/storage/{delete_storage_u256_transient_u256_1.sol => delete_overlapping_transient_before_storage_delete_same_value_type.sol} (57%) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index e1a278494b8a..4c3cd8c0fc69 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -4361,7 +4361,22 @@ std::string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFu std::string YulUtilFunctions::storageSetToZeroFunction(Type const& _type, VariableDeclaration::Location _location) { - std::string const functionName = "storage_set_to_zero_" + _type.identifier(); + solAssert( + _location == VariableDeclaration::Location::Transient || + _location == VariableDeclaration::Location::Unspecified, + "Invalid location for the storage_set_to_zero function" + ); + + if (dynamic_cast(&_type)) + solAssert( + _location == VariableDeclaration::Location::Unspecified && + _type.dataStoredIn(DataLocation::Storage) + ); + + std::string const functionName = + (_location == VariableDeclaration::Location::Transient ? "transient_"s : "") + + "storage_set_to_zero_" + + _type.identifier(); return m_functionCollector.createFunction(functionName, [&]() { if (_type.isValueType()) diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/output b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output index e3a251b2d8b0..dd8abeb333b0 100644 --- a/test/cmdlineTests/storage_transient_storage_collision_ir_output/output +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output @@ -179,11 +179,21 @@ object "C_25" { let convertedValue := convert_uint256_to_uint256(value) tstore(slot, update_byte_slice_dynamic32(tload(slot), offset, prepare_store_uint256(convertedValue))) } - function storage_set_to_zero_uint256(slot, offset) + function transient_storage_set_to_zero_uint256(slot, offset) { let zero := zero_value_for_split_uint256() update_transient_storage_value_uint256_to_uint256(slot, offset, zero) } + function update_storage_value_uint256_to_uint256(slot, offset, value) + { + let convertedValue := convert_uint256_to_uint256(value) + sstore(slot, update_byte_slice_dynamic32(sload(slot), offset, prepare_store_uint256(convertedValue))) + } + function storage_set_to_zero_uint256(slot, offset) + { + let zero := zero_value_for_split_uint256() + update_storage_value_uint256_to_uint256(slot, offset, zero) + } function shift_right_0_unsigned(value) -> newValue { newValue := shr(0, value) } function extract_from_storage_value_offset_uint256(slot_value) -> value @@ -201,7 +211,7 @@ object "C_25" { let expr := 0xffffffff let _1 := convert_rational_by_to_uint256(expr) update_transient_storage_value_offset_uint256_to_uint256(0x00, _1) - storage_set_to_zero_uint256(0x00, 0) + transient_storage_set_to_zero_uint256(0x00, 0) storage_set_to_zero_uint256(0x00, 0) let _2 := read_from_storage_split_offset_uint256(0x00) let expr_1 := _2 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol index f4badbc98845..dae46cd32b51 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_inherited_storage_same_value_type.sol @@ -20,7 +20,6 @@ contract C is Base { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // setAndClear() -> -// x() -> 1 +// x() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_delete_different_base_type.sol similarity index 75% rename from test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol rename to test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_delete_different_base_type.sol index f95b191c108c..d9a904f3caff 100644 --- a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_2.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_delete_different_base_type.sol @@ -1,14 +1,12 @@ -pragma solidity >=0.0; - contract C { bool[3] flags = [true, true, true]; uint256 transient temp; - function f() external { + function setAndClear() external { temp = 0xffffffff; - assert(temp == 0xffffffff); delete flags; delete temp; + assert(temp == 0); } function getFlags() external returns(bool[3] memory) @@ -19,8 +17,7 @@ contract C { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // getFlags() -> true, true, true -// f() -> +// setAndClear() -> // getFlags() -> false, false, false diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_pop_same_base_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_pop_same_base_type.sol new file mode 100644 index 000000000000..76dc10c43c11 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_array_pop_same_base_type.sol @@ -0,0 +1,30 @@ +contract C { + uint256[] arr; + uint256 transient t; + + function pushArr() external { + arr.push(1); + } + + function setAndClear() external { + t = 2; + delete t; + assert(t == 0); + arr.pop(); + } + + // Get value at index 0, which should have been cleared after arr.pop() + function getArr() external returns (uint256 value) { + assembly { + mstore(0, arr.slot) + value := sload(keccak256(0x00, 0x20)) + } + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// pushArr() -> +// getArr() -> 1 +// setAndClear() -> +// getArr() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_delete_same_value_type.sol similarity index 63% rename from test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol rename to test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_delete_same_value_type.sol index 03e06747af1a..ad206c6eeee0 100644 --- a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_2.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_delete_same_value_type.sol @@ -1,22 +1,18 @@ -// SPDX-License-Identifier: GPL-2.0 -pragma solidity >=0.0; - contract C { uint256 transient varTransient; uint256 public varStorage = 0xeeeeeeeeee; - function foo() external { + function setAndClear() external { varTransient = 0xffffffff; - assert(varTransient == 0xffffffff); delete varStorage; delete varTransient; + assert(varTransient == 0); } } // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // varStorage() -> 0xeeeeeeeeee -// foo() -> +// setAndClear() -> // varStorage() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol index b1a38832158d..842cd47504b9 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_mapping_delete_same_value_type.sol @@ -7,7 +7,7 @@ contract C { t = 2; delete m[0]; delete t; - assert(t == 2); + assert(t == 0); } function getM() external view returns (uint256) { @@ -17,7 +17,6 @@ contract C { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // setAndClear() -> // getM() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol index 33a0dc2de46b..703816296268 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_after_storage_struct_delete_same_value_type.sol @@ -4,16 +4,14 @@ contract C { address b; } - S s; + S s = S(1, address(0x1234)); uint256 transient t; function setAndDelete() external { - s.a = 1; - s.b = address(0x1234); t = 2; delete s; delete t; - assert(t == 2); + assert(t == 0); } function getS() external view returns (uint256, address) { @@ -22,7 +20,7 @@ contract C { } // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- +// getS() -> 1, 4660 // setAndDelete() -> // getS() -> 0, 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol index 8e40422a9019..3af1b66e591c 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_inherited_storage_same_value_type.sol @@ -20,7 +20,6 @@ contract C is Base { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // setAndClear() -> -// x() -> 1 +// x() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_delete_different_base_type.sol similarity index 67% rename from test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol rename to test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_delete_different_base_type.sol index 4d84b23f70e7..c2e1c1a241cc 100644 --- a/test/libsolidity/semanticTests/storage/delete_storage_bool_array_transient_uint256_1.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_delete_different_base_type.sol @@ -1,13 +1,11 @@ -pragma solidity >=0.0; - contract C { bool[3] flags = [true, true, true]; uint256 transient temp; - function f() external { + function setAndClear() external { temp = 0xffffffff; - assert(temp == 0xffffffff); delete temp; + assert(temp == 0); delete flags; } @@ -19,8 +17,7 @@ contract C { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // getFlags() -> true, true, true -// f() -> -// getFlags() -> true, true, true +// setAndClear() -> +// getFlags() -> false, false, false diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_partial_assignment_same_base_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_partial_assignment_same_base_type.sol new file mode 100644 index 000000000000..a9d3e0ae7384 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_array_partial_assignment_same_base_type.sol @@ -0,0 +1,27 @@ +contract C { + uint256[2] small; + uint256[4] large; + uint256 transient t; + + function setAndClear() external { + large = [1,2,3,4]; + small = [10, 20]; + t = 99; + + delete t; + assert(t == 0); + large = small; + } + + function getLarge() external view returns (uint256[4] memory) { + return large; + } +} +// ==== +// EVMVersion: >=cancun +// ---- +// setAndClear() -> +// gas irOptimized: 124683 +// gas legacy: 127807 +// gas legacyOptimized: 124828 +// getLarge() -> 10, 20, 0, 0 diff --git a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_delete_same_value_type.sol similarity index 57% rename from test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol rename to test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_delete_same_value_type.sol index 08f8120be0ce..26e34fc26a55 100644 --- a/test/libsolidity/semanticTests/storage/delete_storage_u256_transient_u256_1.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_delete_same_value_type.sol @@ -1,22 +1,18 @@ -// SPDX-License-Identifier: GPL-2.0 -pragma solidity >=0.0; - contract C { uint256 transient varTransient; uint256 public varStorage = 0xeeeeeeeeee; - function foo() external { + function setAndClear() external { varTransient = 0xffffffff; - assert(varTransient == 0xffffffff); delete varTransient; + assert(varTransient == 0); delete varStorage; } } // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // varStorage() -> 0xeeeeeeeeee -// foo() -> -// varStorage() -> 0xeeeeeeeeee +// setAndClear() -> +// varStorage() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol index 129fafad7507..6e262c8a6269 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_mapping_delete_same_value_type.sol @@ -17,7 +17,6 @@ contract C { // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- // setAndClear() -> -// getM() -> 1 +// getM() -> 0 diff --git a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol index d499d0618374..c2127c9bad1f 100644 --- a/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol +++ b/test/libsolidity/semanticTests/storage/delete_overlapping_transient_before_storage_struct_delete_same_value_type.sol @@ -4,12 +4,10 @@ contract C { address b; } - S s; + S s = S(1, address(0x1234)); uint256 transient t; function setAndDelete() external { - s.a = 1; - s.b = address(0x1234); t = 2; delete t; assert(t == 0); @@ -22,7 +20,7 @@ contract C { } // ==== // EVMVersion: >=cancun -// compileViaYul: true // ---- +// getS() -> 1, 4660 // setAndDelete() -> -// getS() -> 1, 0 +// getS() -> 0, 0 From 74d96d4058c64dd4d7fc427ccf64895153815e89 Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:43:08 +0200 Subject: [PATCH 06/41] Move TarjanSCC from libyul to libsolutil (cherry picked from commit 0983a901d6683f6386a0702e115c29352c4ac056) --- libsolutil/CMakeLists.txt | 1 + libsolutil/TarjanSCC.h | 155 ++++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 1 + test/libsolutil/TarjanSCC.cpp | 149 ++++++++++++++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 libsolutil/TarjanSCC.h create mode 100644 test/libsolutil/TarjanSCC.cpp diff --git a/libsolutil/CMakeLists.txt b/libsolutil/CMakeLists.txt index 1ec12051a619..69c53cf9c086 100644 --- a/libsolutil/CMakeLists.txt +++ b/libsolutil/CMakeLists.txt @@ -35,6 +35,7 @@ set(sources StringUtils.h SwarmHash.cpp SwarmHash.h + TarjanSCC.h TemporaryDirectory.cpp TemporaryDirectory.h UTF8.cpp diff --git a/libsolutil/TarjanSCC.h b/libsolutil/TarjanSCC.h new file mode 100644 index 000000000000..a86dcb899c7d --- /dev/null +++ b/libsolutil/TarjanSCC.h @@ -0,0 +1,155 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace solidity::util +{ + +namespace detail +{ + +/// Callers must supply a dense `[0, N)` remapping; this struct conflates the node's +/// identity with its position in `adjacency` for performance. +template +struct TarjanSCC +{ + static constexpr std::size_t undefined = std::numeric_limits::max(); + + struct Frame + { + NodeIndex node; + std::size_t childIdx; + }; + + std::vector> const& adjacency; + /// numbers the nodes consecutively in the order in which they are discovered + std::vector discoveryIndex; + /// lowlink[v] corresponds to the smallest index of a node reachable through v's DFS subtree + std::vector lowlink; + std::vector onStack; + std::vector nodeStack; + std::vector workStack; + std::size_t nextIndex = 0; + std::vector> sccs; + + explicit TarjanSCC(std::vector> const& _adjacency): + adjacency(_adjacency), + discoveryIndex(_adjacency.size(), undefined), + lowlink(_adjacency.size(), 0), + onStack(_adjacency.size(), false) + {} + + void enter(NodeIndex const _v) + { + solAssert(_v < adjacency.size()); + discoveryIndex[_v] = nextIndex; + lowlink[_v] = nextIndex; + ++nextIndex; + nodeStack.push_back(_v); + onStack[_v] = true; + workStack.push_back({_v, 0}); + } + + void emitSCC(NodeIndex const _root) + { + solAssert(!nodeStack.empty()); + std::vector scc; + NodeIndex w; + do + { + w = nodeStack.back(); + nodeStack.pop_back(); + onStack[w] = false; + scc.push_back(w); + } + while (w != _root); + sccs.push_back(std::move(scc)); + } + + std::vector> run() && + { + for (NodeIndex root = 0; root < discoveryIndex.size(); ++root) + { + if (discoveryIndex[root] != undefined) + continue; + + // start new DFS tree + enter(root); + while (!workStack.empty()) + { + NodeIndex const v = workStack.back().node; + std::size_t const childIdx = workStack.back().childIdx; + if (childIdx < adjacency[v].size()) + { + // process next outgoing edge + NodeIndex const w = adjacency[v][childIdx]; + // advance v's currently handled edge before pushing w, so v resumes at the next child when w finishes + workStack.back().childIdx = childIdx + 1; + if (discoveryIndex[w] == undefined) + // Successor w has not yet been visited; recurse on it + enter(w); + else if (onStack[w]) + // Successor w is in stack S and hence in the current SCC + lowlink[v] = std::min(lowlink[v], discoveryIndex[w]); + } + else + { + // if v is a root node + if (lowlink[v] == discoveryIndex[v]) + // generate an SCC and pop the node stack + emitSCC(v); + + // pop v and propagate its lowlink to the parent (post-recursion update) + workStack.pop_back(); + if (!workStack.empty()) + { + NodeIndex const parent = workStack.back().node; + lowlink[parent] = std::min(lowlink[parent], lowlink[v]); + } + } + } + } + return std::move(sccs); + } +}; + +} + +/// Tarjan's strongly-connected-components algorithm. +/// Takes an adjacency list where `_adjacency[v]` is the list of successors of node `v`. Node IDs must lie in `[0, _adjacency.size())`. +/// Implementation based on the Wikipedia pseudocode. +/// +/// Wikipedia contributors, "Tarjan's strongly connected components algorithm," Wikipedia, The Free Encyclopedia, +/// https://en.wikipedia.org/w/index.php?title=Tarjan%27s_strongly_connected_components_algorithm&oldid=1341352351 +/// Tarjan, R.E., "Depth-First Search and Linear Graph Algorithms", https://doi.org/10.1137/0201010 +template +std::vector> computeStronglyConnectedComponents(std::vector> const& _adjacency) +{ + return detail::TarjanSCC(_adjacency).run(); +} + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 211606363bc4..d8927dc5fccf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,6 +43,7 @@ set(libsolutil_sources libsolutil/LEB128.cpp libsolutil/StringUtils.cpp libsolutil/SwarmHash.cpp + libsolutil/TarjanSCC.cpp libsolutil/TemporaryDirectoryTest.cpp libsolutil/UTF8.cpp libsolutil/Whiskers.cpp diff --git a/test/libsolutil/TarjanSCC.cpp b/test/libsolutil/TarjanSCC.cpp new file mode 100644 index 000000000000..c6e8c350c711 --- /dev/null +++ b/test/libsolutil/TarjanSCC.cpp @@ -0,0 +1,149 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Unit tests for the Tarjan SCC utility. + */ + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace solidity::util::test +{ + +using NodeID = std::uint32_t; +using Graph = std::vector>; +using SCCList = std::vector>; + +namespace +{ + +namespace bdata = boost::unit_test::data; + +SCCList compute(Graph const& _g) +{ + return solidity::util::computeStronglyConnectedComponents(_g); +} + +SCCList canonicalize(SCCList _sccs) +{ + for (auto& scc: _sccs) + ranges::sort(scc); + ranges::sort(_sccs); + return _sccs; +} + +std::string formatSCCs(SCCList const& _sccs) +{ + auto const formatScc = [](std::vector const& _scc) { + return fmt::format("{{{}}}", fmt::join(_scc, ", ")); + }; + return fmt::format("{{{}}}", fmt::join(_sccs | ranges::views::transform(formatScc), ", ")); +} + +boost::test_tools::predicate_result sccsEqual(SCCList const& _actual, SCCList const& _expected) +{ + SCCList const canonicalActual = canonicalize(_actual); + SCCList const canonicalExpected = canonicalize(_expected); + if (canonicalActual == canonicalExpected) + return true; + boost::test_tools::predicate_result result(false); + result.message() + << "SCC partitions differ (compared as sets of sets).\n" + << " actual: " << formatSCCs(canonicalActual) << "\n" + << " expected: " << formatSCCs(canonicalExpected); + return result; +} + +struct PartitionCase +{ + std::string name; + Graph graph; + SCCList expectedSCCs; +}; + +std::ostream& operator<<(std::ostream& _os, PartitionCase const& _case) +{ + return _os << _case.name; +} + +std::vector const partitionCases = { + {"empty_graph", {}, {}}, + {"single_node_no_edges", Graph(1), {{0}}}, + {"single_node_self_loop", {{0}}, {{0}}}, + {"two_cycle", {{1}, {0}}, {{0, 1}}}, + {"three_cycle", {{1}, {2}, {0}}, {{0, 1, 2}}}, + {"linear_chain", {{1}, {2}, {3}, {}}, {{0}, {1}, {2}, {3}}}, + // 0 <-> 1, plus self-loop on 0. Yields one SCC {0, 1}. + {"self_loop_inside_two_cycle", {{0, 1}, {0}}, {{0, 1}}}, + // 0 -> 2, 1 -> 2: three singleton SCCs. + {"dag_with_shared_successor", {{2}, {2}, {}}, {{0}, {1}, {2}}}, + { + "clrs_example", + // CLRS, 3rd ed., Fig. 22.9 (relabeled a..h -> 0..7). + // 0 -> 1 + // 1 -> 2, 4, 5 + // 2 -> 3, 6 + // 3 -> 2, 7 + // 4 -> 0, 5 + // 5 -> 6 + // 6 -> 5, 7 + // 7 -> 7 + {{1}, {2, 4, 5}, {3, 6}, {2, 7}, {0, 5}, {6}, {5, 7}, {7}}, + {{0, 1, 4}, {2, 3}, {5, 6}, {7}}, + }, + { + "multiple_disjoint_components", + // Four components: 0 <-> 1, 2 <-> 3, 4 -> 5. + {{1}, {0}, {3}, {2}, {5}, {}}, + {{0, 1}, {2, 3}, {4}, {5}}, + }, + { + "nested_sccs_with_cross_edges", + // Two SCCs connected by an inter-SCC edge: 0 <-> 1 (A), 2 <-> 3 (B), 1 -> 2. + {{1}, {0, 2}, {3}, {2}}, + {{0, 1}, {2, 3}}, + }, + // 0 -> 1, node 2 isolated: 2 still emitted as its own SCC. + {"unreachable_node_still_emitted", {{1}, {}, {}}, {{0}, {1}, {2}}}, +}; + +} + +BOOST_AUTO_TEST_SUITE(TarjanSCC) + +BOOST_DATA_TEST_CASE(partition, bdata::make(partitionCases), testCase) +{ + BOOST_CHECK(sccsEqual(compute(testCase.graph), testCase.expectedSCCs)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} From b5f59ce4da4ee1504539a620a705aa331e2d870b Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:27:00 +0200 Subject: [PATCH 07/41] Add Yul AST call graph recursion tests (cherry picked from commit a0a3dd8f2368f9a0412d625772eb0ac1046979fb) --- test/CMakeLists.txt | 2 + test/InteractiveTests.h | 2 + test/libyul/CallGraphTest.cpp | 111 ++++++++++++++++++ test/libyul/CallGraphTest.h | 36 ++++++ .../intersecting_recursive_cycles.yul | 16 +++ test/libyul/yulCallGraph/ambiguous_names.yul | 6 + .../yulCallGraph/ambiguous_names_nested.yul | 12 ++ .../branching_into_and_out_of_scc.yul | 13 ++ test/libyul/yulCallGraph/caller_into_scc.yul | 10 ++ .../yulCallGraph/direct_plus_mutual.yul | 10 ++ test/libyul/yulCallGraph/direct_self_call.yul | 6 + .../libyul/yulCallGraph/duplicate_callees.yul | 11 ++ .../yulCallGraph/intersecting_cycles.yul | 12 ++ .../intersecting_cycles_visitation_order.yul | 12 ++ test/libyul/yulCallGraph/leaf.yul | 6 + test/libyul/yulCallGraph/mixed.yul | 10 ++ .../yulCallGraph/multi_callee_three_scc.yul | 13 ++ .../multiple_distinct_callees.yul | 13 ++ .../yulCallGraph/mutual_three_cycle.yul | 10 ++ test/libyul/yulCallGraph/mutual_two_cycle.yul | 8 ++ test/libyul/yulCallGraph/nested_functions.yul | 16 +++ .../yulCallGraph/non_recursive_chain.yul | 10 ++ test/libyul/yulCallGraph/self_and_other.yul | 11 ++ test/libyul/yulCallGraph/shared_callee.yul | 10 ++ test/tools/CMakeLists.txt | 1 + 25 files changed, 367 insertions(+) create mode 100644 test/libyul/CallGraphTest.cpp create mode 100644 test/libyul/CallGraphTest.h create mode 100644 test/libyul/functionSideEffects/intersecting_recursive_cycles.yul create mode 100644 test/libyul/yulCallGraph/ambiguous_names.yul create mode 100644 test/libyul/yulCallGraph/ambiguous_names_nested.yul create mode 100644 test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul create mode 100644 test/libyul/yulCallGraph/caller_into_scc.yul create mode 100644 test/libyul/yulCallGraph/direct_plus_mutual.yul create mode 100644 test/libyul/yulCallGraph/direct_self_call.yul create mode 100644 test/libyul/yulCallGraph/duplicate_callees.yul create mode 100644 test/libyul/yulCallGraph/intersecting_cycles.yul create mode 100644 test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul create mode 100644 test/libyul/yulCallGraph/leaf.yul create mode 100644 test/libyul/yulCallGraph/mixed.yul create mode 100644 test/libyul/yulCallGraph/multi_callee_three_scc.yul create mode 100644 test/libyul/yulCallGraph/multiple_distinct_callees.yul create mode 100644 test/libyul/yulCallGraph/mutual_three_cycle.yul create mode 100644 test/libyul/yulCallGraph/mutual_two_cycle.yul create mode 100644 test/libyul/yulCallGraph/nested_functions.yul create mode 100644 test/libyul/yulCallGraph/non_recursive_chain.yul create mode 100644 test/libyul/yulCallGraph/self_and_other.yul create mode 100644 test/libyul/yulCallGraph/shared_callee.yul diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d8927dc5fccf..dbbcf3eaec81 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -144,6 +144,8 @@ detect_stray_source_files("${libsolidity_util_sources}" "libsolidity/util/") set(libyul_sources libyul/Common.cpp libyul/Common.h + libyul/CallGraphTest.cpp + libyul/CallGraphTest.h libyul/CompilabilityChecker.cpp libyul/ControlFlowGraphTest.cpp libyul/ControlFlowGraphTest.h diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 107a6aae0bef..d53406ef693b 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Yul Control Flow Graph", "libyul", "yulControlFlowGraph", false, false, &yul::test::ControlFlowGraphTest::create}, {"Yul SSA Control Flow Graph", "libyul", "yulSSAControlFlowGraph", false, false, &yul::test::SSAControlFlowGraphTest::create}, + {"Yul Call Graph", "libyul", "yulCallGraph", false, false, &yul::test::CallGraphTest::create}, {"Yul Stack Layout", "libyul", "yulStackLayout", false, false, &yul::test::StackLayoutGeneratorTest::create}, {"Yul Stack Shuffling", "libyul", "yulStackShuffling", false, false, &yul::test::StackShufflingTest::create}, {"Control Flow Side Effects", "libyul", "controlFlowSideEffects", false, false, &yul::test::ControlFlowSideEffectsTest::create}, diff --git a/test/libyul/CallGraphTest.cpp b/test/libyul/CallGraphTest.cpp new file mode 100644 index 000000000000..5fad64777b44 --- /dev/null +++ b/test/libyul/CallGraphTest.cpp @@ -0,0 +1,111 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::yul; +using namespace solidity::yul::test; +using namespace solidity::frontend; +using namespace solidity::frontend::test; + +namespace +{ + +/// Collects function names in the order in which the functions are defined, descending into nested +/// functions, so that the call graph can be printed deterministically. +struct FunctionNameCollector: ASTWalker +{ + using ASTWalker::operator(); + void operator()(FunctionDefinition const& _function) override + { + names.emplace_back(_function.name); + ASTWalker::operator()(_function); + } + + std::vector names; +}; + +} + +std::unique_ptr CallGraphTest::create(Config const& _config) +{ + return std::make_unique(_config.filename); +} + +CallGraphTest::CallGraphTest(std::string const& _filename): + TestCase(_filename) +{ + m_source = m_reader.source(); + m_expectation = m_reader.simpleExpectations(); +} + +TestCase::TestResult CallGraphTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted) +{ + YulStack const yulStack = parseYul(m_source); + solUnimplementedAssert(yulStack.parserResult()->subObjects.empty(), "Tests with subobjects not supported."); + + if (yulStack.hasErrors()) + { + printYulErrors(yulStack, _stream, _linePrefix, _formatted); + return TestResult::FatalError; + } + + Block const& root = yulStack.parserResult()->code()->root(); + + std::ostringstream out; + try + { + CallGraph const callGraph = CallGraphGenerator::callGraph(root); + std::set const recursive = callGraph.recursiveFunctions(); + + auto classification = [&](FunctionHandle const& _function) { + return recursive.contains(_function) ? "recursive" : "non-recursive"; + }; + + FunctionNameCollector collector; + collector(root); + + // The outermost (non-function) context is denoted by the empty name in the call graph. + out << "
: " << classification(YulName{}) << "\n"; + for (YulName const& name: collector.names) + out << name.str() << ": " << classification(name) << "\n"; + } + catch (langutil::InternalCompilerError const& _error) + { + out << "InternalCompilerError: " << (_error.comment() ? *_error.comment() : "") << "\n"; + } + m_obtainedResult = out.str(); + + return checkResult(_stream, _linePrefix, _formatted); +} diff --git a/test/libyul/CallGraphTest.h b/test/libyul/CallGraphTest.h new file mode 100644 index 000000000000..0b644cb2e1b9 --- /dev/null +++ b/test/libyul/CallGraphTest.h @@ -0,0 +1,36 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +#include + +namespace solidity::yul::test +{ + +class CallGraphTest: public frontend::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config); + explicit CallGraphTest(std::string const& _filename); + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; +}; + +} diff --git a/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul b/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul new file mode 100644 index 000000000000..de238885a5d6 --- /dev/null +++ b/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul @@ -0,0 +1,16 @@ +{ + // Two recursive cycles sharing alpha: alpha <-> beta and alpha -> gamma -> beta -> alpha, + // so {alpha, beta, gamma} is a single strongly-connected component (all three are recursive). + // + // The old call-graph cycle finder mis-classified gamma as non-recursive (SOL-2026-2), but the + // side-effect result is unaffected: gamma still calls into the (correctly detected) alpha/beta + // cycle, so "can loop" is propagated to it along the call edges regardless of the recursion flag. + function alpha() { beta() gamma() } + function beta() { alpha() } + function gamma() { beta() } +} +// ---- +// : movable, movable apart from effects, can be removed, can be removed if no msize +// alpha: movable apart from effects, can loop +// beta: movable apart from effects, can loop +// gamma: movable apart from effects, can loop diff --git a/test/libyul/yulCallGraph/ambiguous_names.yul b/test/libyul/yulCallGraph/ambiguous_names.yul new file mode 100644 index 000000000000..f8cf3924360e --- /dev/null +++ b/test/libyul/yulCallGraph/ambiguous_names.yul @@ -0,0 +1,6 @@ +{ + { function f() {} } + { function f() {} } +} +// ---- +// InternalCompilerError: CallGraphGenerator requires a disambiguated AST: duplicate function name f. diff --git a/test/libyul/yulCallGraph/ambiguous_names_nested.yul b/test/libyul/yulCallGraph/ambiguous_names_nested.yul new file mode 100644 index 000000000000..4cc98c7e1097 --- /dev/null +++ b/test/libyul/yulCallGraph/ambiguous_names_nested.yul @@ -0,0 +1,12 @@ +{ + { + function f() { + function g() {} + } + } + { + function g() {} + } +} +// ---- +// InternalCompilerError: CallGraphGenerator requires a disambiguated AST: duplicate function name g. diff --git a/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul b/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul new file mode 100644 index 000000000000..82eccf25b6f4 --- /dev/null +++ b/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul @@ -0,0 +1,13 @@ +{ + function f() { + g() + h() + } + function g() { f() } + function h() {} +} +// ---- +//
: non-recursive +// f: recursive +// g: recursive +// h: non-recursive diff --git a/test/libyul/yulCallGraph/caller_into_scc.yul b/test/libyul/yulCallGraph/caller_into_scc.yul new file mode 100644 index 000000000000..4167f3caf651 --- /dev/null +++ b/test/libyul/yulCallGraph/caller_into_scc.yul @@ -0,0 +1,10 @@ +{ + function f() { g() } + function g() { h() } + function h() { g() } +} +// ---- +//
: non-recursive +// f: non-recursive +// g: recursive +// h: recursive diff --git a/test/libyul/yulCallGraph/direct_plus_mutual.yul b/test/libyul/yulCallGraph/direct_plus_mutual.yul new file mode 100644 index 000000000000..f75690c6ba32 --- /dev/null +++ b/test/libyul/yulCallGraph/direct_plus_mutual.yul @@ -0,0 +1,10 @@ +{ + function f() { f() } + function g() { h() } + function h() { g() } +} +// ---- +//
: non-recursive +// f: recursive +// g: recursive +// h: recursive diff --git a/test/libyul/yulCallGraph/direct_self_call.yul b/test/libyul/yulCallGraph/direct_self_call.yul new file mode 100644 index 000000000000..7a57413da61b --- /dev/null +++ b/test/libyul/yulCallGraph/direct_self_call.yul @@ -0,0 +1,6 @@ +{ + function f() { f() } +} +// ---- +//
: non-recursive +// f: recursive diff --git a/test/libyul/yulCallGraph/duplicate_callees.yul b/test/libyul/yulCallGraph/duplicate_callees.yul new file mode 100644 index 000000000000..549ca59daa05 --- /dev/null +++ b/test/libyul/yulCallGraph/duplicate_callees.yul @@ -0,0 +1,11 @@ +{ + function f() { + g() + g() + } + function g() {} +} +// ---- +//
: non-recursive +// f: non-recursive +// g: non-recursive diff --git a/test/libyul/yulCallGraph/intersecting_cycles.yul b/test/libyul/yulCallGraph/intersecting_cycles.yul new file mode 100644 index 000000000000..b38a35720136 --- /dev/null +++ b/test/libyul/yulCallGraph/intersecting_cycles.yul @@ -0,0 +1,12 @@ +{ + // Two recursive cycles sharing alpha: alpha <-> beta and alpha -> gamma -> beta -> alpha, + // so {alpha, beta, gamma} is a single strongly-connected component and all three are recursive. + function alpha() { beta() gamma() } + function beta() { alpha() } + function gamma() { beta() } +} +// ---- +//
: non-recursive +// alpha: recursive +// beta: recursive +// gamma: non-recursive diff --git a/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul new file mode 100644 index 000000000000..1e54d1c51953 --- /dev/null +++ b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul @@ -0,0 +1,12 @@ +{ + // Same topology as intersecting_cycles.yul (two recursive cycles sharing a node, so the whole + // {hub, spoke, rim} set is a single strongly-connected component) but with different function names. + function hub() { spoke() rim() } + function spoke() { hub() } + function rim() { spoke() } +} +// ---- +//
: non-recursive +// hub: recursive +// spoke: recursive +// rim: recursive diff --git a/test/libyul/yulCallGraph/leaf.yul b/test/libyul/yulCallGraph/leaf.yul new file mode 100644 index 000000000000..b44705017a74 --- /dev/null +++ b/test/libyul/yulCallGraph/leaf.yul @@ -0,0 +1,6 @@ +{ + function f() {} +} +// ---- +//
: non-recursive +// f: non-recursive diff --git a/test/libyul/yulCallGraph/mixed.yul b/test/libyul/yulCallGraph/mixed.yul new file mode 100644 index 000000000000..e1bd7247fa38 --- /dev/null +++ b/test/libyul/yulCallGraph/mixed.yul @@ -0,0 +1,10 @@ +{ + function f() { g() } + function g() { f() } + function h() {} +} +// ---- +//
: non-recursive +// f: recursive +// g: recursive +// h: non-recursive diff --git a/test/libyul/yulCallGraph/multi_callee_three_scc.yul b/test/libyul/yulCallGraph/multi_callee_three_scc.yul new file mode 100644 index 000000000000..dd1396a6e314 --- /dev/null +++ b/test/libyul/yulCallGraph/multi_callee_three_scc.yul @@ -0,0 +1,13 @@ +{ + function a() { + b() + c() + } + function b() { a() } + function c() { b() } +} +// ---- +//
: non-recursive +// a: recursive +// b: recursive +// c: recursive diff --git a/test/libyul/yulCallGraph/multiple_distinct_callees.yul b/test/libyul/yulCallGraph/multiple_distinct_callees.yul new file mode 100644 index 000000000000..e67e01ca98f4 --- /dev/null +++ b/test/libyul/yulCallGraph/multiple_distinct_callees.yul @@ -0,0 +1,13 @@ +{ + function f() { + g() + h() + } + function g() {} + function h() {} +} +// ---- +//
: non-recursive +// f: non-recursive +// g: non-recursive +// h: non-recursive diff --git a/test/libyul/yulCallGraph/mutual_three_cycle.yul b/test/libyul/yulCallGraph/mutual_three_cycle.yul new file mode 100644 index 000000000000..1cdeb220de2b --- /dev/null +++ b/test/libyul/yulCallGraph/mutual_three_cycle.yul @@ -0,0 +1,10 @@ +{ + function f() { g() } + function g() { h() } + function h() { f() } +} +// ---- +//
: non-recursive +// f: recursive +// g: recursive +// h: recursive diff --git a/test/libyul/yulCallGraph/mutual_two_cycle.yul b/test/libyul/yulCallGraph/mutual_two_cycle.yul new file mode 100644 index 000000000000..123e266982cf --- /dev/null +++ b/test/libyul/yulCallGraph/mutual_two_cycle.yul @@ -0,0 +1,8 @@ +{ + function f() { g() } + function g() { f() } +} +// ---- +//
: non-recursive +// f: recursive +// g: recursive diff --git a/test/libyul/yulCallGraph/nested_functions.yul b/test/libyul/yulCallGraph/nested_functions.yul new file mode 100644 index 000000000000..8bd506003842 --- /dev/null +++ b/test/libyul/yulCallGraph/nested_functions.yul @@ -0,0 +1,16 @@ +{ + // Nested function definitions with a cycle a -> b -> c -> a. + function a() { + function b() { + function c() { a() } + c() + } + b() + } + a() +} +// ---- +//
: non-recursive +// a: recursive +// b: recursive +// c: recursive diff --git a/test/libyul/yulCallGraph/non_recursive_chain.yul b/test/libyul/yulCallGraph/non_recursive_chain.yul new file mode 100644 index 000000000000..20086c873c71 --- /dev/null +++ b/test/libyul/yulCallGraph/non_recursive_chain.yul @@ -0,0 +1,10 @@ +{ + function f() { g() } + function g() { h() } + function h() {} +} +// ---- +//
: non-recursive +// f: non-recursive +// g: non-recursive +// h: non-recursive diff --git a/test/libyul/yulCallGraph/self_and_other.yul b/test/libyul/yulCallGraph/self_and_other.yul new file mode 100644 index 000000000000..be843421f297 --- /dev/null +++ b/test/libyul/yulCallGraph/self_and_other.yul @@ -0,0 +1,11 @@ +{ + function f() { + f() + g() + } + function g() {} +} +// ---- +//
: non-recursive +// f: recursive +// g: non-recursive diff --git a/test/libyul/yulCallGraph/shared_callee.yul b/test/libyul/yulCallGraph/shared_callee.yul new file mode 100644 index 000000000000..4b28e34f2b49 --- /dev/null +++ b/test/libyul/yulCallGraph/shared_callee.yul @@ -0,0 +1,10 @@ +{ + function f() { h() } + function g() { h() } + function h() {} +} +// ---- +//
: non-recursive +// f: non-recursive +// g: non-recursive +// h: non-recursive diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 289f2f2def35..84eb69013418 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(isoltest ../libsolidity/ASTPropertyTest.cpp ../libsolidity/FunctionDependencyGraphTest.cpp ../libsolidity/SMTCheckerTest.cpp + ../libyul/CallGraphTest.cpp ../libyul/Common.cpp ../libyul/ControlFlowGraphTest.cpp ../libyul/SSAControlFlowGraphTest.cpp From c16578df1e7963366ed158f5d05a6fedc3b4e31b Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:42:20 +0200 Subject: [PATCH 08/41] Add tests reproducing spurious memory spilling (cherry picked from commit 139d696bc9aa3d59d295256d128c29602dd1d18c) --- .../in.sol | 65 +++++++++++ .../input.json | 12 ++ .../output.json | 36 ++++++ .../intersecting_recursive_cycles.yul | 3 +- .../stackLimitEvader/intersecting_cycles.yul | 105 ++++++++++++++++++ 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/in.sol create mode 100644 test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json create mode 100644 test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/in.sol b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/in.sol new file mode 100644 index 000000000000..3e2f0619cf8e --- /dev/null +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/in.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity *; + +// Regression test for stack-to-memory spilling miscompilation SOL-2026-2 caused by the call graph cycle detection +// misclassifying functions in intersecting recursive cycles. +// +// Call graph: a -> {b, c}, b -> a, c -> b +// so {a, b, c} is a single strongly-connected component and all three functions are recursive, spilling is unsound. +// The stack limit evader must refuse to spill here. +contract C { + uint256[26] public seed; + + function trigger() external returns (uint256 storedAt3) { + a(3); + storedAt3 = seed[3]; + } + + function a(uint256 m) internal { + if (m == 0) return; + b(m); + c(m); + } + function b(uint256 m) internal { + if (m == 0) return; + a(m - 1); + } + function c(uint256 m) internal { + if (m == 0) return; + uint256 v1 = seed[0] ^ m; + uint256 v2 = seed[1] ^ m; + uint256 v3 = seed[2] ^ m; + uint256 v4 = seed[3] ^ m; + uint256 v5 = seed[4] ^ m; + uint256 v6 = seed[5] ^ m; + uint256 v7 = seed[6] ^ m; + uint256 v8 = seed[7] ^ m; + uint256 v9 = seed[8] ^ m; + uint256 v10 = seed[9] ^ m; + uint256 v11 = seed[10] ^ m; + uint256 v12 = seed[11] ^ m; + uint256 v13 = seed[12] ^ m; + uint256 v14 = seed[13] ^ m; + uint256 v15 = seed[14] ^ m; + uint256 v16 = seed[15] ^ m; + uint256 v17 = seed[16] ^ m; + uint256 v18 = seed[17] ^ m; + uint256 v19 = seed[18] ^ m; + uint256 v20 = seed[19] ^ m; + uint256 v21 = seed[20] ^ m; + uint256 v22 = seed[21] ^ m; + uint256 v23 = seed[22] ^ m; + uint256 v24 = seed[23] ^ m; + uint256 v25 = seed[24] ^ m; + + b(m - 1); + + // Read m back, provoking a stack too deep situation + seed[m] = m; + // Keep v1..v25 alive across the b(m - 1) call + seed[25] = + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + + v11 + v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 + + v21 + v22 + v23 + v24 + v25; + } +} diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json new file mode 100644 index 000000000000..dc86ac68494e --- /dev/null +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json @@ -0,0 +1,12 @@ +{ + "language": "Solidity", + "sources": { + "in.sol": {"urls": ["in.sol"]} + }, + "settings": { + "viaIR": true, + "outputSelection": { + "*": {"*": ["evm.bytecode"]} + } + } +} diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json new file mode 100644 index 000000000000..d722677373f0 --- /dev/null +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json @@ -0,0 +1,36 @@ +{ + "contracts": { + "in.sol": { + "C": { + "evm": { + "bytecode": { + "functionDebugData": { + "allocate_unbounded": { + "entryPoint": 32, + "id": null, + "parameterSlots": 0, + "returnSlots": 1 + }, + "revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb": { + "entryPoint": 38, + "id": null, + "parameterSlots": 0, + "returnSlots": 0 + } + }, + "generatedSources": [], + "linkReferences": {}, + "object": "", + "opcodes":"", + "sourceMap": "567:1327:0:-:0;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;" + } + } + } + } + }, + "sources": { + "in.sol": { + "id": 0 + } + } +} diff --git a/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul b/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul index de238885a5d6..9cda724b46e7 100644 --- a/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul +++ b/test/libyul/functionSideEffects/intersecting_recursive_cycles.yul @@ -3,8 +3,7 @@ // so {alpha, beta, gamma} is a single strongly-connected component (all three are recursive). // // The old call-graph cycle finder mis-classified gamma as non-recursive (SOL-2026-2), but the - // side-effect result is unaffected: gamma still calls into the (correctly detected) alpha/beta - // cycle, so "can loop" is propagated to it along the call edges regardless of the recursion flag. + // side-effect result is unaffected. function alpha() { beta() gamma() } function beta() { alpha() } function gamma() { beta() } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul new file mode 100644 index 000000000000..cfa226a4e50f --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul @@ -0,0 +1,105 @@ +{ + mstore(0x40, memoryguard(128)) + ba() + function ba() { + z() + y() + } + function z() { + ba() + } + function y() { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + z() + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } +} +// ==== +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// ba() +// function ba() +// { +// z() +// y() +// } +// function z() +// { ba() } +// function y() +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// z() +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// } +// } From 9b997d0918a1baf9b7d86816badf1c147dd07da8 Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:44:25 +0200 Subject: [PATCH 09/41] Fix call graph cycle detection for intersecting recursive cycles Use Tarjan's strongly-connected-components algorithm: a function is recursive iff it lies in a non-trivial SCC or has a self-edge. Also add intersection recursive cycles regression test to function specializer tests. (cherry picked from commit 502561897c59e31f5c5cb1218874b01750e6aa2d) --- docs/bugs.json | 47 +++- docs/bugs_by_version.json | 213 +++++++++++++++++- libyul/optimiser/CallGraphGenerator.cpp | 105 ++++++--- .../output.json | 49 ++-- .../yulCallGraph/intersecting_cycles.yul | 2 +- .../intersecting_cycles_visitation_order.yul | 8 +- .../intersecting_recursive_cycles.yul | 35 +++ .../stackLimitEvader/intersecting_cycles.yul | 10 +- 8 files changed, 388 insertions(+), 81 deletions(-) create mode 100644 test/libyul/yulOptimizerTests/functionSpecializer/intersecting_recursive_cycles.yul diff --git a/docs/bugs.json b/docs/bugs.json index 4b5dd74f8571..4710520cbee9 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,41 @@ [ + { + "uid": "SOL-2026-2", + "name": "UnsoundSpillInMutualRecursion", + "summary": "Local variables of a function involved in mutual recursion may spuriously be moved to fixed memory offsets and overwritten across recursive calls.", + "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", + "severity": "medium", + "conditions": { + "viaIR": true + } + }, + { + "uid": "SOL-2026-1", + "name": "TransientStorageClearingHelperCollision", + "summary": "Clearing both storage and transient storage variables in the same contract may result in only one of these locations being cleared.", + "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", + "severity": "high", + "conditions": { + "viaIR": true, + "evmVersion": ">=cancun" + } + }, + { + "uid": "SOL-2025-1", + "name": "LostStorageArrayWriteOnSlotOverflow", + "summary": "Operations that involve clearing or copying from arrays that straddle the end of storage could result in silent data retention.", + "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", + "severity": "low" + }, { "uid": "SOL-2023-3", "name": "VerbatimInvalidDeduplication", @@ -476,7 +513,7 @@ "uid": "SOL-2017-5", "name": "ZeroFunctionSelector", "summary": "It is possible to craft the name of a function such that it is executed instead of the fallback function in very specific circumstances.", - "description": "If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Trx is sent to the contract without data.", + "description": "If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Ether is sent to the contract without data.", "fixed": "0.4.18", "severity": "very low" }, @@ -561,8 +598,8 @@ { "uid": "SOL-2016-7", "name": "LibrariesNotCallableFromPayableFunctions", - "summary": "Library functions threw an exception when called from a call that received Trx.", - "description": "Library functions are protected against sending them Trx through a call. Since the DELEGATECALL opcode forwards the information about how much Trx was sent with a call, the library function incorrectly assumed that Trx was sent to the library and threw an exception.", + "summary": "Library functions threw an exception when called from a call that received Ether.", + "description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.", "severity": "low", "introduced": "0.4.0", "fixed": "0.4.2" @@ -570,8 +607,8 @@ { "uid": "SOL-2016-6", "name": "SendFailsForZeroEther", - "summary": "The send function did not provide enough gas to the recipient if no Trx was sent with it.", - "description": "The recipient of an Trx transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.", + "summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.", + "description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.", "severity": "low", "fixed": "0.4.0" }, diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 1a210b885c2a..ce37a10ddb4d 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1,6 +1,7 @@ { "0.1.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -23,6 +24,7 @@ }, "0.1.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -45,6 +47,7 @@ }, "0.1.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -67,6 +70,7 @@ }, "0.1.3": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -89,6 +93,7 @@ }, "0.1.4": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -112,6 +117,7 @@ }, "0.1.5": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -135,6 +141,7 @@ }, "0.1.6": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -160,6 +167,7 @@ }, "0.1.7": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -185,6 +193,7 @@ }, "0.2.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -211,6 +220,7 @@ }, "0.2.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -237,6 +247,7 @@ }, "0.2.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -263,6 +274,7 @@ }, "0.3.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -291,6 +303,7 @@ }, "0.3.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -318,6 +331,7 @@ }, "0.3.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -345,6 +359,7 @@ }, "0.3.3": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -371,6 +386,7 @@ }, "0.3.4": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -397,6 +413,7 @@ }, "0.3.5": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -423,6 +440,7 @@ }, "0.3.6": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -447,6 +465,7 @@ }, "0.4.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -471,6 +490,7 @@ }, "0.4.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -495,6 +515,7 @@ }, "0.4.10": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -518,6 +539,7 @@ }, "0.4.11": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -540,6 +562,7 @@ }, "0.4.12": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -561,6 +584,7 @@ }, "0.4.13": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -582,6 +606,7 @@ }, "0.4.14": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -602,6 +627,7 @@ }, "0.4.15": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -621,6 +647,7 @@ }, "0.4.16": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -643,6 +670,7 @@ }, "0.4.17": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -666,6 +694,7 @@ }, "0.4.18": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -688,6 +717,7 @@ }, "0.4.19": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -711,6 +741,7 @@ }, "0.4.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -734,6 +765,7 @@ }, "0.4.20": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -757,6 +789,7 @@ }, "0.4.21": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -780,6 +813,7 @@ }, "0.4.22": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -803,6 +837,7 @@ }, "0.4.23": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -825,6 +860,7 @@ }, "0.4.24": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -847,6 +883,7 @@ }, "0.4.25": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -867,6 +904,7 @@ }, "0.4.26": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -884,6 +922,7 @@ }, "0.4.3": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -906,6 +945,7 @@ }, "0.4.4": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -927,6 +967,7 @@ }, "0.4.5": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -951,6 +992,7 @@ }, "0.4.6": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -974,6 +1016,7 @@ }, "0.4.7": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -997,6 +1040,7 @@ }, "0.4.8": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -1020,6 +1064,7 @@ }, "0.4.9": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "KeccakCaching", "EmptyByteArrayCopy", @@ -1043,6 +1088,7 @@ }, "0.5.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1063,6 +1109,7 @@ }, "0.5.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1083,6 +1130,7 @@ }, "0.5.10": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1101,6 +1149,7 @@ }, "0.5.11": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1118,6 +1167,7 @@ }, "0.5.12": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1135,6 +1185,7 @@ }, "0.5.13": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1152,6 +1203,7 @@ }, "0.5.14": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1171,6 +1223,7 @@ }, "0.5.15": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1189,6 +1242,7 @@ }, "0.5.16": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1206,6 +1260,7 @@ }, "0.5.17": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1222,6 +1277,7 @@ }, "0.5.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1242,6 +1298,7 @@ }, "0.5.3": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1262,6 +1319,7 @@ }, "0.5.4": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1282,6 +1340,7 @@ }, "0.5.5": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1304,6 +1363,7 @@ }, "0.5.6": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1326,6 +1386,7 @@ }, "0.5.7": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "DirtyBytesArrayToStorage", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1346,6 +1407,7 @@ }, "0.5.8": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1367,6 +1429,7 @@ }, "0.5.9": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1387,6 +1450,7 @@ }, "0.6.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1405,6 +1469,7 @@ }, "0.6.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "NestedCalldataArrayAbiReencodingSizeValidation", @@ -1422,6 +1487,7 @@ }, "0.6.10": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1438,6 +1504,7 @@ }, "0.6.11": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1454,6 +1521,7 @@ }, "0.6.12": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1470,6 +1538,7 @@ }, "0.6.2": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1488,6 +1557,7 @@ }, "0.6.3": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1506,6 +1576,7 @@ }, "0.6.4": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1524,6 +1595,7 @@ }, "0.6.5": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1542,6 +1614,7 @@ }, "0.6.6": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1559,6 +1632,7 @@ }, "0.6.7": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1577,6 +1651,7 @@ }, "0.6.8": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1592,6 +1667,7 @@ }, "0.6.9": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1609,6 +1685,7 @@ }, "0.7.0": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1625,6 +1702,7 @@ }, "0.7.1": { "bugs": [ + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1642,6 +1720,8 @@ }, "0.7.2": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1658,6 +1738,8 @@ }, "0.7.3": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1673,6 +1755,8 @@ }, "0.7.4": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1687,6 +1771,8 @@ }, "0.7.5": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1701,6 +1787,8 @@ }, "0.7.6": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1715,6 +1803,8 @@ }, "0.8.0": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1729,6 +1819,8 @@ }, "0.8.1": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1743,6 +1835,8 @@ }, "0.8.10": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1755,6 +1849,8 @@ }, "0.8.11": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1768,6 +1864,8 @@ }, "0.8.12": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1781,6 +1879,8 @@ }, "0.8.13": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1795,6 +1895,8 @@ }, "0.8.14": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1807,6 +1909,8 @@ }, "0.8.15": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1817,6 +1921,8 @@ }, "0.8.16": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1826,6 +1932,8 @@ }, "0.8.17": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess" @@ -1834,6 +1942,8 @@ }, "0.8.18": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess" @@ -1842,6 +1952,8 @@ }, "0.8.19": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess" @@ -1850,6 +1962,8 @@ }, "0.8.2": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1864,6 +1978,8 @@ }, "0.8.20": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess" @@ -1872,46 +1988,75 @@ }, "0.8.21": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication" ], "released": "2023-07-19" }, "0.8.22": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication" ], "released": "2023-10-25" }, "0.8.23": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2023-11-08" }, "0.8.24": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2024-01-25" }, "0.8.25": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2024-03-14" }, "0.8.26": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2024-05-21" }, "0.8.27": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2024-09-04" }, "0.8.28": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2024-10-09" }, "0.8.29": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2025-03-12" }, "0.8.3": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1924,11 +2069,51 @@ "released": "2021-03-23" }, "0.8.30": { - "bugs": [], + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision", + "LostStorageArrayWriteOnSlotOverflow" + ], "released": "2025-05-07" }, + "0.8.31": { + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision", + "LostStorageArrayWriteOnSlotOverflow" + ], + "released": "2025-12-03" + }, + "0.8.32": { + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision" + ], + "released": "2025-12-18" + }, + "0.8.33": { + "bugs": [ + "UnsoundSpillInMutualRecursion", + "TransientStorageClearingHelperCollision" + ], + "released": "2025-12-18" + }, + "0.8.34": { + "bugs": [ + "UnsoundSpillInMutualRecursion" + ], + "released": "2026-02-18" + }, + "0.8.35": { + "bugs": [ + "UnsoundSpillInMutualRecursion" + ], + "released": "2026-04-29" + }, "0.8.4": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", @@ -1941,6 +2126,8 @@ }, "0.8.5": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1954,6 +2141,8 @@ }, "0.8.6": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1967,6 +2156,8 @@ }, "0.8.7": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1980,6 +2171,8 @@ }, "0.8.8": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -1994,6 +2187,8 @@ }, "0.8.9": { "bugs": [ + "UnsoundSpillInMutualRecursion", + "LostStorageArrayWriteOnSlotOverflow", "VerbatimInvalidDeduplication", "FullInlinerNonExpressionSplitArgumentEvaluationOrder", "MissingSideEffectsOnSelectorAccess", @@ -2004,4 +2199,4 @@ ], "released": "2021-09-29" } -} \ No newline at end of file +} diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index 9b69835dc4f1..f4b1ed6c6e41 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -22,10 +22,17 @@ #include #include +#include + #include +#include #include -#include +#include +#include +#include + +#include using namespace solidity; using namespace solidity::yul; @@ -33,43 +40,80 @@ using namespace solidity::util; namespace { -// TODO: This algorithm is non-optimal. -struct CallGraphCycleFinder -{ - CallGraph const& callGraph; - std::set containedInCycle{}; - std::set visited{}; - std::vector currentPath{}; - void visit(FunctionHandle const& _function) +class FunctionToIndexBiMapping +{ +public: + explicit FunctionToIndexBiMapping(std::map> const& _functionCalls) { - if (visited.count(_function)) - return; - if ( - auto it = find(currentPath.begin(), currentPath.end(), _function); - it != currentPath.end() - ) - containedInCycle.insert(it, currentPath.end()); - else + for (auto const& [function, callees]: _functionCalls) { - currentPath.emplace_back(_function); - if (callGraph.functionCalls.count(_function)) - for (auto const& child: callGraph.functionCalls.at(_function)) - visit(child); - currentPath.pop_back(); - visited.insert(_function); + if (m_functionToIndex.try_emplace(function, m_indexToFunction.size()).second) + m_indexToFunction.emplace_back(function); + for (auto const& callee: callees) + if (m_functionToIndex.try_emplace(callee, m_indexToFunction.size()).second) + m_indexToFunction.emplace_back(callee); } } + + FunctionHandle const& indexToFunction(std::size_t const _index) const + { + return m_indexToFunction.at(_index); + } + + std::size_t numFunctions() const + { + return m_indexToFunction.size(); + } + + std::size_t functionToIndex(FunctionHandle const& _functionHandle) const + { + return m_functionToIndex.at(_functionHandle); + } + +private: + std::vector m_indexToFunction; + std::map m_functionToIndex; }; + } std::set CallGraph::recursiveFunctions() const { - CallGraphCycleFinder cycleFinder{*this}; - // Visiting the root only is not enough, since there may be disconnected recursive functions. - for (auto const& call: functionCalls) - cycleFinder.visit(call.first); - return cycleFinder.containedInCycle; + // A function is recursive iff it is part of a non-trivial strongly-connected component of the call graph (a + // mutual-recursion cycle of any length) or it directly calls itself. The SCCs are computed with Tarjan's algorithm. + // Tarjan's implementation requires dense node indices in [0, N), so assign each function handle + // (both callers and callees) a consecutive index. + FunctionToIndexBiMapping const functionIndexBimap(functionCalls); + + // Build list of edges in the call graph + std::vector> indexBasedAdjacencyList(functionIndexBimap.numFunctions()); + for (auto const& [function, callees]: functionCalls) + for (auto const& callee: callees) + indexBasedAdjacencyList[functionIndexBimap.functionToIndex(function)].emplace_back(functionIndexBimap.functionToIndex(callee)); + + // Sort and deduplicate each adjacency list so the self-loop check below can use a binary search + for (auto& callees: indexBasedAdjacencyList) + { + ranges::sort(callees); + callees.erase(ranges::unique(callees), callees.end()); + } + + std::set recursiveFunctionHandleSet; + for (std::vector const& scc: util::computeStronglyConnectedComponents(indexBasedAdjacencyList)) + { + yulAssert(!scc.empty()); + if (scc.size() > 1) + // more than one element in the SCC: everything in it is mutually recursive + for (std::size_t const node: scc) + recursiveFunctionHandleSet.insert(functionIndexBimap.indexToFunction(node)); + else if (ranges::binary_search(indexBasedAdjacencyList[scc.front()], scc.front())) + // self-recursion f -> f + recursiveFunctionHandleSet.insert(functionIndexBimap.indexToFunction(scc.front())); + } + + yulAssert(!recursiveFunctionHandleSet.contains(YulName{}), "the top-level block cannot be recursive"); + return recursiveFunctionHandleSet; } CallGraph CallGraphGenerator::callGraph(Block const& _ast) @@ -99,9 +143,12 @@ void CallGraphGenerator::operator()(ForLoop const& _forLoop) void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition) { + yulAssert( + !m_callGraph.functionCalls.contains(_functionDefinition.name), + "CallGraphGenerator requires a disambiguated AST: duplicate function name " + _functionDefinition.name.str() + "." + ); YulName previousFunction = m_currentFunction; m_currentFunction = _functionDefinition.name; - yulAssert(m_callGraph.functionCalls.count(m_currentFunction) == 0, ""); m_callGraph.functionCalls[m_currentFunction] = {}; ASTWalker::operator()(_functionDefinition); m_currentFunction = previousFunction; diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json index d722677373f0..6650da378c61 100644 --- a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/output.json @@ -1,33 +1,26 @@ { - "contracts": { - "in.sol": { - "C": { - "evm": { - "bytecode": { - "functionDebugData": { - "allocate_unbounded": { - "entryPoint": 32, - "id": null, - "parameterSlots": 0, - "returnSlots": 1 - }, - "revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb": { - "entryPoint": 38, - "id": null, - "parameterSlots": 0, - "returnSlots": 0 - } - }, - "generatedSources": [], - "linkReferences": {}, - "object": "", - "opcodes":"", - "sourceMap": "567:1327:0:-:0;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;" - } - } - } + "errors": [ + { + "component": "general", + "formattedMessage": "YulException: Cannot swap Variable var_m with Variable expr_70: too deep in the stack by 1 slots in [ RET var_m var_m var_m var_m var_m var_m var_m var_m var_m expr_66 var_v15 var_v14 var_v13 var_v12 var_v11 var_v10 var_v9 var_v8 var_v7 var_v6 var_v5 var_v4 var_v3 var_v2 var_v1 expr_70 ] +memoryguard was present. + --> in.sol:46:23: + | +46 | uint256 v18 = seed[17] ^ m; + | ^^^^ + +", + "message": "Cannot swap Variable var_m with Variable expr_70: too deep in the stack by 1 slots in [ RET var_m var_m var_m var_m var_m var_m var_m var_m var_m expr_66 var_v15 var_v14 var_v13 var_v12 var_v11 var_v10 var_v9 var_v8 var_v7 var_v6 var_v5 var_v4 var_v3 var_v2 var_v1 expr_70 ] +memoryguard was present.", + "severity": "error", + "sourceLocation": { + "end": 1488, + "file": "in.sol", + "start": 1484 + }, + "type": "YulException" } - }, + ], "sources": { "in.sol": { "id": 0 diff --git a/test/libyul/yulCallGraph/intersecting_cycles.yul b/test/libyul/yulCallGraph/intersecting_cycles.yul index b38a35720136..1bbdc7429b25 100644 --- a/test/libyul/yulCallGraph/intersecting_cycles.yul +++ b/test/libyul/yulCallGraph/intersecting_cycles.yul @@ -9,4 +9,4 @@ //
: non-recursive // alpha: recursive // beta: recursive -// gamma: non-recursive +// gamma: recursive diff --git a/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul index 1e54d1c51953..d5acc1058c0c 100644 --- a/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul +++ b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul @@ -6,7 +6,7 @@ function rim() { spoke() } } // ---- -//
: non-recursive -// hub: recursive -// spoke: recursive -// rim: recursive +//
+// hub -> spoke, rim (recursive) +// spoke -> hub (recursive) +// rim -> spoke (recursive) diff --git a/test/libyul/yulOptimizerTests/functionSpecializer/intersecting_recursive_cycles.yul b/test/libyul/yulOptimizerTests/functionSpecializer/intersecting_recursive_cycles.yul new file mode 100644 index 000000000000..9bcfd14d26a8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/functionSpecializer/intersecting_recursive_cycles.yul @@ -0,0 +1,35 @@ +{ + // Intersecting recursive cycles e -> b -> d -> e and e -> c -> d -> e share {d, e}, so the whole + // {e, b, c, d} set is a single strongly-connected component and all of them are recursive. + // In particular c() must be recognized as recursive and therefore left un-specialized, despite the + // constant call site c(10). This is a regression test for the call graph cycle-detection bug that + // misclassified some members of intersecting cycles as non-recursive: specializing c(10) here would + // produce code that is not equivalent to the original. + function e(n) { + if eq(n, 0) { leave } + b(sub(n, 1)) + c(sub(n, 1)) + } + function b(n) { d(sub(n, 1)) } + function c(n) { d(sub(n, 1)) } + function d(n) { e(sub(n, 1)) } + c(10) +} +// ---- +// step: functionSpecializer +// +// { +// c(10) +// function e(n) +// { +// if eq(n, 0) { leave } +// b(sub(n, 1)) +// c(sub(n, 1)) +// } +// function b(n_1) +// { d(sub(n_1, 1)) } +// function c(n_2) +// { d(sub(n_2, 1)) } +// function d(n_3) +// { e(sub(n_3, 1)) } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul index cfa226a4e50f..a875fda5280c 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/intersecting_cycles.yul @@ -53,7 +53,7 @@ // step: stackLimitEvader // // { -// mstore(0x40, memoryguard(0xa0)) +// mstore(0x40, memoryguard(128)) // ba() // function ba() // { @@ -64,7 +64,7 @@ // { ba() } // function y() // { -// mstore(0x80, calldataload(mul(1, 4))) +// let a1 := calldataload(mul(1, 4)) // let a2 := calldataload(mul(2, 4)) // let a3 := calldataload(mul(3, 4)) // let a4 := calldataload(mul(4, 4)) @@ -73,7 +73,7 @@ // let a7 := calldataload(mul(7, 4)) // let a8 := calldataload(mul(8, 4)) // let a9 := calldataload(mul(9, 4)) -// mstore(0x80, calldataload(mul(0, 4))) +// a1 := calldataload(mul(0, 4)) // let a10 := calldataload(mul(10, 4)) // let a11 := calldataload(mul(11, 4)) // let a12 := calldataload(mul(12, 4)) @@ -83,7 +83,7 @@ // let a16 := calldataload(mul(16, 4)) // let a17 := calldataload(mul(17, 4)) // z() -// sstore(0, mload(0x80)) +// sstore(0, a1) // sstore(mul(17, 4), a17) // sstore(mul(16, 4), a16) // sstore(mul(15, 4), a15) @@ -100,6 +100,6 @@ // sstore(mul(4, 4), a4) // sstore(mul(3, 4), a3) // sstore(mul(2, 4), a2) -// sstore(mul(1, 4), mload(0x80)) +// sstore(mul(1, 4), a1) // } // } From fb4726dd352e9e87de9c97a4b37429b3cefd0edc Mon Sep 17 00:00:00 2001 From: blishko Date: Mon, 29 Jun 2026 13:16:03 +0200 Subject: [PATCH 10/41] Tests: Add tests demonstrating incorrect behaviour These are tests to demonstrate incorrect processing of inheritance hierarchy in the presence of custom storage layout close to the storage end. (cherry picked from commit 2ac792906bcc3d52f99f3d4df486e15cff03ebce) --- .../args | 1 + .../err | 10 +++ .../output | 90 +++++++++++++++++++ .../stdin | 6 ++ ...tance_constructor_order_calling_revert.sol | 15 ++++ ..._constructor_order_setting_storage_var.sol | 19 ++++ ...ors_with_different_number_of_arguments.sol | 15 ++++ .../inheritance_fallback_calling_revert.sol | 28 ++++++ .../inheritance_immutables.sol | 21 +++++ .../inheritance_modifier_calling_revert.sol | 38 ++++++++ ...le_parents_constructors_with_arguments.sol | 15 ++++ .../inheritance_receive_calling_revert.sol | 28 ++++++ ...nheritance_storage_vars_initialization.sol | 25 ++++++ ...ance_calling_super_specifier_on_caller.sol | 4 + ...tance_calling_super_specifier_on_child.sol | 5 ++ 15 files changed, 320 insertions(+) create mode 100644 test/cmdlineTests/storage_layout_specifier_with_inheritance/args create mode 100644 test/cmdlineTests/storage_layout_specifier_with_inheritance/err create mode 100644 test/cmdlineTests/storage_layout_specifier_with_inheritance/output create mode 100644 test/cmdlineTests/storage_layout_specifier_with_inheritance/stdin create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol create mode 100644 test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol create mode 100644 test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol create mode 100644 test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol diff --git a/test/cmdlineTests/storage_layout_specifier_with_inheritance/args b/test/cmdlineTests/storage_layout_specifier_with_inheritance/args new file mode 100644 index 000000000000..23f63dd3b141 --- /dev/null +++ b/test/cmdlineTests/storage_layout_specifier_with_inheritance/args @@ -0,0 +1 @@ +--storage-layout --pretty-json --json-indent 4 - diff --git a/test/cmdlineTests/storage_layout_specifier_with_inheritance/err b/test/cmdlineTests/storage_layout_specifier_with_inheritance/err new file mode 100644 index 000000000000..12e97c1e44f3 --- /dev/null +++ b/test/cmdlineTests/storage_layout_specifier_with_inheritance/err @@ -0,0 +1,10 @@ +Warning: This contract is very close to the end of storage. This limits its future upgradability. + --> :6:17: + | +6 | contract C is B layout at 2**256 - 2**40 { uint z; } + | ^^^^^^^^^^^^^^^^^^^^^^^^ +Note: There are 0xffFFFFfffc storage slots between this state variable and the end of storage. + --> :4:14: + | +4 | contract A { uint x; } + | ^^^^^^ diff --git a/test/cmdlineTests/storage_layout_specifier_with_inheritance/output b/test/cmdlineTests/storage_layout_specifier_with_inheritance/output new file mode 100644 index 000000000000..2b297eb3d9b7 --- /dev/null +++ b/test/cmdlineTests/storage_layout_specifier_with_inheritance/output @@ -0,0 +1,90 @@ + +======= :A ======= +Contract Storage Layout: +{ + "storage": [ + { + "astId": 3, + "contract": ":A", + "label": "x", + "offset": 0, + "slot": "0", + "type": "t_uint256" + } + ], + "types": { + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } +} + +======= :B ======= +Contract Storage Layout: +{ + "storage": [ + { + "astId": 3, + "contract": ":B", + "label": "x", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 8, + "contract": ":B", + "label": "y", + "offset": 0, + "slot": "1", + "type": "t_uint256" + } + ], + "types": { + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } +} + +======= :C ======= +Contract Storage Layout: +{ + "storage": [ + { + "astId": 21, + "contract": ":C", + "label": "z", + "offset": 0, + "slot": "115792089237316195423570985008687907853269984665640564039457584006813618012160", + "type": "t_uint256" + }, + { + "astId": 8, + "contract": ":C", + "label": "y", + "offset": 0, + "slot": "115792089237316195423570985008687907853269984665640564039457584006813618012161", + "type": "t_uint256" + }, + { + "astId": 3, + "contract": ":C", + "label": "x", + "offset": 0, + "slot": "115792089237316195423570985008687907853269984665640564039457584006813618012162", + "type": "t_uint256" + } + ], + "types": { + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } +} diff --git a/test/cmdlineTests/storage_layout_specifier_with_inheritance/stdin b/test/cmdlineTests/storage_layout_specifier_with_inheritance/stdin new file mode 100644 index 000000000000..5d7bb66b6b79 --- /dev/null +++ b/test/cmdlineTests/storage_layout_specifier_with_inheritance/stdin @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0.0; + +contract A { uint x; } +contract B is A { uint y; } +contract C is B layout at 2**256 - 2**40 { uint z; } diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol new file mode 100644 index 000000000000..608da424c2aa --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol @@ -0,0 +1,15 @@ +contract A { constructor() { revert("A"); } } +contract B is A { constructor() { revert("B"); } } +contract C1 is B layout at 2**256 - 2**42 { constructor() { revert("C"); } } +contract C2 is B { constructor() { revert("C"); } } +contract F { + function withSpecifier() public returns (string memory) { + new C1(); + } + function withoutSpecifier() public returns (string memory) { + new C2(); + } +} +// ---- +// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "C" +// withoutSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "A" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol new file mode 100644 index 000000000000..07943c0c4d1b --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol @@ -0,0 +1,19 @@ +contract A { uint public x; constructor() { x = 1; } } +contract B is A { constructor() { x = x * 10; } } +contract C1 is B layout at 2**256 - 2**40 { constructor() { x = x + 5; } } +contract C2 is B { constructor() { x = x + 5; } } +contract F { + function withSpecifier() public returns (uint) { + return new C1().x(); + } + function withoutSpecifier() public returns (uint) { + return new C2().x(); + } +} +// ---- +// withSpecifier() -> 5 +// gas legacy: 76868 +// gas legacy code: 30000 +// withoutSpecifier() -> 15 +// gas legacy: 77502 +// gas legacy code: 23600 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol new file mode 100644 index 000000000000..8ef84b119bb5 --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol @@ -0,0 +1,15 @@ +contract A { constructor(uint x) {} } +contract B is A(1) { constructor(uint y, uint z) {} } +// Compilation of C1 crashes with "invalid IR generated" with `--via-ir` +//contract C1 is B(2, 3) layout at 2**256 - 2**42 { constructor() {} } +contract C2 is B(2, 3) { constructor() {} } +contract F { + //function withSpecifier() public { + // new C1(); + //} + function withoutSpecifier() public { + new C2(); + } +} +// ---- +// withoutSpecifier() -> diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol new file mode 100644 index 000000000000..9ef90e6ec9cc --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol @@ -0,0 +1,28 @@ +contract A { fallback() external virtual { revert("A"); } } +contract B is A { fallback() external override virtual { revert("B"); } } +// C1 cannot be compiled with `--via-ir` at the moment, compiler crashes because of the storage layout specifier bug +//contract C1 is B layout at 2**256 - 2**42 { fallback() external override virtual { revert("C"); } } +contract C2 is B { fallback() external override virtual { revert("C"); } } +contract F { + function decode(bytes memory data) internal returns (string memory) { + assembly { + // Shift the memory pointer forward by 4 bytes to skip the selector + data := add(data, 4) + } + return abi.decode(data, (string)); + } + //function withSpecifier() public returns (string memory) { + // (bool success, bytes memory data) = address(new C1()).call(hex"beee"); + // require(!success, "Must fail!"); + // return decode(data); + //} + function withoutSpecifier() public returns (string memory) { + (bool success, bytes memory data) = address(new C2()).call(hex"beee"); + require(!success, "Must fail!"); + return decode(data); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// withoutSpecifier() -> 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol new file mode 100644 index 000000000000..256102cc4807 --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol @@ -0,0 +1,21 @@ +contract A { uint public immutable a = 1; } +contract B is A { uint public immutable b = 2; } +// Using specifier, `--via-ir` produces incorrect output, while legacy produces correct one. +//contract C1 is B layout at 2**256 - 2**42 { uint public immutable c = 3; } +contract C2 is B { uint public immutable c = 3; } +contract F { + //function withSpecifier() public returns(uint, uint, uint) { + // C1 c = new C1(); + // return (c.a(), c.b(), c.c()); + //} + function withoutSpecifier() public returns(uint, uint, uint) { + C2 c = new C2(); + return (c.a(), c.b(), c.c()); + } +} +// ---- +// withoutSpecifier() -> 1, 2, 3 +// gas irOptimized: 55246 +// gas irOptimized code: 45400 +// gas legacy: 56451 +// gas legacy code: 62800 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol new file mode 100644 index 000000000000..6f1eb52f3fc4 --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol @@ -0,0 +1,38 @@ +contract A { + modifier m() virtual { + revert("A"); + _; + } +} +contract B is A { + modifier m() virtual override { + revert("B"); + _; + } +} +contract C1 is B layout at 2**256 - 2**42 { + function f() public m {} + modifier m() virtual override { + revert("C"); + _; + } +} +contract C2 is B { + function f() public m {} + modifier m() virtual override { + revert("C"); + _; + } +} + +contract F { + function withSpecifier() public returns (string memory) { + new C1().f(); + } + function withoutSpecifier() public returns (string memory) { + new C2().f(); + } +} +// ---- +// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "A" +// withoutSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol new file mode 100644 index 000000000000..1c1d754a5006 --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol @@ -0,0 +1,15 @@ +contract A { constructor(uint x) {} } +contract B { constructor(uint x) {} } +// Compilation of C1 crashes with "invalid IR generated" with `--via-ir` +//contract C1 is A(2), B(3) layout at 2**256 - 2**42 { constructor(uint x) {} } +contract C2 is A(2), B(3) { constructor(uint x) {} } +contract F { + //function withSpecifier() public { + // new C1(4); + //} + function withoutSpecifier() public { + new C2(4); + } +} +// ---- +// withoutSpecifier() -> diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol new file mode 100644 index 000000000000..ffb706c40702 --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol @@ -0,0 +1,28 @@ +contract A { receive() external payable virtual { revert("A"); } } +contract B is A { receive() external payable override virtual { revert("B"); } } +// C1 cannot be compiled with `via-ir` at the moment, compiler crashes because of the storage layout specifier bug +//contract C1 is B layout at 2**256 - 2**42 { receive() external payable override virtual { revert("C"); } } +contract C2 is B { receive() external payable override virtual { revert("C"); } } +contract F { + function decode(bytes memory data) internal returns (string memory) { + assembly { + // Shift the memory pointer forward by 4 bytes to skip the selector + data := add(data, 4) + } + return abi.decode(data, (string)); + } + //function withSpecifier() public returns (string memory) { + // (bool success, bytes memory data) = payable(new C1()).call{value: 0}(""); + // require(!success, "Must fail!"); + // return decode(data); + //} + function withoutSpecifier() public returns (string memory) { + (bool success, bytes memory data) = payable(new C2()).call{value: 0}(""); + require(!success, "Must fail!"); + return decode(data); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// withoutSpecifier() -> 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol new file mode 100644 index 000000000000..8cf33c8c219c --- /dev/null +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol @@ -0,0 +1,25 @@ +contract A { uint public x = 1; } +contract B is A { uint public y = x * 10; } +// Using specifier, `--via-ir` and legacy produce diffrent output (incorrect in both cases). +//contract C1 is B layout at 2**256 - 2**40 { uint public z = y + 5; } +contract C2 is B { uint public z = y + 5; } +contract F { + //function withSpecifier() public returns (uint, uint, uint) { + // C1 c = new C1(); + // return (c.x(), c.y(), c.z()); + //} + function withoutSpecifier() public returns (uint, uint, uint) { + C2 c = new C2(); + return (c.x(), c.y(), c.z()); + } +} +// ---- +// withoutSpecifier() -> 1, 10, 15 +// gas irOptimized: 121728 +// gas irOptimized code: 27600 +// gas legacy: 123599 +// gas legacy code: 40400 +// gas legacyOptimized: 121916 +// gas legacyOptimized code: 20600 +// gas ssaCFGOptimized: 121687 +// gas ssaCFGOptimized code: 26200 diff --git a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol new file mode 100644 index 000000000000..b00f7f083c4a --- /dev/null +++ b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol @@ -0,0 +1,4 @@ +contract A { function f() public virtual {} } +// Compilation of B1 currently crashes. +//contract B1 is A layout at 2**256 - 2**42 { function f() public override virtual { super.f(); } } +contract B2 is A { function f() public override virtual { super.f(); } } diff --git a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol new file mode 100644 index 000000000000..7d7b70c63aef --- /dev/null +++ b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol @@ -0,0 +1,5 @@ +contract A { function f() public virtual {} } +contract B is A { function f() public override virtual { super.f(); } } +//Compilation of C1 currently crashes. +//contract C1 is B layout at 2**256 - 2**42 {} +contract C2 is B {} From 500d51ce03cafe54d6ecfedce3d746aeac981c47 Mon Sep 17 00:00:00 2001 From: blishko Date: Mon, 29 Jun 2026 13:32:53 +0200 Subject: [PATCH 11/41] Analysis: Fix unintentional reversing of linearized based contracts When the compiler emits a warning about storage base location being too close to storage end, it calls a helper function to determine the last storage variable. This helper function previously unintentionally reversed the linearized based contracts annotation. The function only needs to take reversed **view**. It should not modify the underlying data. (cherry picked from commit 9fb715d81970cd316e09d023ec6d77a8a9555633) --- docs/bugs.json | 10 ++++++++ docs/bugs_by_version.json | 7 ++++++ .../analysis/PostTypeContractLevelChecker.cpp | 3 ++- .../output | 8 +++---- ...tance_constructor_order_calling_revert.sol | 2 +- ..._constructor_order_setting_storage_var.sol | 4 ++-- ...ors_with_different_number_of_arguments.sol | 10 ++++---- .../inheritance_fallback_calling_revert.sol | 14 +++++------ .../inheritance_immutables.sol | 18 ++++++++------ .../inheritance_modifier_calling_revert.sol | 2 +- ...le_parents_constructors_with_arguments.sol | 10 ++++---- .../inheritance_receive_calling_revert.sol | 14 +++++------ ...nheritance_storage_vars_initialization.sol | 24 ++++++++++++------- ...ance_calling_super_specifier_on_caller.sol | 5 ++-- ...tance_calling_super_specifier_on_child.sol | 5 ++-- 15 files changed, 84 insertions(+), 52 deletions(-) diff --git a/docs/bugs.json b/docs/bugs.json index 4710520cbee9..06a74c3b2835 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,14 @@ [ + { + "uid": "SOL-2026-3", + "name": "InheritanceOrderReversalOnStorageEndWarning", + "summary": "Emitting a warning about storage base location being too close to the storage end unintentionally reversed the ``linearizedBaseContracts`` annotation, possibly leading to miscompilation due to reversing the order in which inheritance is resolved.", + "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", + "severity": "medium" + }, { "uid": "SOL-2026-2", "name": "UnsoundSpillInMutualRecursion", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index ce37a10ddb4d..0cc12bfe9eb7 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -2047,6 +2047,7 @@ }, "0.8.29": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" @@ -2070,6 +2071,7 @@ }, "0.8.30": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" @@ -2078,6 +2080,7 @@ }, "0.8.31": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion", "TransientStorageClearingHelperCollision", "LostStorageArrayWriteOnSlotOverflow" @@ -2086,6 +2089,7 @@ }, "0.8.32": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion", "TransientStorageClearingHelperCollision" ], @@ -2093,6 +2097,7 @@ }, "0.8.33": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion", "TransientStorageClearingHelperCollision" ], @@ -2100,12 +2105,14 @@ }, "0.8.34": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion" ], "released": "2026-02-18" }, "0.8.35": { "bugs": [ + "InheritanceOrderReversalOnStorageEndWarning", "UnsoundSpillInMutualRecursion" ], "released": "2026-04-29" diff --git a/libsolidity/analysis/PostTypeContractLevelChecker.cpp b/libsolidity/analysis/PostTypeContractLevelChecker.cpp index c7b7b0961622..ae1246cd4141 100644 --- a/libsolidity/analysis/PostTypeContractLevelChecker.cpp +++ b/libsolidity/analysis/PostTypeContractLevelChecker.cpp @@ -30,6 +30,7 @@ #include #include +#include #include @@ -155,7 +156,7 @@ namespace VariableDeclaration const* findLastStorageVariable(ContractDefinition const& _contract) { - for (ContractDefinition const* baseContract: ranges::actions::reverse(_contract.annotation().linearizedBaseContracts)) + for (ContractDefinition const* baseContract: ranges::views::reverse(_contract.annotation().linearizedBaseContracts)) for (VariableDeclaration const* stateVariable: ranges::actions::reverse(baseContract->stateVariables())) if (stateVariable->referenceLocation() == VariableDeclaration::Location::Unspecified) return stateVariable; diff --git a/test/cmdlineTests/storage_layout_specifier_with_inheritance/output b/test/cmdlineTests/storage_layout_specifier_with_inheritance/output index 2b297eb3d9b7..b9fc73291a08 100644 --- a/test/cmdlineTests/storage_layout_specifier_with_inheritance/output +++ b/test/cmdlineTests/storage_layout_specifier_with_inheritance/output @@ -56,9 +56,9 @@ Contract Storage Layout: { "storage": [ { - "astId": 21, + "astId": 3, "contract": ":C", - "label": "z", + "label": "x", "offset": 0, "slot": "115792089237316195423570985008687907853269984665640564039457584006813618012160", "type": "t_uint256" @@ -72,9 +72,9 @@ Contract Storage Layout: "type": "t_uint256" }, { - "astId": 3, + "astId": 21, "contract": ":C", - "label": "x", + "label": "z", "offset": 0, "slot": "115792089237316195423570985008687907853269984665640564039457584006813618012162", "type": "t_uint256" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol index 608da424c2aa..760439091716 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_calling_revert.sol @@ -11,5 +11,5 @@ contract F { } } // ---- -// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "C" +// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "A" // withoutSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "A" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol index 07943c0c4d1b..d9e597e62e1e 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructor_order_setting_storage_var.sol @@ -11,8 +11,8 @@ contract F { } } // ---- -// withSpecifier() -> 5 -// gas legacy: 76868 +// withSpecifier() -> 15 +// gas legacy: 77592 // gas legacy code: 30000 // withoutSpecifier() -> 15 // gas legacy: 77502 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol index 8ef84b119bb5..16394592d436 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_constructors_with_different_number_of_arguments.sol @@ -1,15 +1,15 @@ contract A { constructor(uint x) {} } contract B is A(1) { constructor(uint y, uint z) {} } -// Compilation of C1 crashes with "invalid IR generated" with `--via-ir` -//contract C1 is B(2, 3) layout at 2**256 - 2**42 { constructor() {} } +contract C1 is B(2, 3) layout at 2**256 - 2**42 { constructor() {} } contract C2 is B(2, 3) { constructor() {} } contract F { - //function withSpecifier() public { - // new C1(); - //} + function withSpecifier() public { + new C1(); + } function withoutSpecifier() public { new C2(); } } // ---- +// withSpecifier() -> // withoutSpecifier() -> diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol index 9ef90e6ec9cc..e0f0cfcfd7bd 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_fallback_calling_revert.sol @@ -1,7 +1,6 @@ contract A { fallback() external virtual { revert("A"); } } contract B is A { fallback() external override virtual { revert("B"); } } -// C1 cannot be compiled with `--via-ir` at the moment, compiler crashes because of the storage layout specifier bug -//contract C1 is B layout at 2**256 - 2**42 { fallback() external override virtual { revert("C"); } } +contract C1 is B layout at 2**256 - 2**42 { fallback() external override virtual { revert("C"); } } contract C2 is B { fallback() external override virtual { revert("C"); } } contract F { function decode(bytes memory data) internal returns (string memory) { @@ -11,11 +10,11 @@ contract F { } return abi.decode(data, (string)); } - //function withSpecifier() public returns (string memory) { - // (bool success, bytes memory data) = address(new C1()).call(hex"beee"); - // require(!success, "Must fail!"); - // return decode(data); - //} + function withSpecifier() public returns (string memory) { + (bool success, bytes memory data) = address(new C1()).call(hex"beee"); + require(!success, "Must fail!"); + return decode(data); + } function withoutSpecifier() public returns (string memory) { (bool success, bytes memory data) = address(new C2()).call(hex"beee"); require(!success, "Must fail!"); @@ -25,4 +24,5 @@ contract F { // ==== // EVMVersion: >homestead // ---- +// withSpecifier() -> 0x20, 1, "C" // withoutSpecifier() -> 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol index 256102cc4807..7c2d65c67681 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_immutables.sol @@ -1,21 +1,25 @@ contract A { uint public immutable a = 1; } contract B is A { uint public immutable b = 2; } -// Using specifier, `--via-ir` produces incorrect output, while legacy produces correct one. -//contract C1 is B layout at 2**256 - 2**42 { uint public immutable c = 3; } +contract C1 is B layout at 2**256 - 2**42 { uint public immutable c = 3; } contract C2 is B { uint public immutable c = 3; } contract F { - //function withSpecifier() public returns(uint, uint, uint) { - // C1 c = new C1(); - // return (c.a(), c.b(), c.c()); - //} + function withSpecifier() public returns(uint, uint, uint) { + C1 c = new C1(); + return (c.a(), c.b(), c.c()); + } function withoutSpecifier() public returns(uint, uint, uint) { C2 c = new C2(); return (c.a(), c.b(), c.c()); } } // ---- +// withSpecifier() -> 1, 2, 3 +// gas irOptimized: 55312 +// gas irOptimized code: 45400 +// gas legacy: 56473 +// gas legacy code: 62800 // withoutSpecifier() -> 1, 2, 3 -// gas irOptimized: 55246 +// gas irOptimized: 55286 // gas irOptimized code: 45400 // gas legacy: 56451 // gas legacy code: 62800 diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol index 6f1eb52f3fc4..6783ce94a295 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_modifier_calling_revert.sol @@ -34,5 +34,5 @@ contract F { } } // ---- -// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "A" +// withSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "C" // withoutSpecifier() -> FAILURE, hex"08c379a0", 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol index 1c1d754a5006..e7032107d06e 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_multiple_parents_constructors_with_arguments.sol @@ -1,15 +1,15 @@ contract A { constructor(uint x) {} } contract B { constructor(uint x) {} } -// Compilation of C1 crashes with "invalid IR generated" with `--via-ir` -//contract C1 is A(2), B(3) layout at 2**256 - 2**42 { constructor(uint x) {} } +contract C1 is A(2), B(3) layout at 2**256 - 2**42 { constructor(uint x) {} } contract C2 is A(2), B(3) { constructor(uint x) {} } contract F { - //function withSpecifier() public { - // new C1(4); - //} + function withSpecifier() public { + new C1(4); + } function withoutSpecifier() public { new C2(4); } } // ---- +// withSpecifier() -> // withoutSpecifier() -> diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol index ffb706c40702..a867a6090819 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_receive_calling_revert.sol @@ -1,7 +1,6 @@ contract A { receive() external payable virtual { revert("A"); } } contract B is A { receive() external payable override virtual { revert("B"); } } -// C1 cannot be compiled with `via-ir` at the moment, compiler crashes because of the storage layout specifier bug -//contract C1 is B layout at 2**256 - 2**42 { receive() external payable override virtual { revert("C"); } } +contract C1 is B layout at 2**256 - 2**42 { receive() external payable override virtual { revert("C"); } } contract C2 is B { receive() external payable override virtual { revert("C"); } } contract F { function decode(bytes memory data) internal returns (string memory) { @@ -11,11 +10,11 @@ contract F { } return abi.decode(data, (string)); } - //function withSpecifier() public returns (string memory) { - // (bool success, bytes memory data) = payable(new C1()).call{value: 0}(""); - // require(!success, "Must fail!"); - // return decode(data); - //} + function withSpecifier() public returns (string memory) { + (bool success, bytes memory data) = payable(new C1()).call{value: 0}(""); + require(!success, "Must fail!"); + return decode(data); + } function withoutSpecifier() public returns (string memory) { (bool success, bytes memory data) = payable(new C2()).call{value: 0}(""); require(!success, "Must fail!"); @@ -25,4 +24,5 @@ contract F { // ==== // EVMVersion: >homestead // ---- +// withSpecifier() -> 0x20, 1, "C" // withoutSpecifier() -> 0x20, 1, "C" diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol index 8cf33c8c219c..382c84bde695 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol @@ -1,25 +1,33 @@ contract A { uint public x = 1; } contract B is A { uint public y = x * 10; } -// Using specifier, `--via-ir` and legacy produce diffrent output (incorrect in both cases). -//contract C1 is B layout at 2**256 - 2**40 { uint public z = y + 5; } +contract C1 is B layout at 2**256 - 2**40 { uint public z = y + 5; } contract C2 is B { uint public z = y + 5; } contract F { - //function withSpecifier() public returns (uint, uint, uint) { - // C1 c = new C1(); - // return (c.x(), c.y(), c.z()); - //} + function withSpecifier() public returns (uint, uint, uint) { + C1 c = new C1(); + return (c.x(), c.y(), c.z()); + } function withoutSpecifier() public returns (uint, uint, uint) { C2 c = new C2(); return (c.x(), c.y(), c.z()); } } // ---- +// withSpecifier() -> 1, 10, 15 +// gas irOptimized: 121822 +// gas irOptimized code: 30800 +// gas legacy: 123715 +// gas legacy code: 63400 +// gas legacyOptimized: 121966 +// gas legacyOptimized code: 23800 +// gas ssaCFGOptimized: 121771 +// gas ssaCFGOptimized code: 29400 // withoutSpecifier() -> 1, 10, 15 -// gas irOptimized: 121728 +// gas irOptimized: 121768 // gas irOptimized code: 27600 // gas legacy: 123599 // gas legacy code: 40400 // gas legacyOptimized: 121916 // gas legacyOptimized code: 20600 -// gas ssaCFGOptimized: 121687 +// gas ssaCFGOptimized: 121722 // gas ssaCFGOptimized code: 26200 diff --git a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol index b00f7f083c4a..801aa813091b 100644 --- a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol +++ b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_caller.sol @@ -1,4 +1,5 @@ contract A { function f() public virtual {} } -// Compilation of B1 currently crashes. -//contract B1 is A layout at 2**256 - 2**42 { function f() public override virtual { super.f(); } } +contract B1 is A layout at 2**256 - 2**42 { function f() public override virtual { super.f(); } } contract B2 is A { function f() public override virtual { super.f(); } } +// ---- +// Warning 3495: (63-87): This contract is very close to the end of storage. This limits its future upgradability. diff --git a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol index 7d7b70c63aef..030a54f084a0 100644 --- a/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol +++ b/test/libsolidity/syntaxTests/storageLayoutSpecifier/inheritance_calling_super_specifier_on_child.sol @@ -1,5 +1,6 @@ contract A { function f() public virtual {} } contract B is A { function f() public override virtual { super.f(); } } -//Compilation of C1 currently crashes. -//contract C1 is B layout at 2**256 - 2**42 {} +contract C1 is B layout at 2**256 - 2**42 {} contract C2 is B {} +// ---- +// Warning 3495: (135-159): This contract is very close to the end of storage. This limits its future upgradability. From 3d567ea18fcb1eba4626b3fc16abf02d4745288e Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:26:47 +0200 Subject: [PATCH 12/41] Add disambiguator prerequisite to call graph generator (cherry picked from commit 58bc9f8f20a363cdd1e741c8a4a302184d78a7b9) --- libyul/optimiser/CallGraphGenerator.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index 722cbba4a796..ca3f11434eba 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -17,6 +17,8 @@ // SPDX-License-Identifier: GPL-3.0 /** * Specific AST walker that generates the call graph. + * + * Prerequisites: Disambiguator */ #pragma once From e1690728493cf890527ba9a78924e01fecaef9b5 Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:55:40 +0200 Subject: [PATCH 13/41] Call graph test: output the whole call graph (cherry picked from commit 56edc07403ac1e37650e9915e9a8425a594ab5f8) --- test/libyul/CallGraphTest.cpp | 44 +++++++++++++++---- test/libyul/yulCallGraph/ambiguous_names.yul | 2 +- .../yulCallGraph/ambiguous_names_nested.yul | 2 +- .../branching_into_and_out_of_scc.yul | 8 ++-- .../builtin_in_recursive_cycle.yul | 8 ++++ test/libyul/yulCallGraph/caller_into_scc.yul | 8 ++-- .../yulCallGraph/direct_plus_mutual.yul | 8 ++-- test/libyul/yulCallGraph/direct_self_call.yul | 4 +- .../libyul/yulCallGraph/duplicate_callees.yul | 6 +-- .../yulCallGraph/intersecting_cycles.yul | 8 ++-- .../intersecting_cycles_visitation_order.yul | 6 +-- test/libyul/yulCallGraph/leaf.yul | 4 +- test/libyul/yulCallGraph/loop_in_main.yul | 5 +++ .../yulCallGraph/loop_in_nested_function.yul | 14 ++++++ test/libyul/yulCallGraph/mixed.yul | 8 ++-- .../yulCallGraph/multi_callee_three_scc.yul | 8 ++-- .../multiple_distinct_callees.yul | 8 ++-- test/libyul/yulCallGraph/multiple_loops.yul | 13 ++++++ .../yulCallGraph/mutual_three_cycle.yul | 8 ++-- test/libyul/yulCallGraph/mutual_two_cycle.yul | 6 +-- test/libyul/yulCallGraph/nested_functions.yul | 8 ++-- .../yulCallGraph/non_recursive_chain.yul | 8 ++-- test/libyul/yulCallGraph/self_and_other.yul | 6 +-- test/libyul/yulCallGraph/shared_callee.yul | 8 ++-- 24 files changed, 137 insertions(+), 71 deletions(-) create mode 100644 test/libyul/yulCallGraph/builtin_in_recursive_cycle.yul create mode 100644 test/libyul/yulCallGraph/loop_in_main.yul create mode 100644 test/libyul/yulCallGraph/loop_in_nested_function.yul create mode 100644 test/libyul/yulCallGraph/multiple_loops.yul diff --git a/test/libyul/CallGraphTest.cpp b/test/libyul/CallGraphTest.cpp index 5fad64777b44..b1eb34468284 100644 --- a/test/libyul/CallGraphTest.cpp +++ b/test/libyul/CallGraphTest.cpp @@ -25,14 +25,21 @@ #include #include +#include +#include +#include #include +#include #include #include +#include + #include using namespace solidity; +using namespace solidity::util; using namespace solidity::langutil; using namespace solidity::yul; using namespace solidity::yul::test; @@ -87,23 +94,42 @@ TestCase::TestResult CallGraphTest::run(std::ostream& _stream, std::string const try { CallGraph const callGraph = CallGraphGenerator::callGraph(root); - std::set const recursive = callGraph.recursiveFunctions(); - - auto classification = [&](FunctionHandle const& _function) { - return recursive.contains(_function) ? "recursive" : "non-recursive"; + std::set const recursiveFunctionHandles = callGraph.recursiveFunctions(); + Dialect const& dialect = yulStack.dialect(); + + auto printNode = [&](YulName const _name) { + FunctionHandle const handle{_name}; + out << (_name == YulName{} ? "
" : _name.str()); + + std::vector annotations; + if (recursiveFunctionHandles.contains(handle)) + annotations.emplace_back("recursive"); + if (callGraph.functionsWithLoops.contains(_name)) + annotations.emplace_back("loops"); + if (!annotations.empty()) + out << " (" << joinHumanReadable(annotations) << ")"; + + std::vector renderedCallees; + for (FunctionHandle const& callee: callGraph.functionCalls.at(handle)) + renderedCallees.emplace_back(resolveFunctionName(callee, dialect)); + if (!renderedCallees.empty()) + out << " -> " << joinHumanReadable(renderedCallees); + + out << "\n"; }; FunctionNameCollector collector; collector(root); - // The outermost (non-function) context is denoted by the empty name in the call graph. - out << "
: " << classification(YulName{}) << "\n"; + printNode(YulName{}); for (YulName const& name: collector.names) - out << name.str() << ": " << classification(name) << "\n"; + printNode(name); + + yulAssert(!recursiveFunctionHandles.contains(YulName{}), "main cannot be recursive"); } - catch (langutil::InternalCompilerError const& _error) + catch (YulAssertion const& _error) { - out << "InternalCompilerError: " << (_error.comment() ? *_error.comment() : "") << "\n"; + out << "YulAssertion: " << (_error.comment() ? *_error.comment() : "") << "\n"; } m_obtainedResult = out.str(); diff --git a/test/libyul/yulCallGraph/ambiguous_names.yul b/test/libyul/yulCallGraph/ambiguous_names.yul index f8cf3924360e..4cb2703f0e22 100644 --- a/test/libyul/yulCallGraph/ambiguous_names.yul +++ b/test/libyul/yulCallGraph/ambiguous_names.yul @@ -3,4 +3,4 @@ { function f() {} } } // ---- -// InternalCompilerError: CallGraphGenerator requires a disambiguated AST: duplicate function name f. +// YulAssertion: CallGraphGenerator requires a disambiguated AST: duplicate function name f. diff --git a/test/libyul/yulCallGraph/ambiguous_names_nested.yul b/test/libyul/yulCallGraph/ambiguous_names_nested.yul index 4cc98c7e1097..b4df07554769 100644 --- a/test/libyul/yulCallGraph/ambiguous_names_nested.yul +++ b/test/libyul/yulCallGraph/ambiguous_names_nested.yul @@ -9,4 +9,4 @@ } } // ---- -// InternalCompilerError: CallGraphGenerator requires a disambiguated AST: duplicate function name g. +// YulAssertion: CallGraphGenerator requires a disambiguated AST: duplicate function name g. diff --git a/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul b/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul index 82eccf25b6f4..50d500e8c3a6 100644 --- a/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul +++ b/test/libyul/yulCallGraph/branching_into_and_out_of_scc.yul @@ -7,7 +7,7 @@ function h() {} } // ---- -//
: non-recursive -// f: recursive -// g: recursive -// h: non-recursive +//
+// f (recursive) -> g, h +// g (recursive) -> f +// h diff --git a/test/libyul/yulCallGraph/builtin_in_recursive_cycle.yul b/test/libyul/yulCallGraph/builtin_in_recursive_cycle.yul new file mode 100644 index 000000000000..018bdd9c7771 --- /dev/null +++ b/test/libyul/yulCallGraph/builtin_in_recursive_cycle.yul @@ -0,0 +1,8 @@ +{ + function f() { g() mstore(0, 1) } + function g() { f() } +} +// ---- +//
+// f (recursive) -> g, mstore +// g (recursive) -> f diff --git a/test/libyul/yulCallGraph/caller_into_scc.yul b/test/libyul/yulCallGraph/caller_into_scc.yul index 4167f3caf651..b8007d9bf320 100644 --- a/test/libyul/yulCallGraph/caller_into_scc.yul +++ b/test/libyul/yulCallGraph/caller_into_scc.yul @@ -4,7 +4,7 @@ function h() { g() } } // ---- -//
: non-recursive -// f: non-recursive -// g: recursive -// h: recursive +//
+// f -> g +// g (recursive) -> h +// h (recursive) -> g diff --git a/test/libyul/yulCallGraph/direct_plus_mutual.yul b/test/libyul/yulCallGraph/direct_plus_mutual.yul index f75690c6ba32..0d52709bef1e 100644 --- a/test/libyul/yulCallGraph/direct_plus_mutual.yul +++ b/test/libyul/yulCallGraph/direct_plus_mutual.yul @@ -4,7 +4,7 @@ function h() { g() } } // ---- -//
: non-recursive -// f: recursive -// g: recursive -// h: recursive +//
+// f (recursive) -> f +// g (recursive) -> h +// h (recursive) -> g diff --git a/test/libyul/yulCallGraph/direct_self_call.yul b/test/libyul/yulCallGraph/direct_self_call.yul index 7a57413da61b..0a902ab41c00 100644 --- a/test/libyul/yulCallGraph/direct_self_call.yul +++ b/test/libyul/yulCallGraph/direct_self_call.yul @@ -2,5 +2,5 @@ function f() { f() } } // ---- -//
: non-recursive -// f: recursive +//
+// f (recursive) -> f diff --git a/test/libyul/yulCallGraph/duplicate_callees.yul b/test/libyul/yulCallGraph/duplicate_callees.yul index 549ca59daa05..df6950c6607e 100644 --- a/test/libyul/yulCallGraph/duplicate_callees.yul +++ b/test/libyul/yulCallGraph/duplicate_callees.yul @@ -6,6 +6,6 @@ function g() {} } // ---- -//
: non-recursive -// f: non-recursive -// g: non-recursive +//
+// f -> g +// g diff --git a/test/libyul/yulCallGraph/intersecting_cycles.yul b/test/libyul/yulCallGraph/intersecting_cycles.yul index 1bbdc7429b25..baaa4ecaef7a 100644 --- a/test/libyul/yulCallGraph/intersecting_cycles.yul +++ b/test/libyul/yulCallGraph/intersecting_cycles.yul @@ -6,7 +6,7 @@ function gamma() { beta() } } // ---- -//
: non-recursive -// alpha: recursive -// beta: recursive -// gamma: recursive +//
+// alpha (recursive) -> beta, gamma +// beta (recursive) -> alpha +// gamma (recursive) -> beta diff --git a/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul index d5acc1058c0c..3ae2e10fc95f 100644 --- a/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul +++ b/test/libyul/yulCallGraph/intersecting_cycles_visitation_order.yul @@ -7,6 +7,6 @@ } // ---- //
-// hub -> spoke, rim (recursive) -// spoke -> hub (recursive) -// rim -> spoke (recursive) +// hub (recursive) -> spoke, rim +// spoke (recursive) -> hub +// rim (recursive) -> spoke diff --git a/test/libyul/yulCallGraph/leaf.yul b/test/libyul/yulCallGraph/leaf.yul index b44705017a74..06a0ba5ad89c 100644 --- a/test/libyul/yulCallGraph/leaf.yul +++ b/test/libyul/yulCallGraph/leaf.yul @@ -2,5 +2,5 @@ function f() {} } // ---- -//
: non-recursive -// f: non-recursive +//
+// f diff --git a/test/libyul/yulCallGraph/loop_in_main.yul b/test/libyul/yulCallGraph/loop_in_main.yul new file mode 100644 index 000000000000..debeabb9cf52 --- /dev/null +++ b/test/libyul/yulCallGraph/loop_in_main.yul @@ -0,0 +1,5 @@ +{ + for {} 1 {} {} +} +// ---- +//
(loops) diff --git a/test/libyul/yulCallGraph/loop_in_nested_function.yul b/test/libyul/yulCallGraph/loop_in_nested_function.yul new file mode 100644 index 000000000000..064933b5f1c0 --- /dev/null +++ b/test/libyul/yulCallGraph/loop_in_nested_function.yul @@ -0,0 +1,14 @@ +{ + function outer() { + function inner() { + for {} 1 {} {} + outer() + } + inner() + } + outer() +} +// ---- +//
-> outer +// outer (recursive) -> inner +// inner (recursive, loops) -> outer diff --git a/test/libyul/yulCallGraph/mixed.yul b/test/libyul/yulCallGraph/mixed.yul index e1bd7247fa38..c3541316f360 100644 --- a/test/libyul/yulCallGraph/mixed.yul +++ b/test/libyul/yulCallGraph/mixed.yul @@ -4,7 +4,7 @@ function h() {} } // ---- -//
: non-recursive -// f: recursive -// g: recursive -// h: non-recursive +//
+// f (recursive) -> g +// g (recursive) -> f +// h diff --git a/test/libyul/yulCallGraph/multi_callee_three_scc.yul b/test/libyul/yulCallGraph/multi_callee_three_scc.yul index dd1396a6e314..0d787837a7ee 100644 --- a/test/libyul/yulCallGraph/multi_callee_three_scc.yul +++ b/test/libyul/yulCallGraph/multi_callee_three_scc.yul @@ -7,7 +7,7 @@ function c() { b() } } // ---- -//
: non-recursive -// a: recursive -// b: recursive -// c: recursive +//
+// a (recursive) -> b, c +// b (recursive) -> a +// c (recursive) -> b diff --git a/test/libyul/yulCallGraph/multiple_distinct_callees.yul b/test/libyul/yulCallGraph/multiple_distinct_callees.yul index e67e01ca98f4..c08aba6e68d0 100644 --- a/test/libyul/yulCallGraph/multiple_distinct_callees.yul +++ b/test/libyul/yulCallGraph/multiple_distinct_callees.yul @@ -7,7 +7,7 @@ function h() {} } // ---- -//
: non-recursive -// f: non-recursive -// g: non-recursive -// h: non-recursive +//
+// f -> g, h +// g +// h diff --git a/test/libyul/yulCallGraph/multiple_loops.yul b/test/libyul/yulCallGraph/multiple_loops.yul new file mode 100644 index 000000000000..144e638de251 --- /dev/null +++ b/test/libyul/yulCallGraph/multiple_loops.yul @@ -0,0 +1,13 @@ +{ + for {} 1 {} {} + function f() { + for {} 1 {} { + for {} 1 {} {} + } + } + function g() {} +} +// ---- +//
(loops) +// f (loops) +// g diff --git a/test/libyul/yulCallGraph/mutual_three_cycle.yul b/test/libyul/yulCallGraph/mutual_three_cycle.yul index 1cdeb220de2b..f2d4af836885 100644 --- a/test/libyul/yulCallGraph/mutual_three_cycle.yul +++ b/test/libyul/yulCallGraph/mutual_three_cycle.yul @@ -4,7 +4,7 @@ function h() { f() } } // ---- -//
: non-recursive -// f: recursive -// g: recursive -// h: recursive +//
+// f (recursive) -> g +// g (recursive) -> h +// h (recursive) -> f diff --git a/test/libyul/yulCallGraph/mutual_two_cycle.yul b/test/libyul/yulCallGraph/mutual_two_cycle.yul index 123e266982cf..cc187f1c140f 100644 --- a/test/libyul/yulCallGraph/mutual_two_cycle.yul +++ b/test/libyul/yulCallGraph/mutual_two_cycle.yul @@ -3,6 +3,6 @@ function g() { f() } } // ---- -//
: non-recursive -// f: recursive -// g: recursive +//
+// f (recursive) -> g +// g (recursive) -> f diff --git a/test/libyul/yulCallGraph/nested_functions.yul b/test/libyul/yulCallGraph/nested_functions.yul index 8bd506003842..5e7068b758fd 100644 --- a/test/libyul/yulCallGraph/nested_functions.yul +++ b/test/libyul/yulCallGraph/nested_functions.yul @@ -10,7 +10,7 @@ a() } // ---- -//
: non-recursive -// a: recursive -// b: recursive -// c: recursive +//
-> a +// a (recursive) -> b +// b (recursive) -> c +// c (recursive) -> a diff --git a/test/libyul/yulCallGraph/non_recursive_chain.yul b/test/libyul/yulCallGraph/non_recursive_chain.yul index 20086c873c71..6d3f95ef1a5b 100644 --- a/test/libyul/yulCallGraph/non_recursive_chain.yul +++ b/test/libyul/yulCallGraph/non_recursive_chain.yul @@ -4,7 +4,7 @@ function h() {} } // ---- -//
: non-recursive -// f: non-recursive -// g: non-recursive -// h: non-recursive +//
+// f -> g +// g -> h +// h diff --git a/test/libyul/yulCallGraph/self_and_other.yul b/test/libyul/yulCallGraph/self_and_other.yul index be843421f297..f60f8723d946 100644 --- a/test/libyul/yulCallGraph/self_and_other.yul +++ b/test/libyul/yulCallGraph/self_and_other.yul @@ -6,6 +6,6 @@ function g() {} } // ---- -//
: non-recursive -// f: recursive -// g: non-recursive +//
+// f (recursive) -> f, g +// g diff --git a/test/libyul/yulCallGraph/shared_callee.yul b/test/libyul/yulCallGraph/shared_callee.yul index 4b28e34f2b49..88776dd0a912 100644 --- a/test/libyul/yulCallGraph/shared_callee.yul +++ b/test/libyul/yulCallGraph/shared_callee.yul @@ -4,7 +4,7 @@ function h() {} } // ---- -//
: non-recursive -// f: non-recursive -// g: non-recursive -// h: non-recursive +//
+// f -> h +// g -> h +// h From 0758b9e0012a3f9ae227d205df942013bf535d5f Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:43:18 +0200 Subject: [PATCH 14/41] Add `resolveFunctionName` overload dealing with instances of `FunctionHandle` to utilities (cherry picked from commit 47e36a38bc869674751bee0990df83984b195b74) --- libyul/Utilities.cpp | 9 +++++++++ libyul/Utilities.h | 1 + 2 files changed, 10 insertions(+) diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index e7caf1b2dff4..5c87cb75a228 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -251,6 +251,15 @@ std::string_view yul::resolveFunctionName(FunctionName const& _functionName, Dia return std::visit(visitor, _functionName); } +std::string_view yul::resolveFunctionName(FunctionHandle const& _functionHandle, Dialect const& _dialect) +{ + GenericVisitor visitor{ + [&](YulName const& _name) -> std::string const& { return _name.str(); }, + [&](BuiltinHandle const& _handle) -> std::string const& { return _dialect.builtin(_handle).name; } + }; + return std::visit(visitor, _functionHandle); +} + BuiltinFunction const* yul::resolveBuiltinFunction(FunctionName const& _functionName, Dialect const& _dialect) { GenericVisitor visitor{ diff --git a/libyul/Utilities.h b/libyul/Utilities.h index 64b5f456ac1f..f3def59ab4f5 100644 --- a/libyul/Utilities.h +++ b/libyul/Utilities.h @@ -89,6 +89,7 @@ struct SwitchCaseCompareByLiteralValue }; std::string_view resolveFunctionName(FunctionName const& _functionName, Dialect const& _dialect); +std::string_view resolveFunctionName(FunctionHandle const& _functionHandle, Dialect const& _dialect); BuiltinFunction const* resolveBuiltinFunction(FunctionName const& _functionName, Dialect const& _dialect); BuiltinFunctionForEVM const* resolveBuiltinFunctionForEVM(FunctionName const& _functionName, EVMDialect const& _dialect); From 28cc279841aa2754f19152f43850c1fc3a57dfca Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:54:09 +0200 Subject: [PATCH 15/41] Yul: Call graph generator asserts that builtins are never recursive (cherry picked from commit 422fe848b5c57f2b99632d990a9ecce7ca70239e) --- libyul/optimiser/CallGraphGenerator.cpp | 2 ++ libyul/optimiser/CallGraphGenerator.h | 3 +++ test/libyul/yulCallGraph/builtin_call.yul | 9 +++++++++ 3 files changed, 14 insertions(+) create mode 100644 test/libyul/yulCallGraph/builtin_call.yul diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index f4b1ed6c6e41..3316038cbf36 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -113,6 +113,8 @@ std::set CallGraph::recursiveFunctions() const } yulAssert(!recursiveFunctionHandleSet.contains(YulName{}), "the top-level block cannot be recursive"); + for (FunctionHandle const& recursiveFunction: recursiveFunctionHandleSet) + yulAssert(std::holds_alternative(recursiveFunction), "a builtin cannot be recursive"); return recursiveFunctionHandleSet; } diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index ca3f11434eba..bb5adb235e3b 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -40,6 +40,9 @@ struct CallGraph /// @returns the set of functions contained in cycles in the call graph, i.e. /// functions that are part of a (mutual) recursion. /// Note that this does not include functions that merely call recursive functions. + /// Postcondition: the result never contains a builtin. At Yul level builtins cannot call other + /// functions, so they have no outgoing call-graph edges and can be neither mutually nor directly + /// recursive. std::set recursiveFunctions() const; }; diff --git a/test/libyul/yulCallGraph/builtin_call.yul b/test/libyul/yulCallGraph/builtin_call.yul new file mode 100644 index 000000000000..8a38e75cac61 --- /dev/null +++ b/test/libyul/yulCallGraph/builtin_call.yul @@ -0,0 +1,9 @@ +{ + function f() { + mstore(0, 1) + } + f() +} +// ---- +//
-> f +// f -> mstore From 4adcd1ae65a48d5078409cd87a31749a0e99a0d5 Mon Sep 17 00:00:00 2001 From: clonker <1685266+clonker@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:37:56 +0200 Subject: [PATCH 16/41] Yul: Call graph generator raises proper exception if input is not disambiguated (cherry picked from commit d32ee36ca1b42d99a89d2af1d5d9de3db9235e7d) --- libyul/optimiser/CallGraphGenerator.cpp | 3 ++- libyul/optimiser/CallGraphGenerator.h | 4 ++++ test/libyul/CallGraphTest.cpp | 4 ++-- test/libyul/yulCallGraph/ambiguous_names.yul | 2 +- test/libyul/yulCallGraph/ambiguous_names_nested.yul | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index 3316038cbf36..15a31d05c7f6 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -145,8 +145,9 @@ void CallGraphGenerator::operator()(ForLoop const& _forLoop) void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition) { - yulAssert( + solRequire( !m_callGraph.functionCalls.contains(_functionDefinition.name), + InputNotDisambiguatedException, "CallGraphGenerator requires a disambiguated AST: duplicate function name " + _functionDefinition.name.str() + "." ); YulName previousFunction = m_currentFunction; diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index bb5adb235e3b..f89e5a04cafa 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -52,10 +52,14 @@ struct CallGraph * It also generates information about which functions contain for loops. * * The outermost (non-function) context is denoted by the empty string. + * + * @throws InputNotDisambiguatedException if input is not disambiguated */ class CallGraphGenerator: public ASTWalker { public: + struct InputNotDisambiguatedException: virtual YulException {}; + static CallGraph callGraph(Block const& _ast); using ASTWalker::operator(); diff --git a/test/libyul/CallGraphTest.cpp b/test/libyul/CallGraphTest.cpp index b1eb34468284..f55ff2475144 100644 --- a/test/libyul/CallGraphTest.cpp +++ b/test/libyul/CallGraphTest.cpp @@ -127,9 +127,9 @@ TestCase::TestResult CallGraphTest::run(std::ostream& _stream, std::string const yulAssert(!recursiveFunctionHandles.contains(YulName{}), "main cannot be recursive"); } - catch (YulAssertion const& _error) + catch (CallGraphGenerator::InputNotDisambiguatedException const& _exception) { - out << "YulAssertion: " << (_error.comment() ? *_error.comment() : "") << "\n"; + out << "InputNotDisambiguatedException: " << (_exception.comment() ? *_exception.comment() : "") << "\n"; } m_obtainedResult = out.str(); diff --git a/test/libyul/yulCallGraph/ambiguous_names.yul b/test/libyul/yulCallGraph/ambiguous_names.yul index 4cb2703f0e22..bf1bdbd7ca35 100644 --- a/test/libyul/yulCallGraph/ambiguous_names.yul +++ b/test/libyul/yulCallGraph/ambiguous_names.yul @@ -3,4 +3,4 @@ { function f() {} } } // ---- -// YulAssertion: CallGraphGenerator requires a disambiguated AST: duplicate function name f. +// InputNotDisambiguatedException: CallGraphGenerator requires a disambiguated AST: duplicate function name f. diff --git a/test/libyul/yulCallGraph/ambiguous_names_nested.yul b/test/libyul/yulCallGraph/ambiguous_names_nested.yul index b4df07554769..3a843bdad9c5 100644 --- a/test/libyul/yulCallGraph/ambiguous_names_nested.yul +++ b/test/libyul/yulCallGraph/ambiguous_names_nested.yul @@ -9,4 +9,4 @@ } } // ---- -// YulAssertion: CallGraphGenerator requires a disambiguated AST: duplicate function name g. +// InputNotDisambiguatedException: CallGraphGenerator requires a disambiguated AST: duplicate function name g. From 6fa56910ad39e879aa5b745873fbe32c6bab9390 Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 16 Jul 2026 16:40:36 +0800 Subject: [PATCH 17/41] tests: adapt security backports to TRON 0.8.30 (cherry picked from commit 11815d27cdceaf8e4fb242216f0511c9706b2047) --- .../args | 2 +- .../output | 40 ++++++++++++++++--- ...nheritance_storage_vars_initialization.sol | 4 -- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/args b/test/cmdlineTests/storage_transient_storage_collision_ir_output/args index b7ec904f1024..1b58291152e4 100644 --- a/test/cmdlineTests/storage_transient_storage_collision_ir_output/args +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/args @@ -1 +1 @@ ---ir-optimized --evm-version cancun --debug-info none +--ir-optimized --evm-version cancun --debug-info none --no-cbor-metadata diff --git a/test/cmdlineTests/storage_transient_storage_collision_ir_output/output b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output index dd8abeb333b0..50f2fcb90fc7 100644 --- a/test/cmdlineTests/storage_transient_storage_collision_ir_output/output +++ b/test/cmdlineTests/storage_transient_storage_collision_ir_output/output @@ -6,7 +6,15 @@ object "C_25" { mstore(64, memoryguard(0x80)) if callvalue() { - revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + revert_error_8061d93638ee074115f764ac3d48f9e554bb1b90dfef10edac8a99bf7cd9567b() + } + if calltokenid() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() + } + if calltokenvalue() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() } constructor_C() let _1 := allocate_unbounded() @@ -15,7 +23,9 @@ object "C_25" { } function allocate_unbounded() -> memPtr { memPtr := mload(64) } - function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + function revert_error_8061d93638ee074115f764ac3d48f9e554bb1b90dfef10edac8a99bf7cd9567b() + { revert(0, 0) } + function revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() { revert(0, 0) } function shift_left(value) -> newValue { newValue := shl(0, value) } @@ -68,7 +78,9 @@ object "C_25" { { newValue := shr(224, value) } function allocate_unbounded() -> memPtr { memPtr := mload(64) } - function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + function revert_error_8061d93638ee074115f764ac3d48f9e554bb1b90dfef10edac8a99bf7cd9567b() + { revert(0, 0) } + function revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() { revert(0, 0) } function revert_error_dbdddcbe895c83990c08b3492a0e83918d802a52331272ac6fdb6a7c4aea3b1b() { revert(0, 0) } @@ -94,7 +106,15 @@ object "C_25" { { if callvalue() { - revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + revert_error_8061d93638ee074115f764ac3d48f9e554bb1b90dfef10edac8a99bf7cd9567b() + } + if calltokenid() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() + } + if calltokenvalue() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() } abi_decode(4, calldatasize()) let ret := fun_foo() @@ -124,7 +144,15 @@ object "C_25" { { if callvalue() { - revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() + revert_error_8061d93638ee074115f764ac3d48f9e554bb1b90dfef10edac8a99bf7cd9567b() + } + if calltokenid() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() + } + if calltokenvalue() + { + revert_error_9a62d7e684059bba6a84639281e9dae3baecf10b1b86b3ab661f6dcc72bcade3() } abi_decode(4, calldatasize()) let ret := getter_fun_varStorage() @@ -219,6 +247,6 @@ object "C_25" { leave } } - data ".metadata" hex"" + data ".metadata" hex"" } } diff --git a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol index 382c84bde695..87fbe48bc973 100644 --- a/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol +++ b/test/libsolidity/semanticTests/storageLayoutSpecifier/inheritance_storage_vars_initialization.sol @@ -20,8 +20,6 @@ contract F { // gas legacy code: 63400 // gas legacyOptimized: 121966 // gas legacyOptimized code: 23800 -// gas ssaCFGOptimized: 121771 -// gas ssaCFGOptimized code: 29400 // withoutSpecifier() -> 1, 10, 15 // gas irOptimized: 121768 // gas irOptimized code: 27600 @@ -29,5 +27,3 @@ contract F { // gas legacy code: 40400 // gas legacyOptimized: 121916 // gas legacyOptimized code: 20600 -// gas ssaCFGOptimized: 121722 -// gas ssaCFGOptimized code: 26200 From 918c6e6b7794aeff524667cbb60edb47b4eed7ae Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 16 Jul 2026 10:55:41 +0800 Subject: [PATCH 18/41] ci: sync GitHub build images with CircleCI --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98af246c1b82..15ef9b29e35f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,8 +86,8 @@ jobs: CPUs: 10 container: - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2404-3 - image: solbuildpackpusher/solidity-buildpack-deps@sha256:ef6a91d7f1434c67fb9e05c6136d80f71c0ad9198479e1a88e3437680993cda4 + # ghcr.io/argotorg/solidity-buildpack-deps:ubuntu2404-6 + image: ghcr.io/argotorg/solidity-buildpack-deps@sha256:c0412c53e59ce0c96bde4c08e7332ea12e4cadba9bbac829621947897fa21272 steps: - name: Checkout code @@ -119,9 +119,9 @@ jobs: CPUs: 5 container: - # solbuildpackpusher/solidity-buildpack-deps:emscripten-20 + # ghcr.io/argotorg/solidity-buildpack-deps:emscripten-21 # NOTE: Keep in sync with the digest in scripts/build_emscripten.sh - image: solbuildpackpusher/solidity-buildpack-deps@sha256:98f963ed799a0d206ef8e7b5475f847e0dea53b7fdea9618bbc6106a62730bd2 + image: ghcr.io/argotorg/solidity-buildpack-deps@sha256:fc53d68a4680ffa7d5f70164e13a903478964f15bcc07434d74833a05f4fbc19 steps: - name: Checkout code From 82ee92087565aba1a9062ed2ba77ae5c7522d644 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 21 Jul 2026 17:46:07 +0800 Subject: [PATCH 19/41] ci: add Linux ARM64 release binary --- .github/workflows/build.yml | 54 ++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15ef9b29e35f..e5f7c4717077 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -110,6 +110,40 @@ jobs: path: github/solc-static-linux key: solc-linux-${{ github.run_id }} + b_linux_arm: + runs-on: ubuntu-24.04-arm + + env: + CMAKE_OPTIONS: -DCMAKE_BUILD_TYPE=Release -DSOLC_LINK_STATIC=ON + TERM: xterm + MAKEFLAGS: -j 4 + CPUs: 4 + + container: + # ghcr.io/argotorg/solidity-buildpack-deps:ubuntu2404.arm-1 + image: ghcr.io/argotorg/solidity-buildpack-deps@sha256:6cdb928fa8743d0b5d515c2c489b8d545c541f2370af28d5d3a71532056a0f22 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Fix git safe directory + run: git config --global --add safe.directory '*' + + - name: Run build script + run: .github/workflows/build.sh + + - name: Prepare artifact for caching + run: | + mkdir github/ + cp build/solc/solc github/solc-static-linux-arm + + - name: Save artifact to cache + uses: actions/cache/save@v5 + with: + path: github/solc-static-linux-arm + key: solc-linux-arm-${{ github.run_id }} + b_ems: runs-on: [ self-hosted, Linux, for-ems ] @@ -145,7 +179,7 @@ jobs: key: solc-ems-${{ github.run_id }} upload-to-s3: - needs: [ b_windows, b_macos, b_linux, b_ems ] + needs: [ b_windows, b_macos, b_linux, b_linux_arm, b_ems ] runs-on: [ self-hosted, macOS ] @@ -174,6 +208,12 @@ jobs: path: github/solc-static-linux key: solc-linux-${{ github.run_id }} + - name: Restore solc-linux-arm + uses: actions/cache/restore@v5 + with: + path: github/solc-static-linux-arm + key: solc-linux-arm-${{ github.run_id }} + - name: Restore solc-ems uses: actions/cache/restore@v5 with: @@ -196,12 +236,13 @@ jobs: sed -En 's/^Version: ([0-9.]+.*\+commit\.[0-9a-f]+(\.mod)?).*$/\1/p' ) - mkdir -p solc-bin/{linux-amd64,macosx-amd64,windows-amd64,bin} + mkdir -p solc-bin/{linux-amd64,linux-arm64,macosx-amd64,windows-amd64,bin} - cp github/solc-static-linux "solc-bin/linux-amd64/solc-linux-amd64-v${full_version}" - cp github/solc-macos "solc-bin/macosx-amd64/solc-macosx-amd64-v${full_version}" - cp github/solc-windows.exe "solc-bin/windows-amd64/solc-windows-amd64-v${full_version}.exe" - cp github/soljson.js "solc-bin/bin/soljson-v${full_version}.js" + cp github/solc-static-linux-arm "solc-bin/linux-arm64/solc-linux-arm64-v${full_version}" + cp github/solc-static-linux "solc-bin/linux-amd64/solc-linux-amd64-v${full_version}" + cp github/solc-macos "solc-bin/macosx-amd64/solc-macosx-amd64-v${full_version}" + cp github/solc-windows.exe "solc-bin/windows-amd64/solc-windows-amd64-v${full_version}.exe" + cp github/soljson.js "solc-bin/bin/soljson-v${full_version}.js" cd solc-bin/ tar --create --file ../solc-bin-binaries.tar * @@ -224,6 +265,7 @@ jobs: aws s3 cp solc-windows.exe "s3://${bucket}/${{ github.sha }}/" --only-show-errors aws s3 cp solc-macos "s3://${bucket}/${{ github.sha }}/" --only-show-errors aws s3 cp solc-static-linux "s3://${bucket}/${{ github.sha }}/" --only-show-errors + aws s3 cp solc-static-linux-arm "s3://${bucket}/${{ github.sha }}/" --only-show-errors aws s3 cp soljson.js "s3://${bucket}/${{ github.sha }}/" --only-show-errors cd .. && rm -rf github solc-bin *.tar From 121a4b726d23eb9170699af2ee760ea62cd9c1b3 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 21 Jul 2026 18:08:15 +0800 Subject: [PATCH 20/41] ci: build release compiler targets only --- .github/workflows/build.sh | 4 ++-- .github/workflows/build_win.ps1 | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.sh b/.github/workflows/build.sh index fd0a79906b1a..e559451f3a71 100755 --- a/.github/workflows/build.sh +++ b/.github/workflows/build.sh @@ -14,6 +14,6 @@ mkdir -p build cd build # shellcheck disable=SC2086 -cmake .. -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" $CMAKE_OPTIONS -G "Unix Makefiles" +cmake .. -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-Release}" -DTESTS=OFF $CMAKE_OPTIONS -G "Unix Makefiles" -make +make solc diff --git a/.github/workflows/build_win.ps1 b/.github/workflows/build_win.ps1 index 8b9fcaa5d66c..431f347dc503 100644 --- a/.github/workflows/build_win.ps1 +++ b/.github/workflows/build_win.ps1 @@ -8,9 +8,7 @@ Write-Host "Building release version." mkdir build cd build $boost_dir=(Resolve-Path $PSScriptRoot\..\..\deps\boost\lib\cmake\Boost-*) -..\deps\cmake\bin\cmake -G "Visual Studio 17 2022" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\..\upload" -DUSE_Z3=OFF .. +..\deps\cmake\bin\cmake -G "Visual Studio 17 2022" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DUSE_Z3=OFF -DTESTS=OFF .. if ( -not $? ) { throw "CMake configure failed." } -msbuild solidity.sln /p:Configuration=Release /m:10 /v:minimal -if ( -not $? ) { throw "Build failed." } -..\deps\cmake\bin\cmake --build . -j 10 --target install --config Release -if ( -not $? ) { throw "Install target failed." } +..\deps\cmake\bin\cmake --build . -j 10 --target solc --config Release +if ( -not $? ) { throw "solc build failed." } From 4d99274b0ff3e6d1118639e3eb0c854955f604d9 Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 22 Jul 2026 11:51:40 +0800 Subject: [PATCH 21/41] ci: migrate binary builds to GitHub-hosted runners --- .github/workflows/build.yml | 170 +++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 59 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5f7c4717077..eed1113383ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,22 +7,22 @@ on: jobs: b_windows: - runs-on: [ self-hosted, Windows ] + runs-on: windows-2022 steps: - name: Checkout code uses: actions/checkout@v5 - - - name: Fix git safe directory - run: git config --global --add safe.directory '*' + with: + submodules: true - name: Cache dependencies id: cache-deps uses: actions/cache@v5 with: - path: .\deps - key: dependencies-win-${{ runner.arch }}-${{ hashFiles('scripts/install_deps.ps1') }} - restore-keys: dependencies-win-${{ runner.arch }}- + path: | + .\deps\boost + .\deps\cmake + key: hosted-dependencies-win-v2-${{ runner.arch }}-${{ hashFiles('scripts/install_deps.ps1') }} - name: Installing dependencies if: steps.cache-deps.outputs.cache-hit != 'true' @@ -39,26 +39,68 @@ jobs: mkdir github/ cp build/solc/Release/solc.exe github/solc-windows.exe - - name: Save artifact to cache - uses: actions/cache/save@v5 + - name: Upload Windows binary + uses: actions/upload-artifact@v4 with: + name: solc-windows path: github/solc-windows.exe - key: solc-windows-${{ github.run_id }} - enableCrossOsArchive: true + if-no-files-found: error b_macos: - runs-on: [ self-hosted, macOS ] + runs-on: macos-15 env: CMAKE_BUILD_TYPE: Release - CMAKE_OPTIONS: -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64;arm64 + CMAKE_OPTIONS: -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64;arm64 -DBoost_ROOT=${{ github.workspace }}/deps/boost -DBoost_NO_SYSTEM_PATHS=ON TERM: xterm - MAKEFLAGS: -j5 - CPUs: 5 + MAKEFLAGS: -j 3 + CPUs: 3 steps: - name: Checkout code uses: actions/checkout@v5 + with: + submodules: true + + - name: Select pinned macOS toolchain + run: | + sudo xcode-select --switch /Applications/Xcode_16.4.app/Contents/Developer + bash .circleci/install_cmake.sh 3.29.3 + xcodebuild -version + clang --version + cmake --version + + - name: Cache universal Boost + id: cache-boost + uses: actions/cache@v5 + with: + path: deps/boost + key: hosted-boost-macos-universal-1.84.0-xcode16.4-v1 + + - name: Build universal Boost + if: steps.cache-boost.outputs.cache-hit != 'true' + run: | + boost_version=1.84.0 + boost_dir="boost_${boost_version//./_}" + boost_archive="${RUNNER_TEMP}/${boost_dir}.tar.bz2" + + curl --fail --location --retry 3 \ + "https://archives.boost.io/release/${boost_version}/source/${boost_dir}.tar.bz2" \ + --output "${boost_archive}" + tar -xjf "${boost_archive}" -C "${RUNNER_TEMP}" + + cd "${RUNNER_TEMP}/${boost_dir}" + ./bootstrap.sh \ + --with-toolset=clang \ + --with-libraries=filesystem,program_options,system,test + ./b2 -j 3 -d0 \ + link=static \ + variant=release \ + threading=multi \ + address-model=64 \ + architecture=arm+x86 \ + --prefix="${GITHUB_WORKSPACE}/deps/boost" \ + install - name: Run build script shell: bash -el {0} @@ -69,21 +111,21 @@ jobs: mkdir github/ cp build/solc/solc github/solc-macos - - name: Save artifact to cache - uses: actions/cache/save@v5 + - name: Upload macOS binary + uses: actions/upload-artifact@v4 with: + name: solc-macos path: github/solc-macos - key: solc-macos-${{ github.run_id }} - enableCrossOsArchive: true + if-no-files-found: error b_linux: - runs-on: [ self-hosted, Linux, for-linux ] + runs-on: ubuntu-24.04 env: CMAKE_OPTIONS: -DCMAKE_BUILD_TYPE=Release -DSOLC_LINK_STATIC=ON TERM: xterm - MAKEFLAGS: -j 10 - CPUs: 10 + MAKEFLAGS: -j 4 + CPUs: 4 container: # ghcr.io/argotorg/solidity-buildpack-deps:ubuntu2404-6 @@ -92,6 +134,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v5 + with: + submodules: true - name: Fix git safe directory run: git config --global --add safe.directory '*' @@ -104,11 +148,12 @@ jobs: mkdir github/ cp build/solc/solc github/solc-static-linux - - name: Save artifact to cache - uses: actions/cache/save@v5 + - name: Upload Linux binary + uses: actions/upload-artifact@v4 with: + name: solc-linux path: github/solc-static-linux - key: solc-linux-${{ github.run_id }} + if-no-files-found: error b_linux_arm: runs-on: ubuntu-24.04-arm @@ -126,6 +171,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v5 + with: + submodules: true - name: Fix git safe directory run: git config --global --add safe.directory '*' @@ -138,19 +185,20 @@ jobs: mkdir github/ cp build/solc/solc github/solc-static-linux-arm - - name: Save artifact to cache - uses: actions/cache/save@v5 + - name: Upload Linux ARM64 binary + uses: actions/upload-artifact@v4 with: + name: solc-linux-arm path: github/solc-static-linux-arm - key: solc-linux-arm-${{ github.run_id }} + if-no-files-found: error b_ems: - runs-on: [ self-hosted, Linux, for-ems ] + runs-on: ubuntu-24.04 env: TERM: xterm - MAKEFLAGS: -j 10 - CPUs: 5 + MAKEFLAGS: -j 4 + CPUs: 4 container: # ghcr.io/argotorg/solidity-buildpack-deps:emscripten-21 @@ -160,6 +208,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v5 + with: + submodules: true - name: Fix git safe directory run: git config --global --add safe.directory '*' @@ -172,53 +222,55 @@ jobs: mkdir github/ cp upload/soljson.js github/soljson.js - - name: Save artifact to cache - uses: actions/cache/save@v5 + - name: Upload Emscripten binary + uses: actions/upload-artifact@v4 with: + name: solc-ems path: github/soljson.js - key: solc-ems-${{ github.run_id }} + if-no-files-found: error upload-to-s3: needs: [ b_windows, b_macos, b_linux, b_linux_arm, b_ems ] - runs-on: [ self-hosted, macOS ] + runs-on: [ self-hosted, Linux ] permissions: actions: read contents: read steps: - - name: Restore solc-windows - uses: actions/cache/restore@v5 + - name: Download solc-windows + uses: actions/download-artifact@v4 with: - path: github/solc-windows.exe - key: solc-windows-${{ github.run_id }} - enableCrossOsArchive: true + name: solc-windows + path: github - - name: Restore solc-macos - uses: actions/cache/restore@v5 + - name: Download solc-macos + uses: actions/download-artifact@v4 with: - path: github/solc-macos - key: solc-macos-${{ github.run_id }} - enableCrossOsArchive: true + name: solc-macos + path: github - - name: Restore solc-linux - uses: actions/cache/restore@v5 + - name: Download solc-linux + uses: actions/download-artifact@v4 with: - path: github/solc-static-linux - key: solc-linux-${{ github.run_id }} + name: solc-linux + path: github - - name: Restore solc-linux-arm - uses: actions/cache/restore@v5 + - name: Download solc-linux-arm + uses: actions/download-artifact@v4 with: - path: github/solc-static-linux-arm - key: solc-linux-arm-${{ github.run_id }} + name: solc-linux-arm + path: github - - name: Restore solc-ems - uses: actions/cache/restore@v5 + - name: Download solc-ems + uses: actions/download-artifact@v4 with: - path: github/soljson.js - key: solc-ems-${{ github.run_id }} + name: solc-ems + path: github + + - name: Restore executable permissions + run: chmod +x github/solc-static-linux github/solc-static-linux-arm github/solc-macos - name: List all artifacts run: | @@ -232,7 +284,7 @@ jobs: - name: Rename binaries to solc-bin naming convention run: | full_version=$( - github/solc-macos --version | + github/solc-static-linux --version | sed -En 's/^Version: ([0-9.]+.*\+commit\.[0-9a-f]+(\.mod)?).*$/\1/p' ) @@ -248,7 +300,7 @@ jobs: tar --create --file ../solc-bin-binaries.tar * - name: Upload to S3 - shell: zsh -el {0} + shell: bash env: S3_BUCKET_PROD: ${{ secrets.S3_BUCKET_PROD }} S3_BUCKET_TEST: ${{ secrets.S3_BUCKET_TEST }} From 470c8d5ba361f4a21afec6129005f764d0f4581a Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 22 Jul 2026 13:30:29 +0800 Subject: [PATCH 22/41] ci: fix S3 artifact handoff --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eed1113383ef..10411607b4c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: cp build/solc/Release/solc.exe github/solc-windows.exe - name: Upload Windows binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: solc-windows path: github/solc-windows.exe @@ -112,7 +112,7 @@ jobs: cp build/solc/solc github/solc-macos - name: Upload macOS binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: solc-macos path: github/solc-macos @@ -149,7 +149,7 @@ jobs: cp build/solc/solc github/solc-static-linux - name: Upload Linux binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: solc-linux path: github/solc-static-linux @@ -186,7 +186,7 @@ jobs: cp build/solc/solc github/solc-static-linux-arm - name: Upload Linux ARM64 binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: solc-linux-arm path: github/solc-static-linux-arm @@ -223,7 +223,7 @@ jobs: cp upload/soljson.js github/soljson.js - name: Upload Emscripten binary - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: solc-ems path: github/soljson.js @@ -232,7 +232,7 @@ jobs: upload-to-s3: needs: [ b_windows, b_macos, b_linux, b_linux_arm, b_ems ] - runs-on: [ self-hosted, Linux ] + runs-on: [ self-hosted, Linux, for-linux ] permissions: actions: read @@ -240,31 +240,31 @@ jobs: steps: - name: Download solc-windows - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: solc-windows path: github - name: Download solc-macos - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: solc-macos path: github - name: Download solc-linux - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: solc-linux path: github - name: Download solc-linux-arm - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: solc-linux-arm path: github - name: Download solc-ems - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: solc-ems path: github From 8d58f5ab198503e1d0100711812437d9b0579c51 Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 22 Jul 2026 15:34:12 +0800 Subject: [PATCH 23/41] ci: use production S3 bucket for uploads --- .github/workflows/build.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10411607b4c0..6ea100159faf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -302,22 +302,16 @@ jobs: - name: Upload to S3 shell: bash env: - S3_BUCKET_PROD: ${{ secrets.S3_BUCKET_PROD }} - S3_BUCKET_TEST: ${{ secrets.S3_BUCKET_TEST }} + S3_BUCKET: ${{ secrets.S3_BUCKET_PROD }} run: | - case "${GITHUB_REF_NAME}" in - develop) bucket="${S3_BUCKET_PROD}" ;; - release_*) bucket="${S3_BUCKET_TEST}" ;; - esac - - aws s3 cp github-binaries.tar "s3://${bucket}/${{ github.sha }}/" --only-show-errors - aws s3 cp solc-bin-binaries.tar "s3://${bucket}/${{ github.sha }}/" --only-show-errors + aws s3 cp github-binaries.tar "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors + aws s3 cp solc-bin-binaries.tar "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors cd github - aws s3 cp solc-windows.exe "s3://${bucket}/${{ github.sha }}/" --only-show-errors - aws s3 cp solc-macos "s3://${bucket}/${{ github.sha }}/" --only-show-errors - aws s3 cp solc-static-linux "s3://${bucket}/${{ github.sha }}/" --only-show-errors - aws s3 cp solc-static-linux-arm "s3://${bucket}/${{ github.sha }}/" --only-show-errors - aws s3 cp soljson.js "s3://${bucket}/${{ github.sha }}/" --only-show-errors + aws s3 cp solc-windows.exe "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors + aws s3 cp solc-macos "s3://${S3_BUCKET}/${{ github.sha }}/" --only-show-errors + 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 From 086543227c7a72c80277ddd90e3d269b94caafeb Mon Sep 17 00:00:00 2001 From: Asuka Date: Fri, 24 Jul 2026 12:09:06 +0800 Subject: [PATCH 24/41] ci: isolate S3 upload workspace --- .github/workflows/build.yml | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) 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 From fd7601996c9f0edf8f90b518c88ebd3c7b41b8ec Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 10:52:08 +0800 Subject: [PATCH 25/41] fix(evmasm): align gas estimates with TVM pricing --- libevmasm/GasMeter.cpp | 24 ++++--- libevmasm/GasMeter.h | 12 ++++ test/libevmasm/GasMeter.cpp | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 11 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 1f4a7ddb1844..8a18dc63e08a 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -71,13 +71,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::tvmSstoreResetGas; //@todo take refunds into account else - gas = GasCosts::totalSstoreSetGas(m_evmVersion); + gas = GasCosts::tvmSstoreSetGas; break; } case Instruction::SLOAD: - gas = GasCosts::sloadGas(m_evmVersion); + gas = GasCosts::tvmSloadGas; break; case Instruction::RETURN: case Instruction::REVERT: @@ -122,13 +122,13 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::EXTCODESIZE: - gas = GasCosts::extCodeGas(m_evmVersion); + gas = GasCosts::tvmExtCodeSizeGas; break; case Instruction::EXTCODEHASH: - gas = GasCosts::balanceGas(m_evmVersion); + gas = GasCosts::tvmExtCodeHashGas; break; case Instruction::EXTCODECOPY: - gas = GasCosts::extCodeGas(m_evmVersion); + gas = GasCosts::tvmExtCodeCopyGas; gas += memoryGas(-1, -3); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-3)); break; @@ -157,18 +157,20 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas = GasConsumption::infinite(); else { - gas = GasCosts::callGas(m_evmVersion); + gas = GasCosts::tvmCallGas; 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 +180,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::SELFDESTRUCT: - gas = GasCosts::selfdestructGas(m_evmVersion); + gas = GasCosts::tvmSelfdestructGas; gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. break; case Instruction::CREATE: @@ -209,7 +211,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::BALANCE: case Instruction::TOKENBALANCE: case Instruction::ISCONTRACT: - gas = GasCosts::balanceGas(m_evmVersion); + gas = GasCosts::tvmBalanceGas; break; case Instruction::NATIVEFREEZE: gas = GasCosts::freezeV1Gas; diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 7f2e4f6a3d00..69519cf0dff4 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -179,6 +179,18 @@ namespace GasCosts static unsigned const copyGas = 3; static unsigned const rjumpiGas = 4; + // 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 tvmSloadGas = 50; + static unsigned const tvmSstoreSetGas = 20000; + static unsigned const tvmSstoreResetGas = 5000; + static unsigned const tvmBalanceGas = 20; + static unsigned const tvmExtCodeSizeGas = 20; + static unsigned const tvmExtCodeCopyGas = 20; + static unsigned const tvmExtCodeHashGas = 400; + static unsigned const tvmCallGas = 40; + static unsigned const tvmSelfdestructGas = 5000; + static unsigned const freezeV1Gas = 20000; static unsigned const expireTimeGas = 50; static unsigned const freezeV2Gas = 10000; diff --git a/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp index 6d82f86552ab..4ef73b455d8f 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,26 @@ 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)}); +} + /// 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 +91,112 @@ 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::tvmSloadGas}, + {Instruction::BALANCE, 1, GasCosts::tvmBalanceGas}, + {Instruction::TOKENBALANCE, 2, GasCosts::tvmBalanceGas}, + {Instruction::ISCONTRACT, 1, GasCosts::tvmBalanceGas}, + {Instruction::EXTCODESIZE, 1, GasCosts::tvmExtCodeSizeGas}, + {Instruction::EXTCODECOPY, 4, GasCosts::tvmExtCodeCopyGas}, + {Instruction::EXTCODEHASH, 1, GasCosts::tvmExtCodeHashGas}, + {Instruction::NATIVEFREEZE, 3, GasCosts::freezeV1Gas + GasCosts::callNewAccountGas}, + {Instruction::NATIVEUNFREEZE, 2, GasCosts::freezeV1Gas}, + {Instruction::NATIVEFREEZEEXPIRETIME, 2, GasCosts::expireTimeGas}, + {Instruction::NATIVEWITHDRAWREWARD, 0, GasCosts::withdrawGas}, + {Instruction::NATIVEFREEZEBALANCEV2, 2, GasCosts::freezeV2Gas}, + {Instruction::NATIVEUNFREEZEBALANCEV2, 2, GasCosts::freezeV2Gas}, + {Instruction::NATIVECANCELALLUNFREEZEV2, 0, GasCosts::freezeV2Gas}, + {Instruction::NATIVEWITHDRAWEXPIREUNFREEZE, 0, GasCosts::freezeV2Gas}, + {Instruction::NATIVEDELEGATERESOURCE, 3, GasCosts::freezeV2Gas}, + {Instruction::NATIVEUNDELEGATERESOURCE, 3, GasCosts::freezeV2Gas} + }; + + 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::tvmSstoreSetGas)); + + GasMeter::GasConsumption reset = estimateInstruction(Instruction::SSTORE, {u256(0), u256(0)}); + BOOST_REQUIRE(!reset.isInfinite); + BOOST_CHECK_EQUAL(reset.value, u256(GasCosts::tvmSstoreResetGas)); +} + +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::tvmCallGas)); + } + + 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::tvmCallGas)); + + 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::tvmCallGas + 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::tvmCallGas + 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::tvmCallGas)); + + 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::tvmCallGas + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) + ); +} + BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays) { // java-tron (EnergyCost.getVoteWitnessCost2/3) charges per array From 8e735509ec5a170e3ab54acc358b097126253bfa Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 12:09:48 +0800 Subject: [PATCH 26/41] fix(codegen): use TVM call price in gasNeededByCaller The pre-tangerine-whistle fallback paths in both code generators still derived the retained-gas amount from Ethereum fork-dependent GasCosts::callGas(). Switch them to the fixed TVM price for consistency with the GasMeter alignment. Also add GasMeter unit coverage for the SELFDESTRUCT estimate (tvmSelfdestructGas + callNewAccountGas). --- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 6 +++--- test/libevmasm/GasMeter.cpp | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 475c4d32b8b0..eb0d7e410c30 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -3256,7 +3256,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::tvmCallGas + 10; if (_functionType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!existenceChecked) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e55282137185..ae1bc32c836e 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -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::tvmCallGas + 10 + evmasm::GasCosts::callNewAccountGas; templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); } @@ -3189,7 +3189,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::tvmCallGas + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!checkExtcodesize) @@ -3298,7 +3298,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::tvmCallGas + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know diff --git a/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp index 4ef73b455d8f..76765b4a6949 100644 --- a/test/libevmasm/GasMeter.cpp +++ b/test/libevmasm/GasMeter.cpp @@ -197,6 +197,16 @@ BOOST_AUTO_TEST_CASE(tvm_calltoken_uses_fixed_base_and_conditional_transfer_pric ); } +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::tvmSelfdestructGas + GasCosts::callNewAccountGas)); +} + BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays) { // java-tron (EnergyCost.getVoteWitnessCost2/3) charges per array From f8949f5250be5d4c8979e6005af15a7586c9e455 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 13:49:34 +0800 Subject: [PATCH 27/41] refactor(evmasm): clarify TVM gas constant names --- libevmasm/GasMeter.cpp | 30 +++++----- libevmasm/GasMeter.h | 28 +++++----- libsolidity/codegen/ExpressionCompiler.cpp | 2 +- .../codegen/ir/IRGeneratorForStatements.cpp | 6 +- test/libevmasm/GasMeter.cpp | 56 +++++++++---------- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 8a18dc63e08a..e7e107fe1d31 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -71,13 +71,13 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ m_state->storageContent().count(slot) && classes.knownNonZero(m_state->storageContent().at(slot)) )) - gas = GasCosts::tvmSstoreResetGas; //@todo take refunds into account + gas = GasCosts::sstoreResetGasInTVM; //@todo take refunds into account else - gas = GasCosts::tvmSstoreSetGas; + gas = GasCosts::sstoreSetGasInTVM; break; } case Instruction::SLOAD: - gas = GasCosts::tvmSloadGas; + gas = GasCosts::sloadGasInTVM; break; case Instruction::RETURN: case Instruction::REVERT: @@ -122,13 +122,13 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::EXTCODESIZE: - gas = GasCosts::tvmExtCodeSizeGas; + gas = GasCosts::extCodeSizeGasInTVM; break; case Instruction::EXTCODEHASH: - gas = GasCosts::tvmExtCodeHashGas; + gas = GasCosts::extCodeHashGasInTVM; break; case Instruction::EXTCODECOPY: - gas = GasCosts::tvmExtCodeCopyGas; + gas = GasCosts::extCodeCopyGasInTVM; gas += memoryGas(-1, -3); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-3)); break; @@ -157,7 +157,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas = GasConsumption::infinite(); else { - gas = GasCosts::tvmCallGas; + gas = GasCosts::callGasInTVM; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) gas += (*value); else @@ -180,7 +180,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::SELFDESTRUCT: - gas = GasCosts::tvmSelfdestructGas; + gas = GasCosts::selfdestructGasInTVM; gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. break; case Instruction::CREATE: @@ -211,27 +211,27 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::BALANCE: case Instruction::TOKENBALANCE: case Instruction::ISCONTRACT: - gas = GasCosts::tvmBalanceGas; + 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: @@ -239,7 +239,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); diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 69519cf0dff4..06a4b8b609de 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -181,21 +181,21 @@ namespace GasCosts // 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 tvmSloadGas = 50; - static unsigned const tvmSstoreSetGas = 20000; - static unsigned const tvmSstoreResetGas = 5000; - static unsigned const tvmBalanceGas = 20; - static unsigned const tvmExtCodeSizeGas = 20; - static unsigned const tvmExtCodeCopyGas = 20; - static unsigned const tvmExtCodeHashGas = 400; - static unsigned const tvmCallGas = 40; - static unsigned const tvmSelfdestructGas = 5000; + 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 freezeV1Gas = 20000; - static unsigned const expireTimeGas = 50; - static unsigned const freezeV2Gas = 10000; - static unsigned const withdrawGas = 20000; - static unsigned const voteGas = 30000; + 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; } /** diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index eb0d7e410c30..768895a31f7c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -3256,7 +3256,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::tvmCallGas + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (_functionType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!existenceChecked) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index ae1bc32c836e..9eb3f379378c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -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::tvmCallGas + 10 + evmasm::GasCosts::callNewAccountGas; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10 + evmasm::GasCosts::callNewAccountGas; templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); } @@ -3189,7 +3189,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::tvmCallGas + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; if (!checkExtcodesize) @@ -3298,7 +3298,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::tvmCallGas + 10; + u256 gasNeededByCaller = evmasm::GasCosts::callGasInTVM + 10; if (funType.valueSet()) gasNeededByCaller += evmasm::GasCosts::callValueTransferGas; gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know diff --git a/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp index 76765b4a6949..aeac86a2931e 100644 --- a/test/libevmasm/GasMeter.cpp +++ b/test/libevmasm/GasMeter.cpp @@ -94,23 +94,23 @@ 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::tvmSloadGas}, - {Instruction::BALANCE, 1, GasCosts::tvmBalanceGas}, - {Instruction::TOKENBALANCE, 2, GasCosts::tvmBalanceGas}, - {Instruction::ISCONTRACT, 1, GasCosts::tvmBalanceGas}, - {Instruction::EXTCODESIZE, 1, GasCosts::tvmExtCodeSizeGas}, - {Instruction::EXTCODECOPY, 4, GasCosts::tvmExtCodeCopyGas}, - {Instruction::EXTCODEHASH, 1, GasCosts::tvmExtCodeHashGas}, - {Instruction::NATIVEFREEZE, 3, GasCosts::freezeV1Gas + GasCosts::callNewAccountGas}, - {Instruction::NATIVEUNFREEZE, 2, GasCosts::freezeV1Gas}, - {Instruction::NATIVEFREEZEEXPIRETIME, 2, GasCosts::expireTimeGas}, - {Instruction::NATIVEWITHDRAWREWARD, 0, GasCosts::withdrawGas}, - {Instruction::NATIVEFREEZEBALANCEV2, 2, GasCosts::freezeV2Gas}, - {Instruction::NATIVEUNFREEZEBALANCEV2, 2, GasCosts::freezeV2Gas}, - {Instruction::NATIVECANCELALLUNFREEZEV2, 0, GasCosts::freezeV2Gas}, - {Instruction::NATIVEWITHDRAWEXPIREUNFREEZE, 0, GasCosts::freezeV2Gas}, - {Instruction::NATIVEDELEGATERESOURCE, 3, GasCosts::freezeV2Gas}, - {Instruction::NATIVEUNDELEGATERESOURCE, 3, GasCosts::freezeV2Gas} + {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()) @@ -131,11 +131,11 @@ 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::tvmSstoreSetGas)); + 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::tvmSstoreResetGas)); + BOOST_CHECK_EQUAL(reset.value, u256(GasCosts::sstoreResetGasInTVM)); } BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_prices) @@ -144,7 +144,7 @@ BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_pr { GasMeter::GasConsumption gas = estimateInstruction(instruction, zeroArguments(6), false); BOOST_REQUIRE(!gas.isInfinite); - BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::tvmCallGas)); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::callGasInTVM)); } GasMeter::GasConsumption zeroValueCall = estimateInstruction( @@ -153,7 +153,7 @@ BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_pr false ); BOOST_REQUIRE(!zeroValueCall.isInfinite); - BOOST_CHECK_EQUAL(zeroValueCall.value, u256(GasCosts::tvmCallGas)); + BOOST_CHECK_EQUAL(zeroValueCall.value, u256(GasCosts::callGasInTVM)); GasMeter::GasConsumption valueCall = estimateInstruction( Instruction::CALL, @@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_pr BOOST_REQUIRE(!valueCall.isInfinite); BOOST_CHECK_EQUAL( valueCall.value, - u256(GasCosts::tvmCallGas + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) + u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) ); GasMeter::GasConsumption valueCallCode = estimateInstruction( @@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(tvm_call_family_uses_fixed_base_and_conditional_transfer_pr false ); BOOST_REQUIRE(!valueCallCode.isInfinite); - BOOST_CHECK_EQUAL(valueCallCode.value, u256(GasCosts::tvmCallGas + GasCosts::callValueTransferGas)); + BOOST_CHECK_EQUAL(valueCallCode.value, u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas)); } BOOST_AUTO_TEST_CASE(tvm_calltoken_uses_fixed_base_and_conditional_transfer_prices) @@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(tvm_calltoken_uses_fixed_base_and_conditional_transfer_pric false ); BOOST_REQUIRE(!zeroValue.isInfinite); - BOOST_CHECK_EQUAL(zeroValue.value, u256(GasCosts::tvmCallGas)); + BOOST_CHECK_EQUAL(zeroValue.value, u256(GasCosts::callGasInTVM)); GasMeter::GasConsumption nonZeroValue = estimateInstruction( Instruction::CALLTOKEN, @@ -193,7 +193,7 @@ BOOST_AUTO_TEST_CASE(tvm_calltoken_uses_fixed_base_and_conditional_transfer_pric BOOST_REQUIRE(!nonZeroValue.isInfinite); BOOST_CHECK_EQUAL( nonZeroValue.value, - u256(GasCosts::tvmCallGas + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) + u256(GasCosts::callGasInTVM + GasCosts::callValueTransferGas + GasCosts::callNewAccountGas) ); } @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(tvm_selfdestruct_uses_fixed_price_plus_new_account_cost) // 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::tvmSelfdestructGas + GasCosts::callNewAccountGas)); + BOOST_CHECK_EQUAL(gas.value, u256(GasCosts::selfdestructGasInTVM + GasCosts::callNewAccountGas)); } BOOST_AUTO_TEST_CASE(nativevote_charges_memory_expansion_for_word_arrays) @@ -215,7 +215,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) @@ -224,7 +224,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) From 121cd1b1fe3d39c049854c19feedfbfebb53e172 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 11:02:37 +0800 Subject: [PATCH 28/41] fix: gate experimental and unsupported VM targets --- liblangutil/EVMVersion.cpp | 18 ++++++++ libsolidity/analysis/TypeChecker.cpp | 38 +++++++++++++++++ libsolidity/interface/StandardCompiler.cpp | 15 ++++++- test/CMakeLists.txt | 1 + .../input.json | 1 + .../input.json | 1 + .../standard_no_append_cbor/input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 1 + .../input.json | 12 ++++++ .../output.json | 16 ++++++++ .../standard_viair_requested/input.json | 3 +- .../standard_yul_cfg_json_export/input.json | 1 + test/liblangutil/EVMVersion.cpp | 41 +++++++++++++++++++ .../tron/precompiles_before_byzantium.sol | 11 +++++ 30 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 test/cmdlineTests/standard_viair_experimental_warning/input.json create mode 100644 test/cmdlineTests/standard_viair_experimental_warning/output.json create mode 100644 test/liblangutil/EVMVersion.cpp create mode 100644 test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index 4fbfe8de41b0..dae910af38e7 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -61,6 +61,24 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _eofVersi case Instruction::TSTORE: case Instruction::TLOAD: return supportsTransientStorage(); + // TRON-specific instructions are not part of the EOF instruction set. + case Instruction::CALLTOKEN: + case Instruction::TOKENBALANCE: + case Instruction::CALLTOKENVALUE: + case Instruction::CALLTOKENID: + case Instruction::ISCONTRACT: + case Instruction::NATIVEFREEZE: + case Instruction::NATIVEUNFREEZE: + case Instruction::NATIVEFREEZEEXPIRETIME: + case Instruction::NATIVEVOTE: + case Instruction::NATIVEWITHDRAWREWARD: + case Instruction::NATIVEFREEZEBALANCEV2: + case Instruction::NATIVEUNFREEZEBALANCEV2: + case Instruction::NATIVECANCELALLUNFREEZEV2: + case Instruction::NATIVEWITHDRAWEXPIREUNFREEZE: + case Instruction::NATIVEDELEGATERESOURCE: + case Instruction::NATIVEUNDELEGATERESOURCE: + return !_eofVersion.has_value(); // Instructions below are deprecated in EOF case Instruction::CALL: case Instruction::CALLCODE: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5c8c53613482..0ce058809cdd 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1999,6 +1999,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); } @@ -3290,6 +3322,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/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 85495a9e37e2..f5f737310c0d 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -430,7 +430,7 @@ std::optional checkAuxiliaryInputKeys(Json const& _input) std::optional checkSettingsKeys(Json const& _input) { - static std::set keys{"debug", "evmVersion", "eofVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; + static std::set keys{"debug", "evmVersion", "eofVersion", "experimentalViaIR", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; return checkKeys(_input, keys, "settings"); } @@ -820,6 +820,19 @@ std::variant StandardCompiler::parseI return formatFatalError(Error::Type::JSONError, "\"settings.viaIR\" must be a Boolean."); ret.viaIR = settings["viaIR"].get(); } + bool experimentalViaIRAcknowledged = false; + if (settings.contains("experimentalViaIR")) + { + if (!settings["experimentalViaIR"].is_boolean()) + return formatFatalError(Error::Type::JSONError, "\"settings.experimentalViaIR\" must be a Boolean."); + experimentalViaIRAcknowledged = settings["experimentalViaIR"].get(); + } + if (ret.viaIR && !experimentalViaIRAcknowledged) + ret.errors.emplace_back(formatError( + Error::Type::Warning, + "general", + "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning." + )); if (settings.contains("evmVersion")) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dbbcf3eaec81..8af881053fa6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,6 +63,7 @@ detect_stray_source_files("${libevmasm_sources}" "libevmasm/") set(liblangutil_sources liblangutil/CharStream.cpp + liblangutil/EVMVersion.cpp liblangutil/Scanner.cpp liblangutil/SourceLocation.cpp ) diff --git a/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json b/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json index 6ff7662901e4..c306a4e01687 100644 --- a/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json +++ b/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json @@ -14,6 +14,7 @@ "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, "outputSelection": diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json index 06b89d0e01bf..d93f78f4d145 100644 --- a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json @@ -7,6 +7,7 @@ "outputSelection": {"*": {"*": ["ir"]}}, "optimizer": {"enabled": true}, "viaIR": true, + "experimentalViaIR": true, "debug": {"debugInfo": []} } } diff --git a/test/cmdlineTests/standard_no_append_cbor/input.json b/test/cmdlineTests/standard_no_append_cbor/input.json index 493325506dda..8fefb3ee0f50 100644 --- a/test/cmdlineTests/standard_no_append_cbor/input.json +++ b/test/cmdlineTests/standard_no_append_cbor/input.json @@ -10,6 +10,7 @@ "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json b/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json index f12e45bf2fa9..a1652b5633d9 100644 --- a/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json +++ b/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json @@ -10,6 +10,7 @@ "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json index 422a54f90d9f..aff5263670d6 100644 --- a/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "debug": { "debugInfo": [ "ethdebug" diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json index 8f8edcd29203..8fac23d615d9 100644 --- a/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "debug": { "debugInfo": [ "ethdebug" diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json index 8ed44ca05de2..15d1cd6ace39 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json index 27c400f9e946..add6ead1e0a0 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json index ad74649f5d1d..9394bea47fc3 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json index ad74649f5d1d..9394bea47fc3 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json index 35c8f940e82d..9a0d64f088a5 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json index e4681967f7db..028e195d408c 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json index 6239a7275e4d..4df522786b38 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json index fdb6c95b813e..1929a6a97ccd 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json index 5bc9d5467345..279c7ef44848 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "outputSelection": { "*": { "*": [ diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json index 8952a241f6e7..e87a97e5bb08 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "outputSelection": { "*": { "*": [ diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json index a684e0dc073b..641dcfb1827e 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json index 1f09e3a70253..c743d1609f2c 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json @@ -10,6 +10,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json b/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json index dc86ac68494e..d2490aef88f6 100644 --- a/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json +++ b/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json @@ -5,6 +5,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "outputSelection": { "*": {"*": ["evm.bytecode"]} } diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json index dc86ac68494e..d2490aef88f6 100644 --- a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json @@ -5,6 +5,7 @@ }, "settings": { "viaIR": true, + "experimentalViaIR": true, "outputSelection": { "*": {"*": ["evm.bytecode"]} } diff --git a/test/cmdlineTests/standard_viair_experimental_warning/input.json b/test/cmdlineTests/standard_viair_experimental_warning/input.json new file mode 100644 index 000000000000..0b05d0d0e7cd --- /dev/null +++ b/test/cmdlineTests/standard_viair_experimental_warning/input.json @@ -0,0 +1,12 @@ +{ + "language": "Solidity", + "sources": { + "A.sol": { + "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >=0.0; contract A {}" + } + }, + "settings": { + "viaIR": true, + "outputSelection": {} + } +} diff --git a/test/cmdlineTests/standard_viair_experimental_warning/output.json b/test/cmdlineTests/standard_viair_experimental_warning/output.json new file mode 100644 index 000000000000..c74bd9d6b3a5 --- /dev/null +++ b/test/cmdlineTests/standard_viair_experimental_warning/output.json @@ -0,0 +1,16 @@ +{ + "errors": [ + { + "component": "general", + "formattedMessage": "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning.", + "message": "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning.", + "severity": "warning", + "type": "Warning" + } + ], + "sources": { + "A.sol": { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_viair_requested/input.json b/test/cmdlineTests/standard_viair_requested/input.json index 76ae807a615c..542b0372d6b0 100644 --- a/test/cmdlineTests/standard_viair_requested/input.json +++ b/test/cmdlineTests/standard_viair_requested/input.json @@ -16,6 +16,7 @@ { "*": { "*": ["ir", "evm.bytecode.object", "evm.bytecode.generatedSources", "evm.deployedBytecode.object"] } }, - "viaIR": true + "viaIR": true, + "experimentalViaIR": true } } diff --git a/test/cmdlineTests/standard_yul_cfg_json_export/input.json b/test/cmdlineTests/standard_yul_cfg_json_export/input.json index b5353721ee09..545bf50ae705 100644 --- a/test/cmdlineTests/standard_yul_cfg_json_export/input.json +++ b/test/cmdlineTests/standard_yul_cfg_json_export/input.json @@ -8,6 +8,7 @@ "enabled": true }, "viaIR": true, + "experimentalViaIR": true, "outputSelection": {"*": {"*": ["yulCFGJson"]}} } } diff --git a/test/liblangutil/EVMVersion.cpp b/test/liblangutil/EVMVersion.cpp new file mode 100644 index 000000000000..930ed4caaceb --- /dev/null +++ b/test/liblangutil/EVMVersion.cpp @@ -0,0 +1,41 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +#include + +namespace solidity::langutil::test +{ + +BOOST_AUTO_TEST_SUITE(EVMVersionTest) + +BOOST_AUTO_TEST_CASE(tron_instructions_are_not_available_in_eof) +{ + for (unsigned opcode = 0xd0; opcode <= 0xdf; ++opcode) + { + auto const instruction = static_cast(opcode); + BOOST_TEST(EVMVersion::osaka().hasOpcode(instruction, std::nullopt)); + BOOST_TEST(!EVMVersion::osaka().hasOpcode(instruction, 1)); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +} 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: Date: Tue, 28 Jul 2026 11:06:03 +0800 Subject: [PATCH 29/41] fix: align TRON builtin semantics across backends --- libsolidity/analysis/GlobalContext.cpp | 2 +- libsolidity/ast/Types.cpp | 1 - libsolidity/codegen/ExpressionCompiler.cpp | 1 + .../codegen/ir/IRGeneratorForStatements.cpp | 35 ++++++++++++------ test/libsolidity/StandardCompiler.cpp | 36 +++++++++++++++++++ .../tron/validatemultisign_mutability.sol | 7 ++++ 6 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 test/libsolidity/syntaxTests/tron/validatemultisign_mutability.sol diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index c186ad1a0f1b..b9666889cfeb 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -379,7 +379,7 @@ void GlobalContext::addValidateMultiSignMethod() { parameterNames, returnParameterNames, FunctionType::Kind::ValidateMultiSign, - StateMutability::Pure, + StateMutability::View, nullptr) )); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 46086f8f5d62..13fb93c3463b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3875,7 +3875,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 768895a31f7c..0704d83e240a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -3319,6 +3319,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 9eb3f379378c..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); @@ -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)); diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 5360a2778677..60533522e36b 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -598,6 +598,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"( diff --git a/test/libsolidity/syntaxTests/tron/validatemultisign_mutability.sol b/test/libsolidity/syntaxTests/tron/validatemultisign_mutability.sol new file mode 100644 index 000000000000..3be19672eb7d --- /dev/null +++ b/test/libsolidity/syntaxTests/tron/validatemultisign_mutability.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure returns (bool) { + return validatemultisign(address(0), 0, bytes32(0), new bytes[](0)); + } +} +// ---- +// TypeError 2527: (74-134): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". From 5ff386e2f5d91011a4cbacb7285335200a8e0a18 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 11:27:32 +0800 Subject: [PATCH 30/41] chore(codegen): assert TRON builtin arities --- libsolidity/codegen/ExpressionCompiler.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 0704d83e240a..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]); From 447974f35c8f3ad1910ec3c2994e9cfbef0f67f8 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 11:14:36 +0800 Subject: [PATCH 31/41] fix(abi): restrict TRON address prefixes --- libsolidity/codegen/YulUtilFunctions.cpp | 4 +++- .../semanticTests/abiEncoderV2/cleanup/address.sol | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 4c3cd8c0fc69..97c4a0cda0c3 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -4056,7 +4056,9 @@ std::string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertO case Type::Category::Address: case Type::Category::Contract: { - templ("condition", "eq(value, " + cleanupFunction(IntegerType(168)) + "(value))"); + // Accept the canonical 20-byte form and TRON's 0x41-prefixed 21-byte form. + // Comparing all high bits at once also rejects values wider than 168 bits. + templ("condition", "or(iszero(div(value, 0x10000000000000000000000000000000000000000)), eq(div(value, 0x10000000000000000000000000000000000000000), 0x41))"); break; } case Type::Category::Integer: diff --git a/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol b/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol index 44a462d1319b..48ad515e2184 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol @@ -21,6 +21,10 @@ contract C { // g(address): 0xabcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff // f(uint256): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff // g(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff +// f(uint256): 0x41abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff +// g(address): 0x41abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff +// f(uint256): 0x42abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff +// g(address): 0x42abcdef0123456789abcdef0123456789abcdefff -> FAILURE // f(uint256): 0x010000000000000000000000000000000000000000 -> 0 // g(address): 0x010000000000000000000000000000000000000000 -> FAILURE // f(uint256): 0x01abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff From 906913fb850fbf02ae4bd9843cf067d4705c8198 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 11:17:19 +0800 Subject: [PATCH 32/41] fix(smt): havoc TRON state effects conservatively --- libsolidity/formal/SMTEncoder.cpp | 62 +++++++++++++++++-- .../tron/state_mutation_havocs_balance.sol | 13 ++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 95268032af0e..ad4120839a92 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -726,16 +726,66 @@ 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(); + 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(), 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. From c2bf7882c5ff27d3776fd1b90bddff5d48d265a3 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 15:03:23 +0800 Subject: [PATCH 33/41] fix: preserve TRON target compatibility --- liblangutil/EVMVersion.cpp | 18 -------- libsolidity/codegen/YulUtilFunctions.cpp | 4 +- test/CMakeLists.txt | 1 - test/liblangutil/EVMVersion.cpp | 41 ------------------- .../abiEncoderV2/cleanup/address.sol | 4 -- 5 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 test/liblangutil/EVMVersion.cpp diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index dae910af38e7..4fbfe8de41b0 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -61,24 +61,6 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _eofVersi case Instruction::TSTORE: case Instruction::TLOAD: return supportsTransientStorage(); - // TRON-specific instructions are not part of the EOF instruction set. - case Instruction::CALLTOKEN: - case Instruction::TOKENBALANCE: - case Instruction::CALLTOKENVALUE: - case Instruction::CALLTOKENID: - case Instruction::ISCONTRACT: - case Instruction::NATIVEFREEZE: - case Instruction::NATIVEUNFREEZE: - case Instruction::NATIVEFREEZEEXPIRETIME: - case Instruction::NATIVEVOTE: - case Instruction::NATIVEWITHDRAWREWARD: - case Instruction::NATIVEFREEZEBALANCEV2: - case Instruction::NATIVEUNFREEZEBALANCEV2: - case Instruction::NATIVECANCELALLUNFREEZEV2: - case Instruction::NATIVEWITHDRAWEXPIREUNFREEZE: - case Instruction::NATIVEDELEGATERESOURCE: - case Instruction::NATIVEUNDELEGATERESOURCE: - return !_eofVersion.has_value(); // Instructions below are deprecated in EOF case Instruction::CALL: case Instruction::CALLCODE: diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 97c4a0cda0c3..4c3cd8c0fc69 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -4056,9 +4056,7 @@ std::string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertO case Type::Category::Address: case Type::Category::Contract: { - // Accept the canonical 20-byte form and TRON's 0x41-prefixed 21-byte form. - // Comparing all high bits at once also rejects values wider than 168 bits. - templ("condition", "or(iszero(div(value, 0x10000000000000000000000000000000000000000)), eq(div(value, 0x10000000000000000000000000000000000000000), 0x41))"); + templ("condition", "eq(value, " + cleanupFunction(IntegerType(168)) + "(value))"); break; } case Type::Category::Integer: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8af881053fa6..dbbcf3eaec81 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,7 +63,6 @@ detect_stray_source_files("${libevmasm_sources}" "libevmasm/") set(liblangutil_sources liblangutil/CharStream.cpp - liblangutil/EVMVersion.cpp liblangutil/Scanner.cpp liblangutil/SourceLocation.cpp ) diff --git a/test/liblangutil/EVMVersion.cpp b/test/liblangutil/EVMVersion.cpp deleted file mode 100644 index 930ed4caaceb..000000000000 --- a/test/liblangutil/EVMVersion.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -// SPDX-License-Identifier: GPL-3.0 - -#include -#include - -#include - -namespace solidity::langutil::test -{ - -BOOST_AUTO_TEST_SUITE(EVMVersionTest) - -BOOST_AUTO_TEST_CASE(tron_instructions_are_not_available_in_eof) -{ - for (unsigned opcode = 0xd0; opcode <= 0xdf; ++opcode) - { - auto const instruction = static_cast(opcode); - BOOST_TEST(EVMVersion::osaka().hasOpcode(instruction, std::nullopt)); - BOOST_TEST(!EVMVersion::osaka().hasOpcode(instruction, 1)); - } -} - -BOOST_AUTO_TEST_SUITE_END() - -} diff --git a/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol b/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol index 48ad515e2184..44a462d1319b 100644 --- a/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol +++ b/test/libsolidity/semanticTests/abiEncoderV2/cleanup/address.sol @@ -21,10 +21,6 @@ contract C { // g(address): 0xabcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff // f(uint256): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff // g(address): 0xffffffffffffffffffffffffffffffffffffffff -> 0xffffffffffffffffffffffffffffffffffffffff -// f(uint256): 0x41abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff -// g(address): 0x41abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff -// f(uint256): 0x42abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff -// g(address): 0x42abcdef0123456789abcdef0123456789abcdefff -> FAILURE // f(uint256): 0x010000000000000000000000000000000000000000 -> 0 // g(address): 0x010000000000000000000000000000000000000000 -> FAILURE // f(uint256): 0x01abcdef0123456789abcdef0123456789abcdefff -> 0xabcdef0123456789abcdef0123456789abcdefff From d18726334c034738899a3ba6a53fa85e7552b8bf Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 15:30:14 +0800 Subject: [PATCH 34/41] fix: keep Standard JSON viaIR behavior unchanged --- libsolidity/interface/StandardCompiler.cpp | 15 +-------------- .../input.json | 1 - .../input.json | 1 - .../standard_no_append_cbor/input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 1 - .../input.json | 12 ------------ .../output.json | 16 ---------------- .../standard_viair_requested/input.json | 3 +-- .../standard_yul_cfg_json_export/input.json | 1 - 25 files changed, 2 insertions(+), 65 deletions(-) delete mode 100644 test/cmdlineTests/standard_viair_experimental_warning/input.json delete mode 100644 test/cmdlineTests/standard_viair_experimental_warning/output.json diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index f5f737310c0d..85495a9e37e2 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -430,7 +430,7 @@ std::optional checkAuxiliaryInputKeys(Json const& _input) std::optional checkSettingsKeys(Json const& _input) { - static std::set keys{"debug", "evmVersion", "eofVersion", "experimentalViaIR", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; + static std::set keys{"debug", "evmVersion", "eofVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; return checkKeys(_input, keys, "settings"); } @@ -820,19 +820,6 @@ std::variant StandardCompiler::parseI return formatFatalError(Error::Type::JSONError, "\"settings.viaIR\" must be a Boolean."); ret.viaIR = settings["viaIR"].get(); } - bool experimentalViaIRAcknowledged = false; - if (settings.contains("experimentalViaIR")) - { - if (!settings["experimentalViaIR"].is_boolean()) - return formatFatalError(Error::Type::JSONError, "\"settings.experimentalViaIR\" must be a Boolean."); - experimentalViaIRAcknowledged = settings["experimentalViaIR"].get(); - } - if (ret.viaIR && !experimentalViaIRAcknowledged) - ret.errors.emplace_back(formatError( - Error::Type::Warning, - "general", - "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning." - )); if (settings.contains("evmVersion")) { diff --git a/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json b/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json index c306a4e01687..6ff7662901e4 100644 --- a/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json +++ b/test/cmdlineTests/standard_debug_info_in_evm_asm_via_ir_location/input.json @@ -14,7 +14,6 @@ "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, "outputSelection": diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json index d93f78f4d145..06b89d0e01bf 100644 --- a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json @@ -7,7 +7,6 @@ "outputSelection": {"*": {"*": ["ir"]}}, "optimizer": {"enabled": true}, "viaIR": true, - "experimentalViaIR": true, "debug": {"debugInfo": []} } } diff --git a/test/cmdlineTests/standard_no_append_cbor/input.json b/test/cmdlineTests/standard_no_append_cbor/input.json index 8fefb3ee0f50..493325506dda 100644 --- a/test/cmdlineTests/standard_no_append_cbor/input.json +++ b/test/cmdlineTests/standard_no_append_cbor/input.json @@ -10,7 +10,6 @@ "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json b/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json index a1652b5633d9..f12e45bf2fa9 100644 --- a/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json +++ b/test/cmdlineTests/standard_no_append_cbor_with_metadata_hash/input.json @@ -10,7 +10,6 @@ "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json index aff5263670d6..422a54f90d9f 100644 --- a/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_compatible/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "debug": { "debugInfo": [ "ethdebug" diff --git a/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json b/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json index 8fac23d615d9..8f8edcd29203 100644 --- a/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json +++ b/test/cmdlineTests/standard_output_debuginfo_ethdebug_incompatible/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "debug": { "debugInfo": [ "ethdebug" diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json index 15d1cd6ace39..8ed44ca05de2 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json index add6ead1e0a0..27c400f9e946 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json index 9394bea47fc3..ad74649f5d1d 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json index 9394bea47fc3..ad74649f5d1d 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_and_deployedbytecode_optimizer_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json index 9a0d64f088a5..35c8f940e82d 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json index 028e195d408c..e4681967f7db 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_bytecode_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json index 4df522786b38..6239a7275e4d 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json index 1929a6a97ccd..fdb6c95b813e 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_deployedbytecode_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": false }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json index 279c7ef44848..5bc9d5467345 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "outputSelection": { "*": { "*": [ diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json index e87a97e5bb08..8952a241f6e7 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "outputSelection": { "*": { "*": [ diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json index 641dcfb1827e..a684e0dc073b 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_ir/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json index c743d1609f2c..1f09e3a70253 100644 --- a/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json +++ b/test/cmdlineTests/standard_output_selection_ethdebug_optimize_iroptimized/input.json @@ -10,7 +10,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "optimizer": { "enabled": true }, diff --git a/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json b/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json index d2490aef88f6..dc86ac68494e 100644 --- a/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json +++ b/test/cmdlineTests/standard_stack_too_deep_from_code_transform/input.json @@ -5,7 +5,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "outputSelection": { "*": {"*": ["evm.bytecode"]} } diff --git a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json index d2490aef88f6..dc86ac68494e 100644 --- a/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json +++ b/test/cmdlineTests/standard_via_ir_intersecting_recursive_cycles_stack_too_deep/input.json @@ -5,7 +5,6 @@ }, "settings": { "viaIR": true, - "experimentalViaIR": true, "outputSelection": { "*": {"*": ["evm.bytecode"]} } diff --git a/test/cmdlineTests/standard_viair_experimental_warning/input.json b/test/cmdlineTests/standard_viair_experimental_warning/input.json deleted file mode 100644 index 0b05d0d0e7cd..000000000000 --- a/test/cmdlineTests/standard_viair_experimental_warning/input.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "language": "Solidity", - "sources": { - "A.sol": { - "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity >=0.0; contract A {}" - } - }, - "settings": { - "viaIR": true, - "outputSelection": {} - } -} diff --git a/test/cmdlineTests/standard_viair_experimental_warning/output.json b/test/cmdlineTests/standard_viair_experimental_warning/output.json deleted file mode 100644 index c74bd9d6b3a5..000000000000 --- a/test/cmdlineTests/standard_viair_experimental_warning/output.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "errors": [ - { - "component": "general", - "formattedMessage": "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning.", - "message": "The via-IR pipeline is experimental in the TRON Solidity compiler. Set \"settings.experimentalViaIR\" to true to acknowledge this and suppress this warning.", - "severity": "warning", - "type": "Warning" - } - ], - "sources": { - "A.sol": { - "id": 0 - } - } -} diff --git a/test/cmdlineTests/standard_viair_requested/input.json b/test/cmdlineTests/standard_viair_requested/input.json index 542b0372d6b0..76ae807a615c 100644 --- a/test/cmdlineTests/standard_viair_requested/input.json +++ b/test/cmdlineTests/standard_viair_requested/input.json @@ -16,7 +16,6 @@ { "*": { "*": ["ir", "evm.bytecode.object", "evm.bytecode.generatedSources", "evm.deployedBytecode.object"] } }, - "viaIR": true, - "experimentalViaIR": true + "viaIR": true } } diff --git a/test/cmdlineTests/standard_yul_cfg_json_export/input.json b/test/cmdlineTests/standard_yul_cfg_json_export/input.json index 545bf50ae705..b5353721ee09 100644 --- a/test/cmdlineTests/standard_yul_cfg_json_export/input.json +++ b/test/cmdlineTests/standard_yul_cfg_json_export/input.json @@ -8,7 +8,6 @@ "enabled": true }, "viaIR": true, - "experimentalViaIR": true, "outputSelection": {"*": {"*": ["yulCFGJson"]}} } } From 65b4dfd6ec8eb430992654e662b69b2912a54db3 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 11:18:48 +0800 Subject: [PATCH 35/41] fix: clarify TRON builtin parameter names --- libsolidity/analysis/GlobalContext.cpp | 16 ++++++++-------- .../syntaxTests/tron/builtin_named_arguments.sol | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 test/libsolidity/syntaxTests/tron/builtin_named_arguments.sol diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index b9666889cfeb..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; @@ -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/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)}); + } +} +// ---- From a29792517acca33faaa1fb08be8e46c6827129f3 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 10:55:07 +0800 Subject: [PATCH 36/41] fix: harden compiler and regression inputs --- libsolidity/interface/StandardCompiler.cpp | 2 ++ scripts/regressions.py | 29 +++++++++++++++---- scripts/splitSources.py | 17 +++++++++-- .../input.json | 13 +++++++++ .../output.json | 11 +++++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 test/cmdlineTests/standard_wrong_type_bytecodeHash/input.json create mode 100644 test/cmdlineTests/standard_wrong_type_bytecodeHash/output.json diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 85495a9e37e2..fbb1a4c99868 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -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())) diff --git a/scripts/regressions.py b/scripts/regressions.py index e30c0aeb9f8d..8206b359c018 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 5e783b2d5aa9..8607ee2d9cbf 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 line[:12] != "==== 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" + } + ] +} From 8072fcfd264d61a8c9c3958af5e37d85b8c93b47 Mon Sep 17 00:00:00 2001 From: Asuka Date: Tue, 28 Jul 2026 17:19:00 +0800 Subject: [PATCH 37/41] fix(lsp): avoid scanning the filesystem root --- libsolidity/lsp/LanguageServer.cpp | 18 ++++++++++++++++-- test/lsp.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) 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/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 From 4b8114414a9c74b236e68e77fac2c574d84edccc Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 29 Jul 2026 11:01:05 +0800 Subject: [PATCH 38/41] docs: fix bug metadata for TRON 0.8.30 security backports - Mark SOL-2025-1, SOL-2026-1, SOL-2026-2 and SOL-2026-3 as fixed in 0.8.30 on this release line: the fixes were backported from upstream 0.8.32/0.8.34/0.8.36, so the bug list must not claim 0.8.30 is still affected. - Restore TRON terminology (Trx) in legacy bug descriptions that were inadvertently overwritten with upstream wording (Ether) by the cherry-picks. - Regenerate docs/bugs_by_version.json from docs/bugs.json and this branch's Changelog.md; entries for versions not released on this branch (0.8.31+) drop out accordingly. --- docs/bugs.json | 18 +++++++-------- docs/bugs_by_version.json | 48 ++------------------------------------- 2 files changed, 11 insertions(+), 55 deletions(-) diff --git a/docs/bugs.json b/docs/bugs.json index 06a74c3b2835..1381139aa122 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" }, { @@ -523,7 +523,7 @@ "uid": "SOL-2017-5", "name": "ZeroFunctionSelector", "summary": "It is possible to craft the name of a function such that it is executed instead of the fallback function in very specific circumstances.", - "description": "If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Ether is sent to the contract without data.", + "description": "If a function has a selector consisting only of zeros, is payable and part of a contract that does not have a fallback function and at most five external functions in total, this function is called instead of the fallback function if Trx is sent to the contract without data.", "fixed": "0.4.18", "severity": "very low" }, @@ -608,8 +608,8 @@ { "uid": "SOL-2016-7", "name": "LibrariesNotCallableFromPayableFunctions", - "summary": "Library functions threw an exception when called from a call that received Ether.", - "description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.", + "summary": "Library functions threw an exception when called from a call that received Trx.", + "description": "Library functions are protected against sending them Trx through a call. Since the DELEGATECALL opcode forwards the information about how much Trx was sent with a call, the library function incorrectly assumed that Trx was sent to the library and threw an exception.", "severity": "low", "introduced": "0.4.0", "fixed": "0.4.2" @@ -617,8 +617,8 @@ { "uid": "SOL-2016-6", "name": "SendFailsForZeroEther", - "summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.", - "description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.", + "summary": "The send function did not provide enough gas to the recipient if no Trx was sent with it.", + "description": "The recipient of an Trx transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.", "severity": "low", "fixed": "0.4.0" }, diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 0cc12bfe9eb7..25f987a2fc82 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -2070,53 +2070,9 @@ "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" - ], - "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", @@ -2206,4 +2162,4 @@ ], "released": "2021-09-29" } -} +} \ No newline at end of file From 174578fe0c25cda0264e9429609162a732452939 Mon Sep 17 00:00:00 2001 From: Asuka Date: Wed, 29 Jul 2026 11:22:01 +0800 Subject: [PATCH 39/41] fix: reject Ethereum units in AST imports --- libsolidity/analysis/SyntaxChecker.cpp | 11 +++++ libsolidity/ast/ASTJsonImporter.cpp | 13 +++--- libsolidity/ast/Types.cpp | 13 +++--- libsolidity/interface/StandardCompiler.cpp | 10 +++++ test/libsolidity/SolidityTypes.cpp | 20 +++++++++ test/libsolidity/StandardCompiler.cpp | 50 ++++++++++++++++++++++ 6 files changed, 103 insertions(+), 14 deletions(-) diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 1e3f5f1c2a56..45920e63a91c 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -288,6 +288,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/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 13fb93c3463b..36b73ae98069 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"); diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index fbb1a4c99868..117dc0b1a07b 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -1385,6 +1385,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/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 60533522e36b..34b268b75616 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1816,6 +1816,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"( From 0d4fe485bda9c721e02727af22ca4cf5ee5247db Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 30 Jul 2026 18:40:28 +0800 Subject: [PATCH 40/41] fix: harden SMT, JSON input, and TVM gas estimation --- libevmasm/ConstantOptimiser.cpp | 2 +- libevmasm/GasMeter.cpp | 94 ++++++---- libevmasm/GasMeter.h | 11 +- libsolidity/formal/Predicate.cpp | 7 +- libsolidity/formal/SMTEncoder.cpp | 4 +- libsolidity/formal/SymbolicState.cpp | 12 +- libsolidity/formal/SymbolicTypes.cpp | 7 + libsolidity/interface/StandardCompiler.cpp | 4 +- libyul/backends/evm/EVMMetrics.cpp | 2 +- test/libevmasm/GasMeter.cpp | 167 ++++++++++++++++++ test/libevmasm/Optimiser.cpp | 26 +++ test/libsolidity/StandardCompiler.cpp | 36 ++++ .../smtCheckerTests/tron/magic_members.sol | 39 ++++ .../tron/magic_members_bmc.sol | 39 ++++ .../tron/state_mutation_return_ranges.sol | 23 +++ .../tron/state_mutation_return_ranges_bmc.sol | 23 +++ test/libyul/Metrics.cpp | 21 +++ 17 files changed, 468 insertions(+), 49 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/tron/magic_members.sol create mode 100644 test/libsolidity/smtCheckerTests/tron/magic_members_bmc.sol create mode 100644 test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges.sol create mode 100644 test/libsolidity/smtCheckerTests/tron/state_mutation_return_ranges_bmc.sol 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 e7e107fe1d31..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; @@ -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,11 +109,18 @@ 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; } @@ -192,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: @@ -202,11 +207,11 @@ 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: @@ -267,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) @@ -304,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 06a4b8b609de..3d9448104b28 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -190,6 +190,9 @@ namespace GasCosts 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; @@ -267,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/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 ad4120839a92..d4fbc73c734b 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -741,6 +741,8 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) // 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(), @@ -1470,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..1a7a123848cb 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,7 +249,10 @@ 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 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 117dc0b1a07b..06a98af1d70f 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()) @@ -526,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()) 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/test/libevmasm/GasMeter.cpp b/test/libevmasm/GasMeter.cpp index aeac86a2931e..53dadf325ac7 100644 --- a/test/libevmasm/GasMeter.cpp +++ b/test/libevmasm/GasMeter.cpp @@ -62,6 +62,12 @@ 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( @@ -138,6 +144,145 @@ BOOST_AUTO_TEST_CASE(tvm_sstore_uses_java_tron_set_and_reset_prices) 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}) @@ -233,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 b53205e959e5..a70c88aa4058 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/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 34b268b75616..b2eeb8738390 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -323,6 +323,42 @@ 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(invalid_language) { char const* input = R"( 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_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/libyul/Metrics.cpp b/test/libyul/Metrics.cpp index 9e74cd150e49..ce97b0d66bd3 100644 --- a/test/libyul/Metrics.cpp +++ b/test/libyul/Metrics.cpp @@ -25,10 +25,14 @@ #include #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() + } From 364cfd36ed3992e89d5655b2caae743ccdb45810 Mon Sep 17 00:00:00 2001 From: Asuka Date: Fri, 31 Jul 2026 07:17:23 +0800 Subject: [PATCH 41/41] fix: align SMT library constraints and JSON validation --- libsolidity/formal/SymbolicState.cpp | 6 +- libsolidity/interface/StandardCompiler.cpp | 45 ++++++------ test/libsolidity/StandardCompiler.cpp | 72 +++++++++++++++++-- .../smtCheckerTests/special/ether_units.sol | 17 ----- .../smtCheckerTests/special/trx_units.sol | 17 +++++ .../library_nonpayable_transaction_values.sol | 14 ++++ ...rary_nonpayable_transaction_values_bmc.sol | 14 ++++ .../smtCheckerTests/types/address_balance.sol | 6 +- 8 files changed, 145 insertions(+), 46 deletions(-) delete mode 100644 test/libsolidity/smtCheckerTests/special/ether_units.sol create mode 100644 test/libsolidity/smtCheckerTests/special/trx_units.sol create mode 100644 test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values.sol create mode 100644 test/libsolidity/smtCheckerTests/tron/library_nonpayable_transaction_values_bmc.sol diff --git a/libsolidity/formal/SymbolicState.cpp b/libsolidity/formal/SymbolicState.cpp index 1a7a123848cb..8e55922ae573 100644 --- a/libsolidity/formal/SymbolicState.cpp +++ b/libsolidity/formal/SymbolicState.cpp @@ -257,7 +257,11 @@ smtutil::Expression SymbolicState::txNonPayableConstraint() const 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/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 06a98af1d70f..c1e6ae0b8dab 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -649,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()); @@ -772,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(); } } @@ -872,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, diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index b2eeb8738390..0a86bb40b652 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -359,6 +359,42 @@ BOOST_AUTO_TEST_CASE(output_selection_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"( @@ -564,9 +600,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( @@ -574,10 +610,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); @@ -611,6 +653,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\"}," 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/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.