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)
|
### 0.8.5 (unreleased)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
|
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
@ -99,6 +99,27 @@ rules explicit::
|
|||||||
uint8 d = uint8(uint16(a)); // d will be 0x34
|
uint8 d = uint8(uint16(a)); // d will be 0x34
|
||||||
uint8 e = uint8(bytes1(a)); // e will be 0x12
|
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:
|
.. _types-conversion-literals:
|
||||||
|
|
||||||
Conversions between Literals and Elementary Types
|
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
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
pragma solidity >=0.7.0 <0.9.0;
|
pragma solidity >0.8.4 <0.9.0;
|
||||||
contract Proxy {
|
contract Proxy {
|
||||||
/// @dev Address of the client contract managed by proxy i.e., this contract
|
/// @dev Address of the client contract managed by proxy i.e., this contract
|
||||||
address client;
|
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
|
/// Forward call to "setOwner(address)" that is implemented by client
|
||||||
/// after doing basic validation on the address argument.
|
/// after doing basic validation on the address argument.
|
||||||
function forward(bytes calldata _payload) external {
|
function forward(bytes calldata _payload) external {
|
||||||
// Since ABI decoding requires padded data, we cannot
|
bytes4 sig = bytes4(_payload[:4]);
|
||||||
// use abi.decode(_payload[:4], (bytes4)).
|
// Due to truncating behaviour, bytes4(_payload) performs identically.
|
||||||
bytes4 sig =
|
// bytes4 sig = bytes4(_payload);
|
||||||
_payload[0] |
|
|
||||||
(bytes4(_payload[1]) >> 8) |
|
|
||||||
(bytes4(_payload[2]) >> 16) |
|
|
||||||
(bytes4(_payload[3]) >> 24);
|
|
||||||
if (sig == bytes4(keccak256("setOwner(address)"))) {
|
if (sig == bytes4(keccak256("setOwner(address)"))) {
|
||||||
address owner = abi.decode(_payload[4:], (address));
|
address owner = abi.decode(_payload[4:], (address));
|
||||||
require(owner != address(0), "Address of owner cannot be zero.");
|
require(owner != address(0), "Address of owner cannot be zero.");
|
||||||
|
@ -1710,8 +1710,7 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
|||||||
{
|
{
|
||||||
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
||||||
{
|
{
|
||||||
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType);
|
if (auto resultArrayType = dynamic_cast<ArrayType const*>(resultType))
|
||||||
solAssert(!!resultArrayType, "");
|
|
||||||
solAssert(
|
solAssert(
|
||||||
argArrayType->location() != DataLocation::Storage ||
|
argArrayType->location() != DataLocation::Storage ||
|
||||||
(
|
(
|
||||||
@ -1723,6 +1722,11 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
|||||||
),
|
),
|
||||||
"Invalid explicit conversion to storage type."
|
"Invalid explicit conversion to storage type."
|
||||||
);
|
);
|
||||||
|
else
|
||||||
|
solAssert(
|
||||||
|
argArrayType->isByteArray() && !argArrayType->isString() && resultType->category() == Type::Category::FixedBytes,
|
||||||
|
""
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1570,9 +1570,9 @@ BoolResult ArrayType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
|||||||
{
|
{
|
||||||
if (isImplicitlyConvertibleTo(_convertTo))
|
if (isImplicitlyConvertibleTo(_convertTo))
|
||||||
return true;
|
return true;
|
||||||
// allow conversion bytes <-> string
|
// allow conversion bytes <-> string and bytes -> bytesNN
|
||||||
if (_convertTo.category() != category())
|
if (_convertTo.category() != category())
|
||||||
return false;
|
return isByteArray() && !isString() && _convertTo.category() == Type::Category::FixedBytes;
|
||||||
auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
|
auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
|
||||||
if (convertTo.location() != location())
|
if (convertTo.location() != location())
|
||||||
return false;
|
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
|
string ArraySliceType::richIdentifier() const
|
||||||
{
|
{
|
||||||
return m_arrayType.richIdentifier() + "_slice";
|
return m_arrayType.richIdentifier() + "_slice";
|
||||||
|
@ -889,6 +889,7 @@ public:
|
|||||||
Category category() const override { return Category::ArraySlice; }
|
Category category() const override { return Category::ArraySlice; }
|
||||||
|
|
||||||
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
|
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||||
|
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||||
std::string richIdentifier() const override;
|
std::string richIdentifier() const override;
|
||||||
bool operator==(Type const& _other) const override;
|
bool operator==(Type const& _other) const override;
|
||||||
unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); }
|
unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); }
|
||||||
|
@ -961,8 +961,31 @@ void CompilerUtils::convertType(
|
|||||||
}
|
}
|
||||||
case Type::Category::Array:
|
case Type::Category::Array:
|
||||||
{
|
{
|
||||||
solAssert(targetTypeCategory == stackTypeCategory, "");
|
|
||||||
auto const& typeOnStack = dynamic_cast<ArrayType const&>(_typeOnStack);
|
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);
|
auto const& targetType = dynamic_cast<ArrayType const&>(_targetType);
|
||||||
switch (targetType.location())
|
switch (targetType.location())
|
||||||
{
|
{
|
||||||
@ -1066,8 +1089,30 @@ void CompilerUtils::convertType(
|
|||||||
}
|
}
|
||||||
case Type::Category::ArraySlice:
|
case Type::Category::ArraySlice:
|
||||||
{
|
{
|
||||||
solAssert(_targetType.category() == Type::Category::Array, "");
|
|
||||||
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
|
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);
|
auto const& targetArrayType = dynamic_cast<ArrayType const&>(_targetType);
|
||||||
solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), "");
|
solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), "");
|
||||||
solAssert(
|
solAssert(
|
||||||
|
@ -3084,8 +3084,13 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
}
|
}
|
||||||
else if (_from.category() == Type::Category::ArraySlice)
|
else if (_from.category() == Type::Category::ArraySlice)
|
||||||
{
|
{
|
||||||
solAssert(_to.category() == Type::Category::Array, "");
|
|
||||||
auto const& fromType = dynamic_cast<ArraySliceType const&>(_from);
|
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);
|
auto const& targetType = dynamic_cast<ArrayType const&>(_to);
|
||||||
|
|
||||||
solAssert(fromType.arrayType().isImplicitlyConvertibleTo(targetType), "");
|
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)
|
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, "");
|
solAssert(_to.category() == Type::Category::Array, "");
|
||||||
return arrayConversionFunction(
|
return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_to));
|
||||||
dynamic_cast<ArrayType const&>(_from),
|
|
||||||
dynamic_cast<ArrayType const&>(_to)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
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)
|
string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, StructType const& _to)
|
||||||
{
|
{
|
||||||
solAssert(_to.dataStoredIn(DataLocation::Storage), "");
|
solAssert(_to.dataStoredIn(DataLocation::Storage), "");
|
||||||
|
@ -411,6 +411,10 @@ public:
|
|||||||
/// This is used for data being encoded or general type conversions in the code.
|
/// This is used for data being encoded or general type conversions in the code.
|
||||||
std::string conversionFunction(Type const& _from, Type const& _to);
|
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
|
/// @returns the name of the cleanup function for the given type and
|
||||||
/// adds its implementation to the requested functions.
|
/// adds its implementation to the requested functions.
|
||||||
/// The cleanup function defers to the validator function with "assert"
|
/// 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