diff --git a/Changelog.md b/Changelog.md index 10d6b3fc3..1faf29393 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Language Features: Compiler Features: * Commandline Interface: Don't ignore `--yul-optimizations` in assembly mode. + * Allow using abi encoding functions for calldata array slices without explicit casts. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c9d40a1ce..b19ab4c4b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -330,7 +330,7 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c // Structs are fine in the following circumstances: // - ABIv2 or, // - storage struct for a library - if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage)) + if (_inLibraryCall && encodingType && encodingType->dataStoredIn(DataLocation::Storage)) return encodingType; TypePointer baseType = encodingType; while (auto const* arrayType = dynamic_cast(baseType)) @@ -1971,6 +1971,19 @@ string ArraySliceType::toString(bool _short) const return m_arrayType.toString(_short) + " slice"; } +TypePointer ArraySliceType::mobileType() const +{ + if ( + m_arrayType.dataStoredIn(DataLocation::CallData) && + m_arrayType.isDynamicallySized() && + !m_arrayType.baseType()->isDynamicallyEncoded() + ) + return &m_arrayType; + else + return this; +} + + std::vector> ArraySliceType::makeStackItems() const { return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}}; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d549d4f24..11e948f26 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -831,6 +831,7 @@ public: bool isDynamicallyEncoded() const override { return true; } bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } std::string toString(bool _short) const override; + TypePointer mobileType() const override; BoolResult validForLocation(DataLocation _loc) const override { return m_arrayType.validForLocation(_loc); } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 5018346b4..189fba165 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -279,31 +279,47 @@ string ABIFunctions::abiEncodingFunction( return abiEncodingFunctionStringLiteral(_from, to, _options); else if (auto toArray = dynamic_cast(&to)) { - solAssert(_from.category() == Type::Category::Array, ""); - solAssert(to.dataStoredIn(DataLocation::Memory), ""); - ArrayType const& fromArray = dynamic_cast(_from); + ArrayType const* fromArray = nullptr; + switch (_from.category()) + { + case Type::Category::Array: + fromArray = dynamic_cast(&_from); + break; + case Type::Category::ArraySlice: + fromArray = &dynamic_cast(&_from)->arrayType(); + solAssert( + fromArray->dataStoredIn(DataLocation::CallData) && + fromArray->isDynamicallySized() && + !fromArray->baseType()->isDynamicallyEncoded(), + "" + ); + break; + default: + solAssert(false, ""); + break; + } - switch (fromArray.location()) + switch (fromArray->location()) { case DataLocation::CallData: if ( - fromArray.isByteArray() || - *fromArray.baseType() == *TypeProvider::uint256() || - *fromArray.baseType() == FixedBytesType(32) + fromArray->isByteArray() || + *fromArray->baseType() == *TypeProvider::uint256() || + *fromArray->baseType() == FixedBytesType(32) ) - return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options); + return abiEncodingFunctionCalldataArrayWithoutCleanup(*fromArray, *toArray, _options); else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); case DataLocation::Memory: - if (fromArray.isByteArray()) - return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); + if (fromArray->isByteArray()) + return abiEncodingFunctionMemoryByteArray(*fromArray, *toArray, _options); else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); case DataLocation::Storage: - if (fromArray.baseType()->storageBytes() <= 16) - return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); + if (fromArray->baseType()->storageBytes() <= 16) + return abiEncodingFunctionCompactStorageArray(*fromArray, *toArray, _options); else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options); default: solAssert(false, ""); } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index feae52caa..8bf0a21e2 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -480,6 +480,16 @@ void CompilerUtils::encodeToMemory( convertType(*_givenTypes[i], *targetType, true); if (auto arrayType = dynamic_cast(type)) ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); + else if (auto arraySliceType = dynamic_cast(type)) + { + solAssert( + arraySliceType->dataStoredIn(DataLocation::CallData) && + arraySliceType->isDynamicallySized() && + !arraySliceType->arrayType().baseType()->isDynamicallyEncoded(), + "" + ); + ArrayUtils(m_context).copyArrayToMemory(arraySliceType->arrayType(), _padToWordBoundaries); + } else storeInMemoryDynamic(*type, _padToWordBoundaries); } @@ -516,22 +526,39 @@ void CompilerUtils::encodeToMemory( } else { - solAssert(_givenTypes[i]->category() == Type::Category::Array, "Unknown dynamic type."); - auto const& arrayType = dynamic_cast(*_givenTypes[i]); + ArrayType const* arrayType = nullptr; + switch (_givenTypes[i]->category()) + { + case Type::Category::Array: + arrayType = dynamic_cast(_givenTypes[i]); + break; + case Type::Category::ArraySlice: + arrayType = &dynamic_cast(_givenTypes[i])->arrayType(); + solAssert( + arrayType->isDynamicallySized() && + arrayType->dataStoredIn(DataLocation::CallData) && + !arrayType->baseType()->isDynamicallyEncoded(), + "" + ); + break; + default: + solAssert(false, "Unknown dynamic type."); + break; + } // now copy the array - copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.sizeOnStack()); + copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType->sizeOnStack()); // stack: ... // copy length to memory - m_context << dupInstruction(1 + arrayType.sizeOnStack()); - ArrayUtils(m_context).retrieveLength(arrayType, 1); + m_context << dupInstruction(1 + arrayType->sizeOnStack()); + ArrayUtils(m_context).retrieveLength(*arrayType, 1); // stack: ... storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... // copy the new memory pointer - m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP; + m_context << swapInstruction(arrayType->sizeOnStack() + 1) << Instruction::POP; // stack: ... // copy data part - ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries); + ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); // stack: ... } diff --git a/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol b/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol new file mode 100644 index 000000000..917704e53 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV1/abi_encode_calldata_slice.sol @@ -0,0 +1,62 @@ +contract C { + function enc_packed_bytes(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(data[start:end]); + } + function enc_packed_bytes_reference(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(bytes(data[start:end])); + } + + function enc_bytes(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(data[start:end]); + } + function enc_bytes_reference(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(bytes(data[start:end])); + } + + function enc_uint256(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(x[start:end]); + } + function enc_uint256_reference(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(x[start:end]); + } + + function enc_packed_uint256(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(x[start:end]); + } + function enc_packed_uint256_reference(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(x[start:end]); + } + + function compare(bytes memory x, bytes memory y) internal { + assert(x.length == y.length); + for (uint i = 0; i < x.length; ++i) + assert(x[i] == y[i]); + } + + function test_bytes() public { + bytes memory test = new bytes(3); + test[0] = 0x41; test[1] = 0x42; test[2] = 0x42; + for (uint i = 0; i < test.length; i++) + for (uint j = i; j <= test.length; j++) + { + compare(this.enc_packed_bytes(test, i, j), this.enc_packed_bytes_reference(test, i, j)); + compare(this.enc_bytes(test, i, j), this.enc_bytes_reference(test, i, j)); + } + } + + function test_uint256() public { + uint256[] memory test = new uint256[](3); + test[0] = 0x41; test[1] = 0x42; test[2] = 0x42; + for (uint i = 0; i < test.length; i++) + for (uint j = i; j <= test.length; j++) + { + compare(this.enc_packed_uint256(test, i, j), this.enc_packed_uint256_reference(test, i, j)); + compare(this.enc_uint256(test, i, j), this.enc_uint256_reference(test, i, j)); + } + } +} +// ==== +// EVMVersion: >homestead +// ---- +// test_bytes() -> +// test_uint256() -> diff --git a/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol new file mode 100644 index 000000000..1f1444672 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/abi_encode_calldata_slice.sol @@ -0,0 +1,63 @@ +pragma experimental ABIEncoderV2; +contract C { + function enc_packed_bytes(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(data[start:end]); + } + function enc_packed_bytes_reference(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(bytes(data[start:end])); + } + + function enc_bytes(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(data[start:end]); + } + function enc_bytes_reference(bytes calldata data, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(bytes(data[start:end])); + } + + function enc_uint256(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(x[start:end]); + } + function enc_uint256_reference(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encode(x[start:end]); + } + + function enc_packed_uint256(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(x[start:end]); + } + function enc_packed_uint256_reference(uint256[] calldata x, uint256 start, uint256 end) external returns (bytes memory) { + return abi.encodePacked(x[start:end]); + } + + function compare(bytes memory x, bytes memory y) internal { + assert(x.length == y.length); + for (uint i = 0; i < x.length; ++i) + assert(x[i] == y[i]); + } + + function test_bytes() public { + bytes memory test = new bytes(3); + test[0] = 0x41; test[1] = 0x42; test[2] = 0x42; + for (uint i = 0; i < test.length; i++) + for (uint j = i; j <= test.length; j++) + { + compare(this.enc_packed_bytes(test, i, j), this.enc_packed_bytes_reference(test, i, j)); + compare(this.enc_bytes(test, i, j), this.enc_bytes_reference(test, i, j)); + } + } + + function test_uint256() public { + uint256[] memory test = new uint256[](3); + test[0] = 0x41; test[1] = 0x42; test[2] = 0x42; + for (uint i = 0; i < test.length; i++) + for (uint j = i; j <= test.length; j++) + { + compare(this.enc_packed_uint256(test, i, j), this.enc_packed_uint256_reference(test, i, j)); + compare(this.enc_uint256(test, i, j), this.enc_uint256_reference(test, i, j)); + } + } +} +// ==== +// EVMVersion: >homestead +// ---- +// test_bytes() -> +// test_uint256() -> diff --git a/test/libsolidity/syntaxTests/array/slice/assign_to_storage.sol b/test/libsolidity/syntaxTests/array/slice/assign_to_storage.sol new file mode 100644 index 000000000..7b443949a --- /dev/null +++ b/test/libsolidity/syntaxTests/array/slice/assign_to_storage.sol @@ -0,0 +1,8 @@ +contract c { + bytes public b; + function f() public { + b = msg.data[:]; + } +} +// ---- +// TypeError: (63-74): Type bytes calldata slice is not implicitly convertible to expected type bytes storage ref. diff --git a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol index a31e5ab85..de90b0f75 100644 --- a/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol +++ b/test/libsolidity/syntaxTests/array/slice/calldata_dynamic_encode.sol @@ -4,4 +4,3 @@ contract C { } } // ---- -// TypeError: (85-91): This type cannot be encoded.