mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #11047 from ethereum/bytesToBytesNNConversion
Bytes to bytesNN conversion
This commit is contained in:
commit
659da4bdc7
@ -1,6 +1,7 @@
|
||||
### 0.8.5 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
|
@ -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
|
||||
|
@ -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.");
|
||||
|
@ -1710,19 +1710,23 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
{
|
||||
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
||||
{
|
||||
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType);
|
||||
solAssert(!!resultArrayType, "");
|
||||
solAssert(
|
||||
argArrayType->location() != DataLocation::Storage ||
|
||||
(
|
||||
if (auto resultArrayType = dynamic_cast<ArrayType const*>(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
|
||||
|
@ -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<ArrayType const&>(_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";
|
||||
|
@ -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, ""); }
|
||||
|
@ -961,8 +961,31 @@ void CompilerUtils::convertType(
|
||||
}
|
||||
case Type::Category::Array:
|
||||
{
|
||||
solAssert(targetTypeCategory == stackTypeCategory, "");
|
||||
auto const& typeOnStack = dynamic_cast<ArrayType const&>(_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<FixedBytesType const &>(_targetType)
|
||||
),
|
||||
typeOnStack.sizeOnStack(),
|
||||
1
|
||||
);
|
||||
break;
|
||||
}
|
||||
solAssert(targetTypeCategory == stackTypeCategory, "");
|
||||
auto const& targetType = dynamic_cast<ArrayType const&>(_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<ArraySliceType const&>(_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<FixedBytesType const &>(_targetType)
|
||||
),
|
||||
2,
|
||||
1
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
solAssert(_targetType.category() == Type::Category::Array, "");
|
||||
auto const& targetArrayType = dynamic_cast<ArrayType const&>(_targetType);
|
||||
solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), "");
|
||||
solAssert(
|
||||
|
@ -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<ArraySliceType const&>(_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<FixedBytesType const &>(_to));
|
||||
}
|
||||
solAssert(_to.category() == Type::Category::Array, "");
|
||||
auto const& targetType = dynamic_cast<ArrayType const&>(_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<ArrayType const&>(_from);
|
||||
if (_to.category() == Type::Category::FixedBytes)
|
||||
{
|
||||
solAssert(fromArrayType.isByteArray(), "Array types other than bytes not convertible to bytesNN.");
|
||||
return bytesToFixedBytesConversionFunction(fromArrayType, dynamic_cast<FixedBytesType const &>(_to));
|
||||
}
|
||||
solAssert(_to.category() == Type::Category::Array, "");
|
||||
return arrayConversionFunction(
|
||||
dynamic_cast<ArrayType const&>(_from),
|
||||
dynamic_cast<ArrayType const&>(_to)
|
||||
);
|
||||
return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_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 := <arrayLen>(array<?fromCalldata>, len</fromCalldata>)
|
||||
let dataArea := array
|
||||
<?fromMemory>
|
||||
dataArea := <dataArea>(array)
|
||||
</fromMemory>
|
||||
<?fromStorage>
|
||||
if gt(length, 31) { dataArea := <dataArea>(array) }
|
||||
</fromStorage>
|
||||
|
||||
<?fromCalldata>
|
||||
value := <cleanup>(calldataload(dataArea))
|
||||
<!fromCalldata>
|
||||
value := <extractValue>(dataArea)
|
||||
</fromCalldata>
|
||||
|
||||
if lt(length, <fixedBytesLen>) {
|
||||
value := and(
|
||||
value,
|
||||
<shl>(
|
||||
mul(8, sub(<fixedBytesLen>, length)),
|
||||
<mask>
|
||||
)
|
||||
)
|
||||
}
|
||||
)");
|
||||
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), "");
|
||||
|
@ -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"
|
||||
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -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)));
|
||||
}
|
||||
}
|
||||
// ----
|
@ -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.
|
Loading…
Reference in New Issue
Block a user