mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #5839 from ethereum/inplaceEncoding
Inplace and non-padded encoding for ABIEncoderV2.
This commit is contained in:
commit
49cd55d3a0
@ -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).
|
||||
|
@ -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(),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -6,4 +6,4 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError: (116-117): This type cannot be encoded.
|
||||
// TypeError: (116-117): Type not supported in packed mode.
|
||||
|
@ -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.
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user