Merge pull request #11047 from ethereum/bytesToBytesNNConversion

Bytes to bytesNN conversion
This commit is contained in:
chriseth 2021-04-26 11:51:41 +02:00 committed by GitHub
commit 659da4bdc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 271 additions and 29 deletions

View File

@ -1,6 +1,7 @@
### 0.8.5 (unreleased)
Language Features:
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
Compiler Features:

View File

@ -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

View File

@ -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.");

View File

@ -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

View File

@ -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";

View File

@ -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, ""); }

View File

@ -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(

View File

@ -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), "");

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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)));
}
}
// ----

View File

@ -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.