Merge pull request #5839 from ethereum/inplaceEncoding

Inplace and non-padded encoding for ABIEncoderV2.
This commit is contained in:
chriseth 2019-02-11 12:01:28 +01:00 committed by GitHub
commit 49cd55d3a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 666 additions and 100 deletions

View File

@ -12,6 +12,7 @@ Language Features:
Compiler Features:
* Implement packed encoding for ABIEncoderV2.
* C API (``libsolc`` / raw ``soljson.js``): Introduce ``solidity_free`` method which releases all internal buffers to save memory.
* Commandline interface: Adds new option ``--new-reporter`` for improved diagnostics formatting
along with ``--color`` and ``--no-color`` for colorized output to be forced (or explicitly disabled).

View File

@ -48,9 +48,9 @@ using namespace dev::solidity;
namespace
{
bool typeSupportedByOldABIEncoder(Type const& _type)
bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall)
{
if (_type.dataStoredIn(DataLocation::Storage))
if (_isLibraryCall && _type.dataStoredIn(DataLocation::Storage))
return true;
if (_type.category() == Type::Category::Struct)
return false;
@ -58,7 +58,7 @@ bool typeSupportedByOldABIEncoder(Type const& _type)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(_type);
auto base = arrayType.baseType();
if (!typeSupportedByOldABIEncoder(*base) || (base->category() == Type::Category::Array && base->isDynamicallySized()))
if (!typeSupportedByOldABIEncoder(*base, _isLibraryCall) || (base->category() == Type::Category::Array && base->isDynamicallySized()))
return false;
}
return true;
@ -355,7 +355,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (
_function.isPublic() &&
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
!typeSupportedByOldABIEncoder(*type(var))
!typeSupportedByOldABIEncoder(*type(var), isLibraryFunction)
)
m_errorReporter.typeError(
var.location(),
@ -475,7 +475,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
{
vector<string> unsupportedTypes;
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
if (!typeSupportedByOldABIEncoder(*param))
if (!typeSupportedByOldABIEncoder(*param, false /* isLibrary */))
unsupportedTypes.emplace_back(param->toString());
if (!unsupportedTypes.empty())
m_errorReporter.typeError(_variable.location(),
@ -578,24 +578,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
{
if (var->isIndexed())
{
numIndexed++;
if (
_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
dynamic_cast<ReferenceType const*>(type(*var).get())
)
m_errorReporter.typeError(
var->location(),
"Indexed reference types cannot yet be used with ABIEncoderV2."
);
}
if (!type(*var)->canLiveOutsideStorage())
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
!typeSupportedByOldABIEncoder(*type(*var))
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
)
m_errorReporter.typeError(
var->location(),
@ -1560,6 +1550,15 @@ void TypeChecker::typeCheckABIEncodeFunctions(
}
}
if (isPacked && !typeSupportedByOldABIEncoder(*argType, false /* isLibrary */))
{
m_errorReporter.typeError(
arguments[i]->location(),
"Type not supported in packed mode."
);
continue;
}
if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments()))
m_errorReporter.typeError(
arguments[i]->location(),

View File

@ -415,7 +415,7 @@ MemberList const& Type::members(ContractDefinition const* _currentScope) const
return *m_members[_currentScope];
}
TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _packed) const
TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) const
{
TypePointer encodingType = mobileType();
if (encodingType)
@ -423,7 +423,7 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p
if (encodingType)
encodingType = encodingType->encodingType();
// Structs are fine in the following circumstances:
// - ABIv2 without packed encoding or,
// - ABIv2 or,
// - storage struct for a library
if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage))
return encodingType;
@ -431,7 +431,7 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
baseType = arrayType->baseType();
if (dynamic_cast<StructType const*>(baseType.get()))
if (!_encoderV2 || _packed)
if (!_encoderV2)
return TypePointer();
return encodingType;
}

View File

@ -107,6 +107,74 @@ string ABIFunctions::tupleEncoder(
});
}
string ABIFunctions::tupleEncoderPacked(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes
)
{
EncodingOptions options;
options.encodeAsLibraryTypes = false;
options.encodeFunctionFromStack = true;
options.padded = false;
options.dynamicInplace = true;
string functionName = string("abi_encode_tuple_packed_");
for (auto const& t: _givenTypes)
functionName += t->identifier() + "_";
functionName += "_to_";
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
return createExternallyUsedFunction(functionName, [&]() {
solAssert(!_givenTypes.empty(), "");
// Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"(
function <functionName>(pos <valueParams>) -> end {
<encodeElements>
end := pos
}
)");
templ("functionName", functionName);
string valueParams;
string encodeElements;
size_t stackPos = 0;
for (size_t i = 0; i < _givenTypes.size(); ++i)
{
solAssert(_givenTypes[i], "");
solAssert(_targetTypes[i], "");
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
string valueNames = "";
for (size_t j = 0; j < sizeOnStack; j++)
{
valueNames += "value" + to_string(stackPos) + ", ";
valueParams = ", value" + to_string(stackPos) + valueParams;
stackPos++;
}
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
string(R"(
pos := <abiEncode>(<values> pos)
)") :
string(R"(
<abiEncode>(<values> pos)
pos := add(pos, <calldataEncodedSize>)
)")
);
elementTempl("values", valueNames);
if (!dynamic)
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
encodeElements += elementTempl.render();
}
templ("valueParams", valueParams);
templ("encodeElements", encodeElements);
return templ.render();
});
}
string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
{
string functionName = string("abi_decode_tuple_");
@ -574,20 +642,66 @@ string ABIFunctions::abiEncodingFunction(
// special case: convert storage reference type to value type - this is only
// possible for library calls where we just forward the storage reference
solAssert(_options.encodeAsLibraryTypes, "");
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
solAssert(to == IntegerType::uint256(), "");
templ("cleanupConvert", "value");
}
else
{
string cleanupConvert;
if (_from == to)
templ("cleanupConvert", cleanupFunction(_from) + "(value)");
cleanupConvert = cleanupFunction(_from) + "(value)";
else
templ("cleanupConvert", conversionFunction(_from, to) + "(value)");
cleanupConvert = conversionFunction(_from, to) + "(value)";
if (!_options.padded)
cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")";
templ("cleanupConvert", cleanupConvert);
}
return templ.render();
});
}
string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
Type const& _givenType,
Type const& _targetType,
ABIFunctions::EncodingOptions const& _options
)
{
string functionName =
"abi_encodeUpdatedPos_" +
_givenType.identifier() +
"_to_" +
_targetType.identifier() +
_options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
if (_targetType.isDynamicallyEncoded())
return Whiskers(R"(
function <functionName>(value, pos) -> updatedPos {
updatedPos := <encode>(value, pos)
}
)")
("functionName", functionName)
("encode", encoder)
.render();
else
{
unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded);
solAssert(encodedSize != 0, "Invalid encoded size.");
return Whiskers(R"(
function <functionName>(value, pos) -> updatedPos {
<encode>(value, pos)
updatedPos := add(pos, <encodedSize>)
}
)")
("functionName", functionName)
("encode", encoder)
("encodedSize", toCompactHexWithPrefix(encodedSize))
.render();
}
});
}
string ABIFunctions::abiEncodingFunctionCalldataArray(
Type const& _from,
Type const& _to,
@ -623,15 +737,15 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
function <functionName>(start, length, pos) -> end {
pos := <storeLength>(pos, length)
<copyFun>(start, pos, length)
end := add(pos, <roundUpFun>(length))
end := add(pos, <lengthPadded>)
}
)");
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType));
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", copyToMemoryFunction(true));
templ("roundUpFun", roundUpFunction());
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
return templ.render();
});
}
@ -659,8 +773,9 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
bool dynamic = _to.isDynamicallyEncoded();
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
bool const usesTail = dynamicBase && !_options.dynamicInplace;
Whiskers templ(
dynamicBase ?
usesTail ?
R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> {
@ -688,9 +803,8 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
let srcPtr := <dataAreaFun>(value)
for { let i := 0 } lt(i, length) { i := add(i, 1) }
{
<encodeToMemoryFun>(<arrayElementAccess>, pos)
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
srcPtr := <nextArrayElement>(srcPtr)
pos := add(pos, <elementEncodedSize>)
}
<assignEnd>
}
@ -702,17 +816,13 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataAreaFun", arrayDataAreaFunction(_from));
templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()));
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
templ("encodeToMemoryFun", abiEncodingFunction(
*_from.baseType(),
*_to.baseType(),
subOptions
));
subOptions.padded = true;
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
templ("nextArrayElement", nextArrayElementFunction(_from));
return templ.render();
@ -742,15 +852,16 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray(
Whiskers templ(R"(
function <functionName>(value, pos) -> end {
let length := <lengthFun>(value)
mstore(pos, length)
<copyFun>(add(value, 0x20), add(pos, 0x20), length)
end := add(add(pos, 0x20), <roundUpFun>(length))
pos := <storeLength>(pos, length)
<copyFun>(add(value, 0x20), pos, length)
end := add(pos, <lengthPadded>)
}
)");
templ("functionName", functionName);
templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("copyFun", copyToMemoryFunction(false));
templ("roundUpFun", roundUpFunction());
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
return templ.render();
});
}
@ -784,28 +895,30 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
case 0 {
// short byte array
let length := and(div(slotValue, 2), 0x7f)
mstore(pos, length)
mstore(add(pos, 0x20), and(slotValue, not(0xff)))
ret := add(pos, 0x40)
pos := <storeLength>(pos, length)
mstore(pos, and(slotValue, not(0xff)))
ret := add(pos, <lengthPaddedShort>)
}
case 1 {
// long byte array
let length := div(slotValue, 2)
mstore(pos, length)
pos := add(pos, 0x20)
pos := <storeLength>(pos, length)
let dataPos := <arrayDataSlot>(value)
let i := 0
for { } lt(i, length) { i := add(i, 0x20) } {
mstore(add(pos, i), sload(dataPos))
dataPos := add(dataPos, 1)
}
ret := add(pos, i)
ret := add(pos, <lengthPaddedLong>)
}
}
)");
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
templ("lengthPaddedLong", _options.padded ? "i" : "length");
templ("arrayDataSlot", arrayDataAreaFunction(_from));
return templ.render();
}
@ -819,7 +932,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
size_t storageBytes = _from.baseType()->storageBytes();
size_t itemsPerSlot = 32 / storageBytes;
// This always writes full slot contents to memory, which might be
// more than desired, i.e. it writes beyond the end of memory.
// more than desired, i.e. it always writes beyond the end of memory.
Whiskers templ(
R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
@ -848,14 +961,16 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataArea", arrayDataAreaFunction(_from));
templ("itemsPerSlot", to_string(itemsPerSlot));
// We use padded size because array elements are always padded.
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
templ("elementEncodedSize", elementEncodedSize);
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string encodeToMemoryFun = abiEncodingFunction(
*_from.baseType(),
*_to.baseType(),
@ -910,7 +1025,12 @@ string ABIFunctions::abiEncodingFunctionStruct(
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("return", dynamic ? " -> end " : "");
templ("assignEnd", dynamic ? "end := tail" : "");
if (dynamic && _options.dynamicInplace)
templ("assignEnd", "end := pos");
else if (dynamic && !_options.dynamicInplace)
templ("assignEnd", "end := tail");
else
templ("assignEnd", "");
// to avoid multiple loads from the same slot for subsequent members
templ("init", fromStorage ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1);
@ -960,27 +1080,38 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
}
Whiskers encodeTempl(
dynamicMember ?
string(R"(
mstore(add(pos, <encodingOffset>), sub(tail, pos))
tail := <abiEncode>(memberValue, tail)
)") :
string(R"(
<abiEncode>(memberValue, add(pos, <encodingOffset>))
)")
);
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
// Like with arrays, struct members are always padded.
subOptions.padded = true;
string encode;
if (_options.dynamicInplace)
encode = Whiskers{"pos := <encode>(memberValue, pos)"}
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
.render();
else
{
Whiskers encodeTempl(
dynamicMember ?
string(R"(
mstore(add(pos, <encodingOffset>), sub(tail, pos))
tail := <abiEncode>(memberValue, tail)
)") :
"<abiEncode>(memberValue, add(pos, <encodingOffset>))"
);
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
encode = encodeTempl.render();
}
members.back()["encode"] = encode;
members.back()["encode"] = encodeTempl.render();
members.back()["memberName"] = member.name;
}
templ("members", members);
if (_options.dynamicInplace)
solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size.");
templ("headSize", toCompactHexWithPrefix(encodingOffset));
return templ.render();
});
@ -1007,9 +1138,10 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
if (_to.isDynamicallySized())
{
solAssert(_to.category() == Type::Category::Array, "");
Whiskers templ(R"(
function <functionName>(pos) -> end {
mstore(pos, <length>)
pos := <storeLength>(pos, <length>)
<#word>
mstore(add(pos, <offset>), <wordValue>)
</word>
@ -1020,12 +1152,17 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
// TODO this can make use of CODECOPY for large strings once we have that in Yul
size_t words = (value.size() + 31) / 32;
templ("overallSize", to_string(32 + words * 32));
templ("length", to_string(value.size()));
templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast<ArrayType const&>(_to), _options));
if (_options.padded)
templ("overallSize", to_string(words * 32));
else
templ("overallSize", to_string(value.size()));
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(32 + i * 32);
wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex();
}
templ("word", wordParams);
@ -1427,6 +1564,66 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
});
}
string ABIFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();
return createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> aligned {
<body>
}
)");
templ("functionName", functionName);
switch (_type.category())
{
case Type::Category::Address:
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
break;
case Type::Category::Integer:
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
if (type.numBits() == 256)
templ("body", "aligned := value");
else
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
break;
}
case Type::Category::RationalNumber:
solAssert(false, "Left align requested for rational number.");
break;
case Type::Category::Bool:
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
break;
case Type::Category::FixedPoint:
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
case Type::Category::Struct:
solAssert(false, "Left align requested for non-value type.");
break;
case Type::Category::FixedBytes:
templ("body", "aligned := value");
break;
case Type::Category::Contract:
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
break;
case Type::Category::Enum:
{
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
break;
}
case Type::Category::InaccessibleDynamic:
solAssert(false, "Left align requested for inaccessible dynamic type.");
break;
default:
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
}
return templ.render();
});
}
string ABIFunctions::shiftLeftFunction(size_t _numBits)
{
solAssert(_numBits < 256, "");
@ -1680,11 +1877,11 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type)
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
{
string functionName = "array_storeLengthForEncoding_" + _type.identifier();
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
return createFunction(functionName, [&]() {
if (_type.isDynamicallySized())
if (_type.isDynamicallySized() && !_options.dynamicInplace)
return Whiskers(R"(
function <functionName>(pos, length) -> updated_pos {
mstore(pos, length)

View File

@ -60,16 +60,26 @@ public:
/// The values represent stack slots. If a type occupies more or less than one
/// stack slot, it takes exactly that number of values.
/// Returns a pointer to the end of the area written in memory.
/// Does not allocate memory (does not change the memory head pointer), but writes
/// Does not allocate memory (does not change the free memory pointer), but writes
/// to memory starting at $headStart and an unrestricted amount after that.
/// Assigns the end of encoded memory either to $value0 or (if that is not present)
/// to $headStart.
std::string tupleEncoder(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes = false
);
/// @returns name of an assembly function to encode values of @a _givenTypes
/// with packed encoding into memory, converting the types to @a _targetTypes on the fly.
/// Parameters are: <memPos> <value_n> ... <value_1>, i.e.
/// the layout on the stack is <value_1> ... <value_n> <memPos> with
/// the top of the stack on the right.
/// The values represent stack slots. If a type occupies more or less than one
/// stack slot, it takes exactly that number of values.
/// Returns a pointer to the end of the area written in memory.
/// Does not allocate memory (does not change the free memory pointer), but writes
/// to memory starting at memPos and an unrestricted amount after that.
std::string tupleEncoderPacked(TypePointers const& _givenTypes, TypePointers const& _targetTypes);
/// @returns name of an assembly function to ABI-decode values of @a _types
/// into memory. If @a _fromMemory is true, decodes from memory instead of
/// from calldata.
@ -89,7 +99,9 @@ public:
private:
struct EncodingOptions
{
/// Pad/signextend value types and bytes/string to multiples of 32 bytes.
/// Pad/signextend value types and bytes/string to multiples of 32 bytes.
/// If false, data is always left-aligned.
/// Note that this is always re-set to true for the elements of arrays and structs.
bool padded = true;
/// Store arrays and structs in place without "data pointer" and do not store the length.
bool dynamicInplace = false;
@ -134,6 +146,14 @@ private:
Type const& _targetType,
EncodingOptions const& _options
);
/// @returns the name of a function that internally calls `abiEncodingFunction`
/// but always returns the updated encoding position, even if the type is
/// statically encoded.
std::string abiEncodeAndReturnUpdatedPosFunction(
Type const& _givenType,
Type const& _targetType,
EncodingOptions const& _options
);
/// Part of @a abiEncodingFunction for array target type and given calldata array.
std::string abiEncodingFunctionCalldataArray(
Type const& _givenType,
@ -212,6 +232,10 @@ private:
/// Pads with zeros and might write more than exactly length.
std::string copyToMemoryFunction(bool _fromCalldata);
/// @returns the name of a function that takes a (cleaned) value of the given value type and
/// left-aligns it, usually for use in non-padded encoding.
std::string leftAlignFunction(Type const& _type);
std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(size_t _numBits);
/// @returns the name of a function that rounds its input to the next multiple
@ -232,9 +256,11 @@ private:
std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function used during encoding that stores the length
/// if the array is dynamically sized. It returns the new encoding position.
/// If the array is not dynamically sized, does nothing and just returns the position again.
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type);
/// if the array is dynamically sized (and the options do not request in-place encoding).
/// It returns the new encoding position.
/// If the array is not dynamically sized (or in-place encoding was requested),
/// does nothing and just returns the position again.
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options);
/// @returns the name of a function that allocates memory.
/// Modifies the "free memory pointer"

View File

@ -348,11 +348,15 @@ void CompilerUtils::encodeToMemory(
if (_givenTypes.empty())
return;
else if (_padToWordBoundaries && !_copyDynamicDataInPlace && encoderV2)
if (encoderV2)
{
// Use the new Yul-based encoding function
solAssert(
_padToWordBoundaries != _copyDynamicDataInPlace,
"Non-padded and in-place encoding can only be combined."
);
auto stackHeightBefore = m_context.stackHeight();
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes);
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries);
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
return;
}
@ -466,15 +470,22 @@ void CompilerUtils::encodeToMemory(
void CompilerUtils::abiEncodeV2(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes
bool _encodeAsLibraryTypes,
bool _padToWordBoundaries
)
{
if (!_padToWordBoundaries)
solAssert(!_encodeAsLibraryTypes, "Library calls cannot be packed.");
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
auto ret = m_context.pushNewTag();
moveIntoStack(sizeOnStack(_givenTypes) + 1);
string encoderName = m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes);
string encoderName =
_padToWordBoundaries ?
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
m_context.appendJumpTo(m_context.namedTag(encoderName));
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
m_context << ret.tag();

View File

@ -159,7 +159,8 @@ public:
void abiEncodeV2(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _encodeAsLibraryTypes = false
bool _encodeAsLibraryTypes = false,
bool _padToWordBoundaries = true
);
/// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,

View File

@ -731,6 +731,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
arguments.front()->accept(*this);
utils().fetchFreeMemoryPointer();
solAssert(function.parameterTypes().front()->isValueType(), "");
utils().packedEncode(
{arguments.front()->annotation().type},
{function.parameterTypes().front()}
@ -744,28 +745,32 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
_functionCall.expression().accept(*this);
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
unsigned numIndexed = 0;
TypePointers paramTypes = function.parameterTypes();
// All indexed arguments go to the stack
for (unsigned arg = arguments.size(); arg > 0; --arg)
if (event.parameters()[arg - 1]->isIndexed())
{
++numIndexed;
arguments[arg - 1]->accept(*this);
if (auto const& arrayType = dynamic_pointer_cast<ArrayType const>(function.parameterTypes()[arg - 1]))
if (auto const& referenceType = dynamic_pointer_cast<ReferenceType const>(paramTypes[arg - 1]))
{
utils().fetchFreeMemoryPointer();
utils().packedEncode(
{arguments[arg - 1]->annotation().type},
{arrayType}
{referenceType}
);
utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::KECCAK256;
}
else
{
solAssert(paramTypes[arg - 1]->isValueType(), "");
utils().convertType(
*arguments[arg - 1]->annotation().type,
*function.parameterTypes()[arg - 1],
*paramTypes[arg - 1],
true
);
}
}
if (!event.isAnonymous())
{
@ -782,7 +787,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
arguments[arg]->accept(*this);
nonIndexedArgTypes.push_back(arguments[arg]->annotation().type);
nonIndexedParamTypes.push_back(function.parameterTypes()[arg]);
nonIndexedParamTypes.push_back(paramTypes[arg]);
}
utils().fetchFreeMemoryPointer();
utils().abiEncode(nonIndexedArgTypes, nonIndexedParamTypes);

View File

@ -30,6 +30,8 @@
#include <libevmasm/Assembly.h>
#include <libdevcore/Keccak256.h>
#include <boost/test/unit_test.hpp>
#include <functional>
@ -13478,13 +13480,322 @@ BOOST_AUTO_TEST_CASE(abi_encodePacked)
y[0] = "e";
require(y[0] == "e");
}
function f4() public pure returns (bytes memory) {
string memory x = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
return abi.encodePacked(uint16(0x0701), x, uint16(0x1201));
}
function f_literal() public pure returns (bytes memory) {
return abi.encodePacked(uint8(0x01), "abc", uint8(0x02));
}
function f_calldata() public pure returns (bytes memory) {
return abi.encodePacked(uint8(0x01), msg.data, uint8(0x02));
}
}
)";
for (auto v2: {false, true})
{
compileAndRun(string(v2 ? "pragma experimental ABIEncoderV2;\n" : "") + sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f0()"), encodeArgs(0x20, 0));
ABI_CHECK(callContractFunction("f1()"), encodeArgs(0x20, 2, "\x01\x02"));
ABI_CHECK(callContractFunction("f2()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
ABI_CHECK(callContractFunction("f3()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
ABI_CHECK(callContractFunction("f4()"), encodeArgs(
0x20,
2 + 26 + 26 + 2,
"\x07\x01" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "\x12\x01"
));
ABI_CHECK(callContractFunction("f_literal()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
ABI_CHECK(callContractFunction("f_calldata()"), encodeArgs(0x20, 6, "\x01" "\xa5\xbf\xa1\xee" "\x02"));
}
}
BOOST_AUTO_TEST_CASE(abi_encodePacked_from_storage)
{
char const* sourceCode = R"(
contract C {
uint24[9] small_fixed;
int24[9] small_fixed_signed;
uint24[] small_dyn;
uint248[5] large_fixed;
uint248[] large_dyn;
bytes bytes_storage;
function sf() public returns (bytes memory) {
small_fixed[0] = 0xfffff1;
small_fixed[2] = 0xfffff2;
small_fixed[5] = 0xfffff3;
small_fixed[8] = 0xfffff4;
return abi.encodePacked(uint8(0x01), small_fixed, uint8(0x02));
}
function sd() public returns (bytes memory) {
small_dyn.length = 9;
small_dyn[0] = 0xfffff1;
small_dyn[2] = 0xfffff2;
small_dyn[5] = 0xfffff3;
small_dyn[8] = 0xfffff4;
return abi.encodePacked(uint8(0x01), small_dyn, uint8(0x02));
}
function sfs() public returns (bytes memory) {
small_fixed_signed[0] = -2;
small_fixed_signed[2] = 0xffff2;
small_fixed_signed[5] = -200;
small_fixed_signed[8] = 0xffff4;
return abi.encodePacked(uint8(0x01), small_fixed_signed, uint8(0x02));
}
function lf() public returns (bytes memory) {
large_fixed[0] = 2**248-1;
large_fixed[1] = 0xfffff2;
large_fixed[2] = 2**248-2;
large_fixed[4] = 0xfffff4;
return abi.encodePacked(uint8(0x01), large_fixed, uint8(0x02));
}
function ld() public returns (bytes memory) {
large_dyn.length = 5;
large_dyn[0] = 2**248-1;
large_dyn[1] = 0xfffff2;
large_dyn[2] = 2**248-2;
large_dyn[4] = 0xfffff4;
return abi.encodePacked(uint8(0x01), large_dyn, uint8(0x02));
}
function bytes_short() public returns (bytes memory) {
bytes_storage = "abcd";
return abi.encodePacked(uint8(0x01), bytes_storage, uint8(0x02));
}
function bytes_long() public returns (bytes memory) {
bytes_storage = "0123456789012345678901234567890123456789";
return abi.encodePacked(uint8(0x01), bytes_storage, uint8(0x02));
}
}
)";
for (auto v2: {false, true})
{
compileAndRun(string(v2 ? "pragma experimental ABIEncoderV2;\n" : "") + sourceCode, 0, "C");
bytes payload = encodeArgs(0xfffff1, 0, 0xfffff2, 0, 0, 0xfffff3, 0, 0, 0xfffff4);
bytes encoded = encodeArgs(0x20, 0x122, "\x01" + asString(payload) + "\x02");
ABI_CHECK(callContractFunction("sf()"), encoded);
ABI_CHECK(callContractFunction("sd()"), encoded);
ABI_CHECK(callContractFunction("sfs()"), encodeArgs(0x20, 0x122, "\x01" + asString(encodeArgs(
u256(-2), 0, 0xffff2, 0, 0, u256(-200), 0, 0, 0xffff4
)) + "\x02"));
payload = encodeArgs(
u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
0xfffff2,
u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"),
0,
0xfffff4
);
ABI_CHECK(callContractFunction("lf()"), encodeArgs(0x20, 5 * 32 + 2, "\x01" + asString(encodeArgs(payload)) + "\x02"));
ABI_CHECK(callContractFunction("ld()"), encodeArgs(0x20, 5 * 32 + 2, "\x01" + asString(encodeArgs(payload)) + "\x02"));
ABI_CHECK(callContractFunction("bytes_short()"), encodeArgs(0x20, 6, "\x01" "abcd\x02"));
ABI_CHECK(
callContractFunction("bytes_long()"),
encodeArgs(0x20, 42, "\x01" "0123456789012345678901234567890123456789\x02")
);
}
}
BOOST_AUTO_TEST_CASE(abi_encodePacked_from_memory)
{
char const* sourceCode = R"(
contract C {
function sf() public pure returns (bytes memory) {
uint24[9] memory small_fixed;
small_fixed[0] = 0xfffff1;
small_fixed[2] = 0xfffff2;
small_fixed[5] = 0xfffff3;
small_fixed[8] = 0xfffff4;
return abi.encodePacked(uint8(0x01), small_fixed, uint8(0x02));
}
function sd() public pure returns (bytes memory) {
uint24[] memory small_dyn = new uint24[](9);
small_dyn[0] = 0xfffff1;
small_dyn[2] = 0xfffff2;
small_dyn[5] = 0xfffff3;
small_dyn[8] = 0xfffff4;
return abi.encodePacked(uint8(0x01), small_dyn, uint8(0x02));
}
function sfs() public pure returns (bytes memory) {
int24[9] memory small_fixed_signed;
small_fixed_signed[0] = -2;
small_fixed_signed[2] = 0xffff2;
small_fixed_signed[5] = -200;
small_fixed_signed[8] = 0xffff4;
return abi.encodePacked(uint8(0x01), small_fixed_signed, uint8(0x02));
}
function lf() public pure returns (bytes memory) {
uint248[5] memory large_fixed;
large_fixed[0] = 2**248-1;
large_fixed[1] = 0xfffff2;
large_fixed[2] = 2**248-2;
large_fixed[4] = 0xfffff4;
return abi.encodePacked(uint8(0x01), large_fixed, uint8(0x02));
}
function ld() public pure returns (bytes memory) {
uint248[] memory large_dyn = new uint248[](5);
large_dyn[0] = 2**248-1;
large_dyn[1] = 0xfffff2;
large_dyn[2] = 2**248-2;
large_dyn[4] = 0xfffff4;
return abi.encodePacked(uint8(0x01), large_dyn, uint8(0x02));
}
}
)";
for (auto v2: {false, true})
{
compileAndRun(string(v2 ? "pragma experimental ABIEncoderV2;\n" : "") + sourceCode, 0, "C");
bytes payload = encodeArgs(0xfffff1, 0, 0xfffff2, 0, 0, 0xfffff3, 0, 0, 0xfffff4);
bytes encoded = encodeArgs(0x20, 0x122, "\x01" + asString(payload) + "\x02");
ABI_CHECK(callContractFunction("sf()"), encoded);
ABI_CHECK(callContractFunction("sd()"), encoded);
ABI_CHECK(callContractFunction("sfs()"), encodeArgs(0x20, 0x122, "\x01" + asString(encodeArgs(
u256(-2), 0, 0xffff2, 0, 0, u256(-200), 0, 0, 0xffff4
)) + "\x02"));
payload = encodeArgs(
u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
0xfffff2,
u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"),
0,
0xfffff4
);
ABI_CHECK(callContractFunction("lf()"), encodeArgs(0x20, 5 * 32 + 2, "\x01" + asString(encodeArgs(payload)) + "\x02"));
ABI_CHECK(callContractFunction("ld()"), encodeArgs(0x20, 5 * 32 + 2, "\x01" + asString(encodeArgs(payload)) + "\x02"));
}
}
BOOST_AUTO_TEST_CASE(abi_encodePacked_functionPtr)
{
char const* sourceCode = R"(
contract C {
C other = C(0x1112131400000000000011121314000000000087);
function testDirect() public view returns (bytes memory) {
return abi.encodePacked(uint8(8), other.f, uint8(2));
}
function testFixedArray() public view returns (bytes memory) {
function () external pure returns (bytes memory)[1] memory x;
x[0] = other.f;
return abi.encodePacked(uint8(8), x, uint8(2));
}
function testDynamicArray() public view returns (bytes memory) {
function () external pure returns (bytes memory)[] memory x = new function() external pure returns (bytes memory)[](1);
x[0] = other.f;
return abi.encodePacked(uint8(8), x, uint8(2));
}
function f() public pure returns (bytes memory) {}
}
)";
for (auto v2: {false, true})
{
compileAndRun(string(v2 ? "pragma experimental ABIEncoderV2;\n" : "") + sourceCode, 0, "C");
string directEncoding = asString(fromHex("08" "1112131400000000000011121314000000000087" "26121ff0" "02"));
ABI_CHECK(callContractFunction("testDirect()"), encodeArgs(0x20, directEncoding.size(), directEncoding));
string arrayEncoding = asString(fromHex("08" "1112131400000000000011121314000000000087" "26121ff0" "0000000000000000" "02"));
ABI_CHECK(callContractFunction("testFixedArray()"), encodeArgs(0x20, arrayEncoding.size(), arrayEncoding));
ABI_CHECK(callContractFunction("testDynamicArray()"), encodeArgs(0x20, arrayEncoding.size(), arrayEncoding));
}
}
BOOST_AUTO_TEST_CASE(abi_encodePackedV2_structs)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint8 a;
int16 b;
uint8[2] c;
int16[] d;
}
S s;
event E(S indexed);
constructor() public {
s.a = 0x12;
s.b = -7;
s.c[0] = 2;
s.c[1] = 3;
s.d.length = 2;
s.d[0] = -7;
s.d[1] = -8;
}
function testStorage() public {
emit E(s);
}
function testMemory() public {
S memory m = s;
emit E(m);
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("f0()"), encodeArgs(0x20, 0));
ABI_CHECK(callContractFunction("f1()"), encodeArgs(0x20, 2, "\x01\x02"));
ABI_CHECK(callContractFunction("f2()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
ABI_CHECK(callContractFunction("f3()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
bytes structEnc = encodeArgs(int(0x12), u256(-7), int(2), int(3), u256(-7), u256(-8));
ABI_CHECK(callContractFunction("testStorage()"), encodeArgs());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E((uint8,int16,uint8[2],int16[]))")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], dev::keccak256(asString(structEnc)));
ABI_CHECK(callContractFunction("testMemory()"), encodeArgs());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E((uint8,int16,uint8[2],int16[]))")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], dev::keccak256(asString(structEnc)));
}
BOOST_AUTO_TEST_CASE(abi_encodePackedV2_nestedArray)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
struct S {
uint8 a;
int16 b;
}
event E(S[2][][3] indexed);
function testNestedArrays() public {
S[2][][3] memory x;
x[1] = new S[2][](2);
x[1][0][0].a = 1;
x[1][0][0].b = 2;
x[1][0][1].a = 3;
x[1][1][1].b = 4;
emit E(x);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bytes structEnc = encodeArgs(1, 2, 3, 0, 0, 0, 0, 4);
ABI_CHECK(callContractFunction("testNestedArrays()"), encodeArgs());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E((uint8,int16)[2][][3])")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], dev::keccak256(asString(structEnc)));
}
BOOST_AUTO_TEST_CASE(abi_encodePackedV2_arrayOfStrings)
{
char const* sourceCode = R"(
pragma experimental ABIEncoderV2;
contract C {
string[] x;
event E(string[] indexed);
constructor() public {
x.length = 2;
x[0] = "abc";
x[1] = "0123456789012345678901234567890123456789";
}
function testStorage() public {
emit E(x);
}
function testMemory() public {
string[] memory y = x;
emit E(y);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bytes arrayEncoding = encodeArgs("abc", "0123456789012345678901234567890123456789");
ABI_CHECK(callContractFunction("testStorage()"), encodeArgs());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(string[])")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], dev::keccak256(asString(arrayEncoding)));
ABI_CHECK(callContractFunction("testMemory()"), encodeArgs());
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(string[])")));
BOOST_CHECK_EQUAL(m_logs[0].topics[1], dev::keccak256(asString(arrayEncoding)));
}
BOOST_AUTO_TEST_CASE(abi_encode_with_selector)

View File

@ -4,4 +4,3 @@ contract c {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (59-73): Indexed reference types cannot yet be used with ABIEncoderV2.

View File

@ -4,4 +4,3 @@ contract c {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (59-75): Indexed reference types cannot yet be used with ABIEncoderV2.

View File

@ -5,4 +5,3 @@ contract c {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (85-94): Indexed reference types cannot yet be used with ABIEncoderV2.

View File

@ -12,5 +12,5 @@ contract C {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (191-192): This type cannot be encoded.
// TypeError: (194-195): This type cannot be encoded.
// TypeError: (191-192): Type not supported in packed mode.
// TypeError: (194-195): Type not supported in packed mode.

View File

@ -13,5 +13,5 @@ contract C {
// ----
// TypeError: (131-132): This type cannot be encoded.
// TypeError: (134-135): This type cannot be encoded.
// TypeError: (200-201): This type cannot be encoded.
// TypeError: (203-204): This type cannot be encoded.
// TypeError: (200-201): Type not supported in packed mode.
// TypeError: (203-204): Type not supported in packed mode.

View File

@ -5,6 +5,10 @@ contract C {
S s;
struct T { uint y; }
T t;
function e() public view {
S memory st;
abi.encodePacked(st);
}
function f() public view {
abi.encode(s, t);
}
@ -14,5 +18,6 @@ contract C {
}
// ----
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.
// TypeError: (235-236): This type cannot be encoded.
// TypeError: (238-239): This type cannot be encoded.
// TypeError: (193-195): Type not supported in packed mode.
// TypeError: (323-324): Type not supported in packed mode.
// TypeError: (326-327): Type not supported in packed mode.

View File

@ -6,4 +6,4 @@ contract C {
}
}
// ----
// TypeError: (116-117): This type cannot be encoded.
// TypeError: (116-117): Type not supported in packed mode.

View File

@ -0,0 +1,13 @@
contract C {
string[] s;
function f() public pure {
string[] memory m;
abi.encodePacked(m);
}
function g() public pure {
abi.encodePacked(s);
}
}
// ----
// TypeError: (112-113): Type not supported in packed mode.
// TypeError: (178-179): Type not supported in packed mode.

View File

@ -9,5 +9,5 @@ contract C {
}
}
// ----
// TypeError: (156-157): This type cannot be encoded.
// TypeError: (159-160): This type cannot be encoded.
// TypeError: (156-157): Type not supported in packed mode.
// TypeError: (159-160): Type not supported in packed mode.