mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8925 from ethereum/abiEncodeCalldataSlice
Allow abi encoding array slices without casting.
This commit is contained in:
commit
1700bdc4ad
@ -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.
|
||||
|
||||
|
||||
|
||||
|
@ -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<ArrayType const*>(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<std::tuple<std::string, TypePointer>> ArraySliceType::makeStackItems() const
|
||||
{
|
||||
return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}};
|
||||
|
@ -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); }
|
||||
|
||||
|
@ -279,31 +279,47 @@ string ABIFunctions::abiEncodingFunction(
|
||||
return abiEncodingFunctionStringLiteral(_from, to, _options);
|
||||
else if (auto toArray = dynamic_cast<ArrayType const*>(&to))
|
||||
{
|
||||
solAssert(_from.category() == Type::Category::Array, "");
|
||||
solAssert(to.dataStoredIn(DataLocation::Memory), "");
|
||||
ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from);
|
||||
ArrayType const* fromArray = nullptr;
|
||||
switch (_from.category())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
fromArray = dynamic_cast<ArrayType const*>(&_from);
|
||||
break;
|
||||
case Type::Category::ArraySlice:
|
||||
fromArray = &dynamic_cast<ArraySliceType const*>(&_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, "");
|
||||
}
|
||||
|
@ -480,6 +480,16 @@ void CompilerUtils::encodeToMemory(
|
||||
convertType(*_givenTypes[i], *targetType, true);
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
|
||||
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries);
|
||||
else if (auto arraySliceType = dynamic_cast<ArraySliceType const*>(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<ArrayType const&>(*_givenTypes[i]);
|
||||
ArrayType const* arrayType = nullptr;
|
||||
switch (_givenTypes[i]->category())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
arrayType = dynamic_cast<ArrayType const*>(_givenTypes[i]);
|
||||
break;
|
||||
case Type::Category::ArraySlice:
|
||||
arrayType = &dynamic_cast<ArraySliceType const*>(_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: ... <end_of_mem> <value...>
|
||||
// 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: ... <end_of_mem> <value...> <end_of_mem'> <length>
|
||||
storeInMemoryDynamic(*TypeProvider::uint256(), true);
|
||||
// stack: ... <end_of_mem> <value...> <end_of_mem''>
|
||||
// copy the new memory pointer
|
||||
m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP;
|
||||
m_context << swapInstruction(arrayType->sizeOnStack() + 1) << Instruction::POP;
|
||||
// stack: ... <end_of_mem''> <value...>
|
||||
// copy data part
|
||||
ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries);
|
||||
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries);
|
||||
// stack: ... <end_of_mem'''>
|
||||
}
|
||||
|
||||
|
@ -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() ->
|
@ -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() ->
|
@ -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.
|
@ -4,4 +4,3 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (85-91): This type cannot be encoded.
|
||||
|
Loading…
Reference in New Issue
Block a user