Merge pull request from ethereum/abiEncodeCalldataSlice

Allow abi encoding array slices without casting.
This commit is contained in:
chriseth 2020-05-13 18:55:16 +02:00 committed by GitHub
commit 1700bdc4ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 214 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,4 +4,3 @@ contract C {
}
}
// ----
// TypeError: (85-91): This type cannot be encoded.