diff --git a/Changelog.md b/Changelog.md index 4b997c391..c453ace9b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.8.5 (unreleased) Language Features: +* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``. Compiler Features: diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst index 2e9e02651..38e0ca6f1 100644 --- a/docs/types/conversion.rst +++ b/docs/types/conversion.rst @@ -99,6 +99,27 @@ rules explicit:: uint8 d = uint8(uint16(a)); // d will be 0x34 uint8 e = uint8(bytes1(a)); // e will be 0x12 +``bytes`` arrays and ``bytes`` calldata slices can be converted explicitly to fixed bytes types (``bytes1``/.../``bytes32``). +In case the array is longer than the target fixed bytes type, truncation at the end will happen. +If the array is shorter than the target type, it will be padded with zeros at the end. + +:: + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity ^0.8.5; + + contract C { + bytes s = "abcdefgh"; + function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) { + require(c.length == 16, ""); + bytes16 b = bytes16(m); // if length of m is greater than 16, truncation will happen + b = bytes16(s); // padded on the right, so result is "abcdefgh\0\0\0\0\0\0\0\0" + bytes3 b1 = bytes3(s); // truncated, b1 equals to "abc" + b = bytes16(c[:8]); // also padded with zeros + return (b, b1); + } + } + .. _types-conversion-literals: Conversions between Literals and Elementary Types diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index 540382083..8d21221b4 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -492,7 +492,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete :: // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; + pragma solidity >0.8.4 <0.9.0; contract Proxy { /// @dev Address of the client contract managed by proxy i.e., this contract address client; @@ -504,13 +504,9 @@ Array slices are useful to ABI-decode secondary data passed in function paramete /// Forward call to "setOwner(address)" that is implemented by client /// after doing basic validation on the address argument. function forward(bytes calldata _payload) external { - // Since ABI decoding requires padded data, we cannot - // use abi.decode(_payload[:4], (bytes4)). - bytes4 sig = - _payload[0] | - (bytes4(_payload[1]) >> 8) | - (bytes4(_payload[2]) >> 16) | - (bytes4(_payload[3]) >> 24); + bytes4 sig = bytes4(_payload[:4]); + // Due to truncating behaviour, bytes4(_payload) performs identically. + // bytes4 sig = bytes4(_payload); if (sig == bytes4(keccak256("setOwner(address)"))) { address owner = abi.decode(_payload[4:], (address)); require(owner != address(0), "Address of owner cannot be zero."); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d13c2eaae..e0c288aeb 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1710,19 +1710,23 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( { if (auto argArrayType = dynamic_cast(argType)) { - auto resultArrayType = dynamic_cast(resultType); - solAssert(!!resultArrayType, ""); - solAssert( - argArrayType->location() != DataLocation::Storage || - ( + if (auto resultArrayType = dynamic_cast(resultType)) + solAssert( + argArrayType->location() != DataLocation::Storage || ( - resultArrayType->isPointer() || - (argArrayType->isByteArray() && resultArrayType->isByteArray()) - ) && - resultArrayType->location() == DataLocation::Storage - ), - "Invalid explicit conversion to storage type." - ); + ( + resultArrayType->isPointer() || + (argArrayType->isByteArray() && resultArrayType->isByteArray()) + ) && + resultArrayType->location() == DataLocation::Storage + ), + "Invalid explicit conversion to storage type." + ); + else + solAssert( + argArrayType->isByteArray() && !argArrayType->isString() && resultType->category() == Type::Category::FixedBytes, + "" + ); } } else diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c076d892f..c4466e398 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1570,9 +1570,9 @@ BoolResult ArrayType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (isImplicitlyConvertibleTo(_convertTo)) return true; - // allow conversion bytes <-> string + // allow conversion bytes <-> string and bytes -> bytesNN if (_convertTo.category() != category()) - return false; + return isByteArray() && !isString() && _convertTo.category() == Type::Category::FixedBytes; auto& convertTo = dynamic_cast(_convertTo); if (convertTo.location() != location()) return false; @@ -1929,6 +1929,13 @@ BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const ); } +BoolResult ArraySliceType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return + isImplicitlyConvertibleTo(_convertTo) || + m_arrayType.isExplicitlyConvertibleTo(_convertTo); +} + string ArraySliceType::richIdentifier() const { return m_arrayType.richIdentifier() + "_slice"; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 7bcd3d44c..95aa3150f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -889,6 +889,7 @@ public: Category category() const override { return Category::ArraySlice; } BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 01143bf42..08180be4e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -961,8 +961,31 @@ void CompilerUtils::convertType( } case Type::Category::Array: { - solAssert(targetTypeCategory == stackTypeCategory, ""); auto const& typeOnStack = dynamic_cast(_typeOnStack); + if (_targetType.category() == Type::Category::FixedBytes) + { + solAssert( + typeOnStack.isByteArray() && !typeOnStack.isString(), + "Array types other than bytes not convertible to bytesNN." + ); + solAssert(typeOnStack.isDynamicallySized(), ""); + + bool fromCalldata = typeOnStack.dataStoredIn(DataLocation::CallData); + solAssert(typeOnStack.sizeOnStack() == (fromCalldata ? 2 : 1), ""); + if (fromCalldata) + m_context << Instruction::SWAP1; + + m_context.callYulFunction( + m_context.utilFunctions().bytesToFixedBytesConversionFunction( + typeOnStack, + dynamic_cast(_targetType) + ), + typeOnStack.sizeOnStack(), + 1 + ); + break; + } + solAssert(targetTypeCategory == stackTypeCategory, ""); auto const& targetType = dynamic_cast(_targetType); switch (targetType.location()) { @@ -1066,8 +1089,30 @@ void CompilerUtils::convertType( } case Type::Category::ArraySlice: { - solAssert(_targetType.category() == Type::Category::Array, ""); auto& typeOnStack = dynamic_cast(_typeOnStack); + if (_targetType.category() == Type::Category::FixedBytes) + { + solAssert( + typeOnStack.arrayType().isByteArray() && !typeOnStack.arrayType().isString(), + "Array types other than bytes not convertible to bytesNN." + ); + solAssert(typeOnStack.isDynamicallySized(), ""); + solAssert(typeOnStack.dataStoredIn(DataLocation::CallData), ""); + solAssert(typeOnStack.sizeOnStack() == 2, ""); + + m_context << Instruction::SWAP1; + m_context.callYulFunction( + m_context.utilFunctions().bytesToFixedBytesConversionFunction( + typeOnStack.arrayType(), + dynamic_cast(_targetType) + ), + 2, + 1 + ); + break; + } + + solAssert(_targetType.category() == Type::Category::Array, ""); auto const& targetArrayType = dynamic_cast(_targetType); solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), ""); solAssert( diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 1f0889e59..5134822bb 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -3084,8 +3084,13 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) } else if (_from.category() == Type::Category::ArraySlice) { - solAssert(_to.category() == Type::Category::Array, ""); auto const& fromType = dynamic_cast(_from); + if (_to.category() == Type::Category::FixedBytes) + { + solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN."); + return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast(_to)); + } + solAssert(_to.category() == Type::Category::Array, ""); auto const& targetType = dynamic_cast(_to); solAssert(fromType.arrayType().isImplicitlyConvertibleTo(targetType), ""); @@ -3117,11 +3122,14 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) } else if (_from.category() == Type::Category::Array) { + auto const& fromArrayType = dynamic_cast(_from); + if (_to.category() == Type::Category::FixedBytes) + { + solAssert(fromArrayType.isByteArray(), "Array types other than bytes not convertible to bytesNN."); + return bytesToFixedBytesConversionFunction(fromArrayType, dynamic_cast(_to)); + } solAssert(_to.category() == Type::Category::Array, ""); - return arrayConversionFunction( - dynamic_cast(_from), - dynamic_cast(_to) - ); + return arrayConversionFunction(fromArrayType, dynamic_cast(_to)); } if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) @@ -3342,6 +3350,64 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) }); } +string YulUtilFunctions::bytesToFixedBytesConversionFunction(ArrayType const& _from, FixedBytesType const& _to) +{ + solAssert(_from.isByteArray() && !_from.isString(), ""); + solAssert(_from.isDynamicallySized(), ""); + string functionName = "convert_bytes_to_fixedbytes_from_" + _from.identifier() + "_to_" + _to.identifier(); + return m_functionCollector.createFunction(functionName, [&](auto& _args, auto& _returnParams) { + _args = { "array" }; + bool fromCalldata = _from.dataStoredIn(DataLocation::CallData); + if (fromCalldata) + _args.emplace_back("len"); + _returnParams = {"value"}; + Whiskers templ(R"( + let length := (array, len) + let dataArea := array + + dataArea := (array) + + + if gt(length, 31) { dataArea := (array) } + + + + value := (calldataload(dataArea)) + + value := (dataArea) + + + if lt(length, ) { + value := and( + value, + ( + mul(8, sub(, length)), + + ) + ) + } + )"); + templ("fromCalldata", fromCalldata); + templ("arrayLen", arrayLengthFunction(_from)); + templ("fixedBytesLen", to_string(_to.numBytes())); + templ("fromMemory", _from.dataStoredIn(DataLocation::Memory)); + templ("fromStorage", _from.dataStoredIn(DataLocation::Storage)); + templ("dataArea", arrayDataAreaFunction(_from)); + if (fromCalldata) + templ("cleanup", cleanupFunction(_to)); + else + templ( + "extractValue", + _from.dataStoredIn(DataLocation::Storage) ? + readFromStorage(_to, 32 - _to.numBytes(), false) : + readFromMemory(_to) + ); + templ("shl", shiftLeftFunctionDynamic()); + templ("mask", formatNumber(~((u256(1) << (256 - _to.numBytes() * 8)) - 1))); + return templ.render(); + }); +} + string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, StructType const& _to) { solAssert(_to.dataStoredIn(DataLocation::Storage), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index c39b2d507..f1eaed9fe 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -411,6 +411,10 @@ public: /// This is used for data being encoded or general type conversions in the code. std::string conversionFunction(Type const& _from, Type const& _to); + /// @returns the name of a function that converts bytes array to fixed bytes type + /// signature: (array) -> value + std::string bytesToFixedBytesConversionFunction(ArrayType const& _from, FixedBytesType const& _to); + /// @returns the name of the cleanup function for the given type and /// adds its implementation to the requested functions. /// The cleanup function defers to the validator function with "assert" diff --git a/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_cleanup.sol b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_cleanup.sol new file mode 100644 index 000000000..d9c55bcc8 --- /dev/null +++ b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_cleanup.sol @@ -0,0 +1,24 @@ +contract C { + bytes s = "abcdefghabcdefghabcdefghabcdefg"; + + function fromMemory(bytes memory m) public returns (bytes16) { + assembly { mstore(m, 14) } + return bytes16(m); + } + function fromCalldata(bytes calldata c) external returns (bytes16) { + return bytes16(c); + } + function fromStorage() external returns (bytes32) { + return bytes32(s); + } + function fromSlice(bytes calldata c) external returns (bytes8) { + return bytes8(c[0:6]); + } +} +// ==== +// compileViaYul: true +// ---- +// fromMemory(bytes): 0x20, 16, "abcdefghabcdefgh" -> "abcdefghabcdef\0\0" +// fromCalldata(bytes): 0x20, 15, "abcdefghabcdefgh" -> "abcdefghabcdefg\0" +// fromStorage() -> "abcdefghabcdefghabcdefghabcdefg\0" +// fromSlice(bytes): 0x20, 15, "abcdefghabcdefgh" -> "abcdef\0\0" diff --git a/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_simple.sol b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_simple.sol new file mode 100644 index 000000000..f495d880b --- /dev/null +++ b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_simple.sol @@ -0,0 +1,28 @@ +contract C { + bytes s = "abcdefghabcdefgh"; + bytes sLong = "abcdefghabcdefghabcdefghabcdefgh"; + + function fromMemory(bytes memory m) public returns (bytes16) { + return bytes16(m); + } + function fromCalldata(bytes calldata c) external returns (bytes16) { + return bytes16(c); + } + function fromStorage() external returns (bytes16) { + return bytes16(s); + } + function fromStorageLong() external returns (bytes32) { + return bytes32(sLong); + } + function fromSlice(bytes calldata c) external returns (bytes8) { + return bytes8(c[1:9]); + } +} +// ==== +// compileViaYul: also +// ---- +// fromMemory(bytes): 0x20, 16, "abcdefghabcdefgh" -> "abcdefghabcdefgh" +// fromCalldata(bytes): 0x20, 16, "abcdefghabcdefgh" -> "abcdefghabcdefgh" +// fromStorage() -> "abcdefghabcdefgh" +// fromStorageLong() -> "abcdefghabcdefghabcdefghabcdefgh" +// fromSlice(bytes): 0x20, 16, "abcdefghabcdefgh" -> "bcdefgha" diff --git a/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_too_long.sol b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_too_long.sol new file mode 100644 index 000000000..72aba092f --- /dev/null +++ b/test/libsolidity/semanticTests/array/bytes_to_fixed_bytes_too_long.sol @@ -0,0 +1,23 @@ +contract C { + bytes s = "abcdefghabcdefghabcdefghabcdefgha"; + + function fromMemory(bytes memory m) public returns (bytes32) { + return bytes32(m); + } + function fromCalldata(bytes calldata c) external returns (bytes32) { + return bytes32(c); + } + function fromStorage() external returns (bytes32) { + return bytes32(s); + } + function fromSlice(bytes calldata c) external returns (bytes32) { + return bytes32(c[0:33]); + } +} +// ==== +// compileViaYul: also +// ---- +// fromMemory(bytes): 0x20, 33, "abcdefghabcdefghabcdefghabcdefgh", "a" -> "abcdefghabcdefghabcdefghabcdefgh" +// fromCalldata(bytes): 0x20, 33, "abcdefghabcdefghabcdefghabcdefgh", "a" -> "abcdefghabcdefghabcdefghabcdefgh" +// fromStorage() -> "abcdefghabcdefghabcdefghabcdefgh" +// fromSlice(bytes): 0x20, 33, "abcdefghabcdefghabcdefghabcdefgh", "a" -> "abcdefghabcdefghabcdefghabcdefgh" diff --git a/test/libsolidity/syntaxTests/array/bytes_to_fixed_bytes.sol b/test/libsolidity/syntaxTests/array/bytes_to_fixed_bytes.sol new file mode 100644 index 000000000..af8cd9d51 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/bytes_to_fixed_bytes.sol @@ -0,0 +1,7 @@ +contract C { + bytes s; + function f(bytes calldata c, string memory m) public view returns (bytes3, bytes8, bytes16, bytes32) { + return (bytes3(c[0:3]), bytes8(s), bytes16(c), bytes32(bytes(m))); + } +} +// ---- \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/array/invalid/bytes_to_fixed_bytes.sol b/test/libsolidity/syntaxTests/array/invalid/bytes_to_fixed_bytes.sol new file mode 100644 index 000000000..ca38231be --- /dev/null +++ b/test/libsolidity/syntaxTests/array/invalid/bytes_to_fixed_bytes.sol @@ -0,0 +1,15 @@ +contract C { + bytes s; + function f(bytes calldata c, string memory m) public view returns (bytes3 r1, bytes16 r2, bytes32 r3) { + require(c.length >= 3, ""); + r2 = s; + r1 = c[0:3]; + r3 = bytes32(m); + r3 = m; + } +} +// ---- +// TypeError 7407: (183-184): Type bytes storage ref is not implicitly convertible to expected type bytes16. +// TypeError 7407: (199-205): Type bytes calldata slice is not implicitly convertible to expected type bytes3. +// TypeError 9640: (220-230): Explicit type conversion not allowed from "string memory" to "bytes32". +// TypeError 7407: (245-246): Type string memory is not implicitly convertible to expected type bytes32.