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:
|
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.
|
* 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
|
* 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).
|
along with ``--color`` and ``--no-color`` for colorized output to be forced (or explicitly disabled).
|
||||||
|
@ -48,9 +48,9 @@ using namespace dev::solidity;
|
|||||||
namespace
|
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;
|
return true;
|
||||||
if (_type.category() == Type::Category::Struct)
|
if (_type.category() == Type::Category::Struct)
|
||||||
return false;
|
return false;
|
||||||
@ -58,7 +58,7 @@ bool typeSupportedByOldABIEncoder(Type const& _type)
|
|||||||
{
|
{
|
||||||
auto const& arrayType = dynamic_cast<ArrayType const&>(_type);
|
auto const& arrayType = dynamic_cast<ArrayType const&>(_type);
|
||||||
auto base = arrayType.baseType();
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -355,7 +355,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
|||||||
if (
|
if (
|
||||||
_function.isPublic() &&
|
_function.isPublic() &&
|
||||||
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||||
!typeSupportedByOldABIEncoder(*type(var))
|
!typeSupportedByOldABIEncoder(*type(var), isLibraryFunction)
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
var.location(),
|
var.location(),
|
||||||
@ -475,7 +475,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
{
|
{
|
||||||
vector<string> unsupportedTypes;
|
vector<string> unsupportedTypes;
|
||||||
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
||||||
if (!typeSupportedByOldABIEncoder(*param))
|
if (!typeSupportedByOldABIEncoder(*param, false /* isLibrary */))
|
||||||
unsupportedTypes.emplace_back(param->toString());
|
unsupportedTypes.emplace_back(param->toString());
|
||||||
if (!unsupportedTypes.empty())
|
if (!unsupportedTypes.empty())
|
||||||
m_errorReporter.typeError(_variable.location(),
|
m_errorReporter.typeError(_variable.location(),
|
||||||
@ -578,24 +578,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
|||||||
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
|
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
|
||||||
{
|
{
|
||||||
if (var->isIndexed())
|
if (var->isIndexed())
|
||||||
{
|
|
||||||
numIndexed++;
|
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())
|
if (!type(*var)->canLiveOutsideStorage())
|
||||||
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
||||||
if (!type(*var)->interfaceType(false))
|
if (!type(*var)->interfaceType(false))
|
||||||
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||||
if (
|
if (
|
||||||
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||||
!typeSupportedByOldABIEncoder(*type(*var))
|
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
var->location(),
|
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()))
|
if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments()))
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
arguments[i]->location(),
|
arguments[i]->location(),
|
||||||
|
@ -415,7 +415,7 @@ MemberList const& Type::members(ContractDefinition const* _currentScope) const
|
|||||||
return *m_members[_currentScope];
|
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();
|
TypePointer encodingType = mobileType();
|
||||||
if (encodingType)
|
if (encodingType)
|
||||||
@ -423,7 +423,7 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p
|
|||||||
if (encodingType)
|
if (encodingType)
|
||||||
encodingType = encodingType->encodingType();
|
encodingType = encodingType->encodingType();
|
||||||
// Structs are fine in the following circumstances:
|
// Structs are fine in the following circumstances:
|
||||||
// - ABIv2 without packed encoding or,
|
// - ABIv2 or,
|
||||||
// - storage struct for a library
|
// - storage struct for a library
|
||||||
if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage))
|
if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage))
|
||||||
return encodingType;
|
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()))
|
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
||||||
baseType = arrayType->baseType();
|
baseType = arrayType->baseType();
|
||||||
if (dynamic_cast<StructType const*>(baseType.get()))
|
if (dynamic_cast<StructType const*>(baseType.get()))
|
||||||
if (!_encoderV2 || _packed)
|
if (!_encoderV2)
|
||||||
return TypePointer();
|
return TypePointer();
|
||||||
return encodingType;
|
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 ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||||
{
|
{
|
||||||
string functionName = string("abi_decode_tuple_");
|
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
|
// special case: convert storage reference type to value type - this is only
|
||||||
// possible for library calls where we just forward the storage reference
|
// possible for library calls where we just forward the storage reference
|
||||||
solAssert(_options.encodeAsLibraryTypes, "");
|
solAssert(_options.encodeAsLibraryTypes, "");
|
||||||
|
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
|
||||||
solAssert(to == IntegerType::uint256(), "");
|
solAssert(to == IntegerType::uint256(), "");
|
||||||
templ("cleanupConvert", "value");
|
templ("cleanupConvert", "value");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
string cleanupConvert;
|
||||||
if (_from == to)
|
if (_from == to)
|
||||||
templ("cleanupConvert", cleanupFunction(_from) + "(value)");
|
cleanupConvert = cleanupFunction(_from) + "(value)";
|
||||||
else
|
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();
|
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(
|
string ABIFunctions::abiEncodingFunctionCalldataArray(
|
||||||
Type const& _from,
|
Type const& _from,
|
||||||
Type const& _to,
|
Type const& _to,
|
||||||
@ -623,15 +737,15 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
|
|||||||
function <functionName>(start, length, pos) -> end {
|
function <functionName>(start, length, pos) -> end {
|
||||||
pos := <storeLength>(pos, length)
|
pos := <storeLength>(pos, length)
|
||||||
<copyFun>(start, 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("functionName", functionName);
|
||||||
templ("readableTypeNameFrom", _from.toString(true));
|
templ("readableTypeNameFrom", _from.toString(true));
|
||||||
templ("readableTypeNameTo", _to.toString(true));
|
templ("readableTypeNameTo", _to.toString(true));
|
||||||
templ("copyFun", copyToMemoryFunction(true));
|
templ("copyFun", copyToMemoryFunction(true));
|
||||||
templ("roundUpFun", roundUpFunction());
|
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||||
return templ.render();
|
return templ.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -659,8 +773,9 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
bool dynamic = _to.isDynamicallyEncoded();
|
bool dynamic = _to.isDynamicallyEncoded();
|
||||||
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
|
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
|
||||||
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
|
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
|
||||||
|
bool const usesTail = dynamicBase && !_options.dynamicInplace;
|
||||||
Whiskers templ(
|
Whiskers templ(
|
||||||
dynamicBase ?
|
usesTail ?
|
||||||
R"(
|
R"(
|
||||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||||
function <functionName>(value, pos) <return> {
|
function <functionName>(value, pos) <return> {
|
||||||
@ -688,9 +803,8 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
let srcPtr := <dataAreaFun>(value)
|
let srcPtr := <dataAreaFun>(value)
|
||||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||||
{
|
{
|
||||||
<encodeToMemoryFun>(<arrayElementAccess>, pos)
|
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
|
||||||
srcPtr := <nextArrayElement>(srcPtr)
|
srcPtr := <nextArrayElement>(srcPtr)
|
||||||
pos := add(pos, <elementEncodedSize>)
|
|
||||||
}
|
}
|
||||||
<assignEnd>
|
<assignEnd>
|
||||||
}
|
}
|
||||||
@ -702,17 +816,13 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
templ("return", dynamic ? " -> end " : "");
|
templ("return", dynamic ? " -> end " : "");
|
||||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||||
templ("lengthFun", arrayLengthFunction(_from));
|
templ("lengthFun", arrayLengthFunction(_from));
|
||||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to));
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||||
templ("dataAreaFun", arrayDataAreaFunction(_from));
|
templ("dataAreaFun", arrayDataAreaFunction(_from));
|
||||||
templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()));
|
|
||||||
|
|
||||||
EncodingOptions subOptions(_options);
|
EncodingOptions subOptions(_options);
|
||||||
subOptions.encodeFunctionFromStack = false;
|
subOptions.encodeFunctionFromStack = false;
|
||||||
templ("encodeToMemoryFun", abiEncodingFunction(
|
subOptions.padded = true;
|
||||||
*_from.baseType(),
|
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||||
*_to.baseType(),
|
|
||||||
subOptions
|
|
||||||
));
|
|
||||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
@ -742,15 +852,16 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray(
|
|||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value, pos) -> end {
|
function <functionName>(value, pos) -> end {
|
||||||
let length := <lengthFun>(value)
|
let length := <lengthFun>(value)
|
||||||
mstore(pos, length)
|
pos := <storeLength>(pos, length)
|
||||||
<copyFun>(add(value, 0x20), add(pos, 0x20), length)
|
<copyFun>(add(value, 0x20), pos, length)
|
||||||
end := add(add(pos, 0x20), <roundUpFun>(length))
|
end := add(pos, <lengthPadded>)
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("lengthFun", arrayLengthFunction(_from));
|
templ("lengthFun", arrayLengthFunction(_from));
|
||||||
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||||
templ("copyFun", copyToMemoryFunction(false));
|
templ("copyFun", copyToMemoryFunction(false));
|
||||||
templ("roundUpFun", roundUpFunction());
|
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||||
return templ.render();
|
return templ.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -784,28 +895,30 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
|||||||
case 0 {
|
case 0 {
|
||||||
// short byte array
|
// short byte array
|
||||||
let length := and(div(slotValue, 2), 0x7f)
|
let length := and(div(slotValue, 2), 0x7f)
|
||||||
mstore(pos, length)
|
pos := <storeLength>(pos, length)
|
||||||
mstore(add(pos, 0x20), and(slotValue, not(0xff)))
|
mstore(pos, and(slotValue, not(0xff)))
|
||||||
ret := add(pos, 0x40)
|
ret := add(pos, <lengthPaddedShort>)
|
||||||
}
|
}
|
||||||
case 1 {
|
case 1 {
|
||||||
// long byte array
|
// long byte array
|
||||||
let length := div(slotValue, 2)
|
let length := div(slotValue, 2)
|
||||||
mstore(pos, length)
|
pos := <storeLength>(pos, length)
|
||||||
pos := add(pos, 0x20)
|
|
||||||
let dataPos := <arrayDataSlot>(value)
|
let dataPos := <arrayDataSlot>(value)
|
||||||
let i := 0
|
let i := 0
|
||||||
for { } lt(i, length) { i := add(i, 0x20) } {
|
for { } lt(i, length) { i := add(i, 0x20) } {
|
||||||
mstore(add(pos, i), sload(dataPos))
|
mstore(add(pos, i), sload(dataPos))
|
||||||
dataPos := add(dataPos, 1)
|
dataPos := add(dataPos, 1)
|
||||||
}
|
}
|
||||||
ret := add(pos, i)
|
ret := add(pos, <lengthPaddedLong>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("readableTypeNameFrom", _from.toString(true));
|
templ("readableTypeNameFrom", _from.toString(true));
|
||||||
templ("readableTypeNameTo", _to.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));
|
templ("arrayDataSlot", arrayDataAreaFunction(_from));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
}
|
}
|
||||||
@ -819,7 +932,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
|||||||
size_t storageBytes = _from.baseType()->storageBytes();
|
size_t storageBytes = _from.baseType()->storageBytes();
|
||||||
size_t itemsPerSlot = 32 / storageBytes;
|
size_t itemsPerSlot = 32 / storageBytes;
|
||||||
// This always writes full slot contents to memory, which might be
|
// 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(
|
Whiskers templ(
|
||||||
R"(
|
R"(
|
||||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||||
@ -848,14 +961,16 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
|||||||
templ("return", dynamic ? " -> end " : "");
|
templ("return", dynamic ? " -> end " : "");
|
||||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||||
templ("lengthFun", arrayLengthFunction(_from));
|
templ("lengthFun", arrayLengthFunction(_from));
|
||||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to));
|
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||||
templ("dataArea", arrayDataAreaFunction(_from));
|
templ("dataArea", arrayDataAreaFunction(_from));
|
||||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||||
|
// We use padded size because array elements are always padded.
|
||||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||||
templ("elementEncodedSize", elementEncodedSize);
|
templ("elementEncodedSize", elementEncodedSize);
|
||||||
|
|
||||||
EncodingOptions subOptions(_options);
|
EncodingOptions subOptions(_options);
|
||||||
subOptions.encodeFunctionFromStack = false;
|
subOptions.encodeFunctionFromStack = false;
|
||||||
|
subOptions.padded = true;
|
||||||
string encodeToMemoryFun = abiEncodingFunction(
|
string encodeToMemoryFun = abiEncodingFunction(
|
||||||
*_from.baseType(),
|
*_from.baseType(),
|
||||||
*_to.baseType(),
|
*_to.baseType(),
|
||||||
@ -910,7 +1025,12 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
|||||||
templ("readableTypeNameFrom", _from.toString(true));
|
templ("readableTypeNameFrom", _from.toString(true));
|
||||||
templ("readableTypeNameTo", _to.toString(true));
|
templ("readableTypeNameTo", _to.toString(true));
|
||||||
templ("return", dynamic ? " -> end " : "");
|
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
|
// to avoid multiple loads from the same slot for subsequent members
|
||||||
templ("init", fromStorage ? "let slotValue := 0" : "");
|
templ("init", fromStorage ? "let slotValue := 0" : "");
|
||||||
u256 previousSlotOffset(-1);
|
u256 previousSlotOffset(-1);
|
||||||
@ -960,27 +1080,38 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
|||||||
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
|
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);
|
EncodingOptions subOptions(_options);
|
||||||
subOptions.encodeFunctionFromStack = false;
|
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;
|
members.back()["memberName"] = member.name;
|
||||||
}
|
}
|
||||||
templ("members", members);
|
templ("members", members);
|
||||||
|
if (_options.dynamicInplace)
|
||||||
|
solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size.");
|
||||||
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
});
|
});
|
||||||
@ -1007,9 +1138,10 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
|
|||||||
|
|
||||||
if (_to.isDynamicallySized())
|
if (_to.isDynamicallySized())
|
||||||
{
|
{
|
||||||
|
solAssert(_to.category() == Type::Category::Array, "");
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(pos) -> end {
|
function <functionName>(pos) -> end {
|
||||||
mstore(pos, <length>)
|
pos := <storeLength>(pos, <length>)
|
||||||
<#word>
|
<#word>
|
||||||
mstore(add(pos, <offset>), <wordValue>)
|
mstore(add(pos, <offset>), <wordValue>)
|
||||||
</word>
|
</word>
|
||||||
@ -1020,12 +1152,17 @@ string ABIFunctions::abiEncodingFunctionStringLiteral(
|
|||||||
|
|
||||||
// TODO this can make use of CODECOPY for large strings once we have that in Yul
|
// TODO this can make use of CODECOPY for large strings once we have that in Yul
|
||||||
size_t words = (value.size() + 31) / 32;
|
size_t words = (value.size() + 31) / 32;
|
||||||
templ("overallSize", to_string(32 + words * 32));
|
|
||||||
templ("length", to_string(value.size()));
|
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);
|
vector<map<string, string>> wordParams(words);
|
||||||
for (size_t i = 0; i < words; ++i)
|
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();
|
wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex();
|
||||||
}
|
}
|
||||||
templ("word", wordParams);
|
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)
|
string ABIFunctions::shiftLeftFunction(size_t _numBits)
|
||||||
{
|
{
|
||||||
solAssert(_numBits < 256, "");
|
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, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
if (_type.isDynamicallySized())
|
if (_type.isDynamicallySized() && !_options.dynamicInplace)
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(pos, length) -> updated_pos {
|
function <functionName>(pos, length) -> updated_pos {
|
||||||
mstore(pos, length)
|
mstore(pos, length)
|
||||||
|
@ -60,16 +60,26 @@ public:
|
|||||||
/// The values represent stack slots. If a type occupies more or less than one
|
/// The values represent stack slots. If a type occupies more or less than one
|
||||||
/// stack slot, it takes exactly that number of values.
|
/// stack slot, it takes exactly that number of values.
|
||||||
/// Returns a pointer to the end of the area written in memory.
|
/// 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.
|
/// 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(
|
std::string tupleEncoder(
|
||||||
TypePointers const& _givenTypes,
|
TypePointers const& _givenTypes,
|
||||||
TypePointers const& _targetTypes,
|
TypePointers const& _targetTypes,
|
||||||
bool _encodeAsLibraryTypes = false
|
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
|
/// @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
|
/// into memory. If @a _fromMemory is true, decodes from memory instead of
|
||||||
/// from calldata.
|
/// from calldata.
|
||||||
@ -89,7 +99,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
struct EncodingOptions
|
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;
|
bool padded = true;
|
||||||
/// Store arrays and structs in place without "data pointer" and do not store the length.
|
/// Store arrays and structs in place without "data pointer" and do not store the length.
|
||||||
bool dynamicInplace = false;
|
bool dynamicInplace = false;
|
||||||
@ -134,6 +146,14 @@ private:
|
|||||||
Type const& _targetType,
|
Type const& _targetType,
|
||||||
EncodingOptions const& _options
|
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.
|
/// Part of @a abiEncodingFunction for array target type and given calldata array.
|
||||||
std::string abiEncodingFunctionCalldataArray(
|
std::string abiEncodingFunctionCalldataArray(
|
||||||
Type const& _givenType,
|
Type const& _givenType,
|
||||||
@ -212,6 +232,10 @@ private:
|
|||||||
/// Pads with zeros and might write more than exactly length.
|
/// Pads with zeros and might write more than exactly length.
|
||||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
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 shiftLeftFunction(size_t _numBits);
|
||||||
std::string shiftRightFunction(size_t _numBits);
|
std::string shiftRightFunction(size_t _numBits);
|
||||||
/// @returns the name of a function that rounds its input to the next multiple
|
/// @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);
|
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||||
|
|
||||||
/// @returns the name of a function used during encoding that stores the length
|
/// @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 dynamically sized (and the options do not request in-place encoding).
|
||||||
/// If the array is not dynamically sized, does nothing and just returns the position again.
|
/// It returns the new encoding position.
|
||||||
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type);
|
/// 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.
|
/// @returns the name of a function that allocates memory.
|
||||||
/// Modifies the "free memory pointer"
|
/// Modifies the "free memory pointer"
|
||||||
|
@ -348,11 +348,15 @@ void CompilerUtils::encodeToMemory(
|
|||||||
|
|
||||||
if (_givenTypes.empty())
|
if (_givenTypes.empty())
|
||||||
return;
|
return;
|
||||||
else if (_padToWordBoundaries && !_copyDynamicDataInPlace && encoderV2)
|
if (encoderV2)
|
||||||
{
|
{
|
||||||
// Use the new Yul-based encoding function
|
// 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();
|
auto stackHeightBefore = m_context.stackHeight();
|
||||||
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes);
|
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries);
|
||||||
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
|
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -466,15 +470,22 @@ void CompilerUtils::encodeToMemory(
|
|||||||
void CompilerUtils::abiEncodeV2(
|
void CompilerUtils::abiEncodeV2(
|
||||||
TypePointers const& _givenTypes,
|
TypePointers const& _givenTypes,
|
||||||
TypePointers const& _targetTypes,
|
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>
|
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
||||||
|
|
||||||
auto ret = m_context.pushNewTag();
|
auto ret = m_context.pushNewTag();
|
||||||
moveIntoStack(sizeOnStack(_givenTypes) + 1);
|
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.appendJumpTo(m_context.namedTag(encoderName));
|
||||||
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
||||||
m_context << ret.tag();
|
m_context << ret.tag();
|
||||||
|
@ -159,7 +159,8 @@ public:
|
|||||||
void abiEncodeV2(
|
void abiEncodeV2(
|
||||||
TypePointers const& _givenTypes,
|
TypePointers const& _givenTypes,
|
||||||
TypePointers const& _targetTypes,
|
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,
|
/// 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);
|
arguments.front()->accept(*this);
|
||||||
utils().fetchFreeMemoryPointer();
|
utils().fetchFreeMemoryPointer();
|
||||||
|
solAssert(function.parameterTypes().front()->isValueType(), "");
|
||||||
utils().packedEncode(
|
utils().packedEncode(
|
||||||
{arguments.front()->annotation().type},
|
{arguments.front()->annotation().type},
|
||||||
{function.parameterTypes().front()}
|
{function.parameterTypes().front()}
|
||||||
@ -744,28 +745,32 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
_functionCall.expression().accept(*this);
|
_functionCall.expression().accept(*this);
|
||||||
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
|
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
|
||||||
unsigned numIndexed = 0;
|
unsigned numIndexed = 0;
|
||||||
|
TypePointers paramTypes = function.parameterTypes();
|
||||||
// All indexed arguments go to the stack
|
// All indexed arguments go to the stack
|
||||||
for (unsigned arg = arguments.size(); arg > 0; --arg)
|
for (unsigned arg = arguments.size(); arg > 0; --arg)
|
||||||
if (event.parameters()[arg - 1]->isIndexed())
|
if (event.parameters()[arg - 1]->isIndexed())
|
||||||
{
|
{
|
||||||
++numIndexed;
|
++numIndexed;
|
||||||
arguments[arg - 1]->accept(*this);
|
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().fetchFreeMemoryPointer();
|
||||||
utils().packedEncode(
|
utils().packedEncode(
|
||||||
{arguments[arg - 1]->annotation().type},
|
{arguments[arg - 1]->annotation().type},
|
||||||
{arrayType}
|
{referenceType}
|
||||||
);
|
);
|
||||||
utils().toSizeAfterFreeMemoryPointer();
|
utils().toSizeAfterFreeMemoryPointer();
|
||||||
m_context << Instruction::KECCAK256;
|
m_context << Instruction::KECCAK256;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
solAssert(paramTypes[arg - 1]->isValueType(), "");
|
||||||
utils().convertType(
|
utils().convertType(
|
||||||
*arguments[arg - 1]->annotation().type,
|
*arguments[arg - 1]->annotation().type,
|
||||||
*function.parameterTypes()[arg - 1],
|
*paramTypes[arg - 1],
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!event.isAnonymous())
|
if (!event.isAnonymous())
|
||||||
{
|
{
|
||||||
@ -782,7 +787,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
{
|
{
|
||||||
arguments[arg]->accept(*this);
|
arguments[arg]->accept(*this);
|
||||||
nonIndexedArgTypes.push_back(arguments[arg]->annotation().type);
|
nonIndexedArgTypes.push_back(arguments[arg]->annotation().type);
|
||||||
nonIndexedParamTypes.push_back(function.parameterTypes()[arg]);
|
nonIndexedParamTypes.push_back(paramTypes[arg]);
|
||||||
}
|
}
|
||||||
utils().fetchFreeMemoryPointer();
|
utils().fetchFreeMemoryPointer();
|
||||||
utils().abiEncode(nonIndexedArgTypes, nonIndexedParamTypes);
|
utils().abiEncode(nonIndexedArgTypes, nonIndexedParamTypes);
|
||||||
|
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
#include <libevmasm/Assembly.h>
|
#include <libevmasm/Assembly.h>
|
||||||
|
|
||||||
|
#include <libdevcore/Keccak256.h>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -13478,13 +13480,322 @@ BOOST_AUTO_TEST_CASE(abi_encodePacked)
|
|||||||
y[0] = "e";
|
y[0] = "e";
|
||||||
require(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");
|
compileAndRun(sourceCode, 0, "C");
|
||||||
ABI_CHECK(callContractFunction("f0()"), encodeArgs(0x20, 0));
|
bytes structEnc = encodeArgs(int(0x12), u256(-7), int(2), int(3), u256(-7), u256(-8));
|
||||||
ABI_CHECK(callContractFunction("f1()"), encodeArgs(0x20, 2, "\x01\x02"));
|
ABI_CHECK(callContractFunction("testStorage()"), encodeArgs());
|
||||||
ABI_CHECK(callContractFunction("f2()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
|
BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 2);
|
||||||
ABI_CHECK(callContractFunction("f3()"), encodeArgs(0x20, 5, "\x01" "abc" "\x02"));
|
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)
|
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.
|
// 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.
|
// 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.
|
// 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.
|
// 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: (191-192): Type not supported in packed mode.
|
||||||
// TypeError: (194-195): This type cannot be encoded.
|
// TypeError: (194-195): Type not supported in packed mode.
|
@ -13,5 +13,5 @@ contract C {
|
|||||||
// ----
|
// ----
|
||||||
// TypeError: (131-132): This type cannot be encoded.
|
// TypeError: (131-132): This type cannot be encoded.
|
||||||
// TypeError: (134-135): This type cannot be encoded.
|
// TypeError: (134-135): This type cannot be encoded.
|
||||||
// TypeError: (200-201): This type cannot be encoded.
|
// TypeError: (200-201): Type not supported in packed mode.
|
||||||
// TypeError: (203-204): This type cannot be encoded.
|
// TypeError: (203-204): Type not supported in packed mode.
|
||||||
|
@ -5,6 +5,10 @@ contract C {
|
|||||||
S s;
|
S s;
|
||||||
struct T { uint y; }
|
struct T { uint y; }
|
||||||
T t;
|
T t;
|
||||||
|
function e() public view {
|
||||||
|
S memory st;
|
||||||
|
abi.encodePacked(st);
|
||||||
|
}
|
||||||
function f() public view {
|
function f() public view {
|
||||||
abi.encode(s, t);
|
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.
|
// 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: (193-195): Type not supported in packed mode.
|
||||||
// TypeError: (238-239): This type cannot be encoded.
|
// 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: (156-157): Type not supported in packed mode.
|
||||||
// TypeError: (159-160): This type cannot be encoded.
|
// TypeError: (159-160): Type not supported in packed mode.
|
||||||
|
Loading…
Reference in New Issue
Block a user