Merge pull request #6262 from ethereum/abiEncodeCalldataArray

ABIEncoderV2: implement encoding of calldata arrays and structs.
This commit is contained in:
Daniel Kirchner 2019-04-04 14:51:45 +02:00 committed by GitHub
commit d0fda7ca7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 733 additions and 152 deletions

View File

@ -6,6 +6,7 @@ Important Bugfixes:
Language Features: Language Features:
* Code Generation: Implement copying recursive structs from storage to memory. * Code Generation: Implement copying recursive structs from storage to memory.
* ABIEncoderV2: Implement encoding of calldata arrays and structs.
Compiler Features: Compiler Features:

View File

@ -607,19 +607,31 @@ string ABIFunctions::abiEncodingFunction(
solAssert(_from.category() == Type::Category::Array, ""); solAssert(_from.category() == Type::Category::Array, "");
solAssert(to.dataStoredIn(DataLocation::Memory), ""); solAssert(to.dataStoredIn(DataLocation::Memory), "");
ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from); ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from);
if (fromArray.location() == DataLocation::CallData)
return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options); switch (fromArray.location())
else if (!fromArray.isByteArray() && ( {
fromArray.location() == DataLocation::Memory || case DataLocation::CallData:
fromArray.baseType()->storageBytes() > 16 if (
)) fromArray.isByteArray() ||
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); *fromArray.baseType() == IntegerType::uint256() ||
else if (fromArray.location() == DataLocation::Memory) *fromArray.baseType() == FixedBytesType(32)
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); )
else if (fromArray.location() == DataLocation::Storage) return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options);
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); else
else return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
solAssert(false, ""); case DataLocation::Memory:
if (fromArray.isByteArray())
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
case DataLocation::Storage:
if (fromArray.baseType()->storageBytes() <= 16)
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
default:
solAssert(false, "");
}
} }
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to)) else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
{ {
@ -721,19 +733,24 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
}); });
} }
string ABIFunctions::abiEncodingFunctionCalldataArray( string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
Type const& _from, Type const& _from,
Type const& _to, Type const& _to,
EncodingOptions const& _options EncodingOptions const& _options
) )
{ {
solAssert(_to.isDynamicallySized(), "");
solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from); auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
auto const& toArrayType = dynamic_cast<ArrayType const&>(_to); auto const& toArrayType = dynamic_cast<ArrayType const&>(_to);
solAssert(fromArrayType.location() == DataLocation::CallData, ""); solAssert(fromArrayType.location() == DataLocation::CallData, "");
solAssert(
fromArrayType.isByteArray() ||
*fromArrayType.baseType() == IntegerType::uint256() ||
*fromArrayType.baseType() == FixedBytesType(32),
"");
solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
solAssert( solAssert(
*fromArrayType.copyForLocation(DataLocation::Memory, true) == *fromArrayType.copyForLocation(DataLocation::Memory, true) ==
@ -748,24 +765,54 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
_to.identifier() + _to.identifier() +
_options.toFunctionNameSuffix(); _options.toFunctionNameSuffix();
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently."); bool needsPadding = _options.padded && fromArrayType.isByteArray();
// TODO if this is not a byte array, we might just copy byte-by-byte anyway, if (fromArrayType.isDynamicallySized())
// because the encoding is position-independent, but we have to check that. {
Whiskers templ(R"( Whiskers templ(R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(start, length, pos) -> end { function <functionName>(start, length, pos) -> end {
pos := <storeLength>(pos, length) pos := <storeLength>(pos, length)
<copyFun>(start, pos, length) <scaleLengthByStride>
end := add(pos, <lengthPadded>) <copyFun>(start, pos, length)
} end := add(pos, <lengthPadded>)
)"); }
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); )");
templ("functionName", functionName); templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
templ("readableTypeNameFrom", _from.toString(true)); templ("functionName", functionName);
templ("readableTypeNameTo", _to.toString(true)); if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1)
templ("copyFun", m_utils.copyToMemoryFunction(true)); templ("scaleLengthByStride", "");
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); else
return templ.render(); templ("scaleLengthByStride",
Whiskers(R"(
if gt(length, <maxLength>) { revert(0, 0) }
length := mul(length, <stride>)
)")
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
.render()
);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", m_utils.copyToMemoryFunction(true));
templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length");
return templ.render();
}
else
{
solAssert(fromArrayType.calldataStride() == 32, "");
Whiskers templ(R"(
// <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(start, pos) {
<copyFun>(start, pos, <byteLength>)
}
)");
templ("functionName", functionName);
templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true));
templ("copyFun", m_utils.copyToMemoryFunction(true));
templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride()));
return templ.render();
}
}); });
} }
@ -784,29 +831,34 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
solAssert(_from.length() == _to.length(), ""); solAssert(_from.length() == _to.length(), "");
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
solAssert(!_from.isByteArray(), ""); solAssert(!_from.isByteArray(), "");
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, ""); if (_from.dataStoredIn(DataLocation::Storage))
solAssert(_from.baseType()->storageBytes() > 16, "");
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
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 const usesTail = dynamicBase && !_options.dynamicInplace; bool const usesTail = dynamicBase && !_options.dynamicInplace;
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
string elementValues = m_utils.suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
Whiskers templ( Whiskers templ(
usesTail ? usesTail ?
R"( R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> { function <functionName>(value,<maybeLength> pos) <return> {
let length := <lengthFun>(value) <declareLength>
pos := <storeLength>(pos, length) pos := <storeLength>(pos, length)
let headStart := pos let headStart := pos
let tail := add(pos, mul(length, 0x20)) let tail := add(pos, mul(length, 0x20))
let srcPtr := <dataAreaFun>(value) let baseRef := <dataAreaFun>(value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) } for { let i := 0 } lt(i, length) { i := add(i, 1) }
{ {
mstore(pos, sub(tail, headStart)) mstore(pos, sub(tail, headStart))
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail) let <elementValues> := <arrayElementAccess>
tail := <encodeToMemoryFun>(<elementValues>, tail)
srcPtr := <nextArrayElement>(srcPtr) srcPtr := <nextArrayElement>(srcPtr)
pos := add(pos, 0x20) pos := add(pos, 0x20)
} }
@ -816,13 +868,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
)" : )" :
R"( R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
function <functionName>(value, pos) <return> { function <functionName>(value,<maybeLength> pos) <return> {
let length := <lengthFun>(value) <declareLength>
pos := <storeLength>(pos, length) pos := <storeLength>(pos, length)
let srcPtr := <dataAreaFun>(value) let baseRef := <dataAreaFun>(value)
let srcPtr := baseRef
for { let i := 0 } lt(i, length) { i := add(i, 1) } for { let i := 0 } lt(i, length) { i := add(i, 1) }
{ {
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos) let <elementValues> := <arrayElementAccess>
pos := <encodeToMemoryFun>(<elementValues>, pos)
srcPtr := <nextArrayElement>(srcPtr) srcPtr := <nextArrayElement>(srcPtr)
} }
<assignEnd> <assignEnd>
@ -830,27 +884,43 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
)" )"
); );
templ("functionName", functionName); templ("functionName", functionName);
templ("elementValues", elementValues);
bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized();
if (lengthAsArgument)
{
templ("maybeLength", " length,");
templ("declareLength", "");
}
else
{
templ("maybeLength", "");
templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)");
}
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 := pos" : ""); templ("assignEnd", dynamic ? "end := pos" : "");
templ("lengthFun", m_utils.arrayLengthFunction(_from));
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
EncodingOptions subOptions(_options);
subOptions.encodeFunctionFromStack = false;
subOptions.padded = true;
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
if (inMemory) switch (_from.location())
templ("arrayElementAccess", "mload(srcPtr)");
else if (_from.baseType()->isValueType())
{ {
solAssert(_from.dataStoredIn(DataLocation::Storage), ""); case DataLocation::Memory:
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); templ("arrayElementAccess", "mload(srcPtr)");
break;
case DataLocation::Storage:
if (_from.baseType()->isValueType())
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
else
templ("arrayElementAccess", "srcPtr");
break;
case DataLocation::CallData:
templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)");
break;
default:
solAssert(false, "");
} }
else
templ("arrayElementAccess", "srcPtr");
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
return templ.render(); return templ.render();
}); });
@ -1059,11 +1129,9 @@ string ABIFunctions::abiEncodingFunctionStruct(
_to.identifier() + _to.identifier() +
_options.toFunctionNameSuffix(); _options.toFunctionNameSuffix();
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() { return createFunction(functionName, [&]() {
bool fromStorage = _from.location() == DataLocation::Storage;
bool dynamic = _to.isDynamicallyEncoded(); bool dynamic = _to.isDynamicallyEncoded();
Whiskers templ(R"( Whiskers templ(R"(
// <readableTypeNameFrom> -> <readableTypeNameTo> // <readableTypeNameFrom> -> <readableTypeNameTo>
@ -1074,7 +1142,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
{ {
// <memberName> // <memberName>
<preprocess> <preprocess>
let memberValue := <retrieveValue> let <memberValues> := <retrieveValue>
<encode> <encode>
} }
</members> </members>
@ -1092,7 +1160,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
else else
templ("assignEnd", ""); 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", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : "");
u256 previousSlotOffset(-1); u256 previousSlotOffset(-1);
u256 encodingOffset = 0; u256 encodingOffset = 0;
vector<map<string, string>> members; vector<map<string, string>> members;
@ -1112,32 +1180,45 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.push_back({}); members.push_back({});
members.back()["preprocess"] = ""; members.back()["preprocess"] = "";
if (fromStorage) switch (_from.location())
{ {
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); case DataLocation::Storage:
u256 storageSlotOffset;
size_t intraSlotOffset;
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
if (memberTypeFrom->isValueType())
{ {
if (storageSlotOffset != previousSlotOffset) solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
u256 storageSlotOffset;
size_t intraSlotOffset;
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
if (memberTypeFrom->isValueType())
{ {
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; if (storageSlotOffset != previousSlotOffset)
previousSlotOffset = storageSlotOffset; {
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
previousSlotOffset = storageSlotOffset;
}
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
} }
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; else
{
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
solAssert(intraSlotOffset == 0, "");
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")";
}
break;
} }
else case DataLocation::Memory:
{ {
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
solAssert(intraSlotOffset == 0, ""); members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; break;
} }
} case DataLocation::CallData:
else {
{ string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name));
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))";
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; break;
}
default:
solAssert(false, "");
} }
EncodingOptions subOptions(_options); EncodingOptions subOptions(_options);
@ -1145,10 +1226,14 @@ string ABIFunctions::abiEncodingFunctionStruct(
// Like with arrays, struct members are always padded. // Like with arrays, struct members are always padded.
subOptions.padded = true; subOptions.padded = true;
string memberValues = m_utils.suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions));
members.back()["memberValues"] = memberValues;
string encode; string encode;
if (_options.dynamicInplace) if (_options.dynamicInplace)
encode = Whiskers{"pos := <encode>(memberValue, pos)"} encode = Whiskers{"pos := <encode>(<memberValues>, pos)"}
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
("memberValues", memberValues)
.render(); .render();
else else
{ {
@ -1156,10 +1241,11 @@ string ABIFunctions::abiEncodingFunctionStruct(
dynamicMember ? dynamicMember ?
string(R"( string(R"(
mstore(add(pos, <encodingOffset>), sub(tail, pos)) mstore(add(pos, <encodingOffset>), sub(tail, pos))
tail := <abiEncode>(memberValue, tail) tail := <abiEncode>(<memberValues>, tail)
)") : )") :
"<abiEncode>(memberValue, add(pos, <encodingOffset>))" "<abiEncode>(<memberValues>, add(pos, <encodingOffset>))"
); );
encodeTempl("memberValues", memberValues);
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
@ -1616,7 +1702,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
}); });
} }
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes) string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{ {
solUnimplementedAssert(!_splitFunctionTypes, ""); solUnimplementedAssert(!_splitFunctionTypes, "");
@ -1662,6 +1747,80 @@ string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset,
}); });
} }
string ABIFunctions::calldataAccessFunction(Type const& _type)
{
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_access_" + _type.identifier();
return createFunction(functionName, [&]() {
if (_type.isDynamicallyEncoded())
{
unsigned int baseEncodedSize = _type.calldataEncodedSize();
solAssert(baseEncodedSize > 1, "");
Whiskers w(R"(
function <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
value := add(rel_offset_of_tail, base_ref)
<handleLength>
}
)");
if (_type.isDynamicallySized())
{
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
solAssert(!!arrayType, "");
unsigned int calldataStride = arrayType->calldataStride();
w("handleLength", Whiskers(R"(
length := calldataload(value)
value := add(value, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
)")("calldataStride", toCompactHexWithPrefix(calldataStride)).render());
w("return", "value, length");
}
else
{
w("handleLength", "");
w("return", "value");
}
w("neededLength", toCompactHexWithPrefix(baseEncodedSize));
w("functionName", functionName);
return w.render();
}
else if (_type.isValueType())
{
string decodingFunction;
if (auto const* functionType = dynamic_cast<FunctionType const*>(&_type))
decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false);
else
decodingFunction = abiDecodingFunctionValueType(_type, false);
// Note that the second argument to the decoding function should be discarded after inlining.
return Whiskers(R"(
function <functionName>(baseRef, ptr) -> value {
value := <decodingFunction>(ptr, add(ptr, 32))
}
)")
("functionName", functionName)
("decodingFunction", decodingFunction)
.render();
}
else
{
solAssert(
_type.category() == Type::Category::Array ||
_type.category() == Type::Category::Struct,
""
);
return Whiskers(R"(
function <functionName>(baseRef, ptr) -> value {
value := ptr
}
)")
("functionName", functionName)
.render();
}
});
}
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
{ {
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();

View File

@ -176,7 +176,9 @@ private:
EncodingOptions const& _options 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( /// Uses calldatacopy and does not perform cleanup or validation and can therefore only
/// be used for byte arrays and arrays with the base type uint256 or bytes32.
std::string abiEncodingFunctionCalldataArrayWithoutCleanup(
Type const& _givenType, Type const& _givenType,
Type const& _targetType, Type const& _targetType,
EncodingOptions const& _options EncodingOptions const& _options
@ -263,6 +265,9 @@ private:
/// single variable. /// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type 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 (and the options do not request in-place encoding). /// if the array is dynamically sized (and the options do not request in-place encoding).
/// It returns the new encoding position. /// It returns the new encoding position.

View File

@ -330,40 +330,53 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{ {
string functionName = "array_dataslot_" + _type.identifier(); string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (_type.dataStoredIn(DataLocation::Memory)) switch (_type.location())
{ {
if (_type.isDynamicallySized()) case DataLocation::Memory:
return Whiskers(R"( if (_type.isDynamicallySized())
function <functionName>(memPtr) -> dataPtr { return Whiskers(R"(
dataPtr := add(memPtr, 0x20) function <functionName>(memPtr) -> dataPtr {
} dataPtr := add(memPtr, 0x20)
)") }
("functionName", functionName) )")
.render(); ("functionName", functionName)
else .render();
return Whiskers(R"( else
function <functionName>(memPtr) -> dataPtr { return Whiskers(R"(
dataPtr := memPtr function <functionName>(memPtr) -> dataPtr {
} dataPtr := memPtr
)") }
("functionName", functionName) )")
.render(); ("functionName", functionName)
} .render();
else if (_type.dataStoredIn(DataLocation::Storage)) case DataLocation::Storage:
{ if (_type.isDynamicallySized())
if (_type.isDynamicallySized()) {
{ Whiskers w(R"(
Whiskers w(R"( function <functionName>(slot) -> dataSlot {
function <functionName>(slot) -> dataSlot { mstore(0, slot)
mstore(0, slot) dataSlot := keccak256(0, 0x20)
dataSlot := keccak256(0, 0x20) }
} )");
)"); w("functionName", functionName);
w("functionName", functionName); return w.render();
return w.render(); }
} else
else {
Whiskers w(R"(
function <functionName>(slot) -> dataSlot {
dataSlot := slot
}
)");
w("functionName", functionName);
return w.render();
}
case DataLocation::CallData:
{ {
// Calldata arrays are stored as offset of the data area and length
// on the stack, so the offset already points to the data area.
// This might change, if calldata arrays are stored in a single
// stack slot at some point.
Whiskers w(R"( Whiskers w(R"(
function <functionName>(slot) -> dataSlot { function <functionName>(slot) -> dataSlot {
dataSlot := slot dataSlot := slot
@ -372,11 +385,8 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
w("functionName", functionName); w("functionName", functionName);
return w.render(); return w.render();
} }
} default:
else solAssert(false, "");
{
// Not used for calldata
solAssert(false, "");
} }
}); });
} }
@ -384,36 +394,40 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{ {
solAssert(!_type.isByteArray(), ""); solAssert(!_type.isByteArray(), "");
solAssert( if (_type.dataStoredIn(DataLocation::Storage))
_type.location() == DataLocation::Memory || solAssert(_type.baseType()->storageBytes() > 16, "");
_type.location() == DataLocation::Storage,
""
);
solAssert(
_type.location() == DataLocation::Memory ||
_type.baseType()->storageBytes() > 16,
""
);
string functionName = "array_nextElement_" + _type.identifier(); string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (_type.location() == DataLocation::Memory) switch (_type.location())
return Whiskers(R"( {
function <functionName>(memPtr) -> nextPtr { case DataLocation::Memory:
nextPtr := add(memPtr, 0x20) return Whiskers(R"(
} function <functionName>(memPtr) -> nextPtr {
)") nextPtr := add(memPtr, 0x20)
("functionName", functionName) }
.render(); )")
else if (_type.location() == DataLocation::Storage) ("functionName", functionName)
return Whiskers(R"( .render();
function <functionName>(slot) -> nextSlot { case DataLocation::Storage:
nextSlot := add(slot, 1) return Whiskers(R"(
} function <functionName>(slot) -> nextSlot {
)") nextSlot := add(slot, 1)
("functionName", functionName) }
.render(); )")
else ("functionName", functionName)
solAssert(false, ""); .render();
case DataLocation::CallData:
return Whiskers(R"(
function <functionName>(calldataPtr) -> nextPtr {
nextPtr := add(calldataPtr, <stride>)
}
)")
("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize()))
("functionName", functionName)
.render();
default:
solAssert(false, "");
}
}); });
} }

View File

@ -79,11 +79,11 @@ public:
/// The function reverts for too large lengths. /// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type); std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number /// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// which is stored in that slot / memory area. /// for the data position of an array which is stored in that slot / memory area / calldata area.
std::string arrayDataAreaFunction(ArrayType const& _type); std::string arrayDataAreaFunction(ArrayType const& _type);
/// @returns the name of a function that advances an array data pointer to the next element. /// @returns the name of a function that advances an array data pointer to the next element.
/// Only works for memory arrays and storage arrays that store one item per slot. /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type); std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that allocates memory. /// @returns the name of a function that allocates memory.

View File

@ -2,7 +2,7 @@
======= gas_test_abiv2/input.sol:C ======= ======= gas_test_abiv2/input.sol:C =======
Gas estimation: Gas estimation:
construction: construction:
1160 + 1115800 = 1116960 1160 + 1119000 = 1120160
external: external:
a(): 530 a(): 530
b(uint256): infinite b(uint256): infinite

View File

@ -8447,6 +8447,9 @@ BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional)
function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) { function test()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) {
return a[i][j]; return a[i][j];
} }
function reenc()" + arrayType + R"( calldata a, uint256 i, uint256 j) external returns (uint256) {
return this.test(a, i, j);
}
} }
)"; )";
compileAndRun(sourceCode, 0, "C"); compileAndRun(sourceCode, 0, "C");
@ -8464,7 +8467,10 @@ BOOST_AUTO_TEST_CASE(calldata_array_two_dimensional)
{ {
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size())); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256)", 0x40, i, encoding), encodeArgs(data[i].size()));
for (size_t j = 0; j < data[i].size(); j++) for (size_t j = 0; j < data[i].size(); j++)
{
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j])); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j]));
ABI_CHECK(callContractFunction("reenc(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j]));
}
// out of bounds access // out of bounds access
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs()); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, data[i].size(), encoding), encodeArgs());
} }
@ -8514,6 +8520,9 @@ BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional)
function test()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) { function test()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) {
return a[i][j][k]; return a[i][j][k];
} }
function reenc()" + arrayType + R"( calldata a, uint256 i, uint256 j, uint256 k) external returns (uint256) {
return this.test(a, i, j, k);
}
} }
)"; )";
compileAndRun(sourceCode, 0, "C"); compileAndRun(sourceCode, 0, "C");
@ -8540,7 +8549,10 @@ BOOST_AUTO_TEST_CASE(calldata_array_dynamic_three_dimensional)
{ {
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j].size())); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256)", 0x60, i, j, encoding), encodeArgs(data[i][j].size()));
for (size_t k = 0; k < data[i][j].size(); k++) for (size_t k = 0; k < data[i][j].size(); k++)
{
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k])); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k]));
ABI_CHECK(callContractFunction("reenc(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, k, encoding), encodeArgs(data[i][j][k]));
}
// out of bounds access // out of bounds access
ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, data[i][j].size(), encoding), encodeArgs()); ABI_CHECK(callContractFunction("test(" + arrayType + ",uint256,uint256,uint256)", 0x80, i, j, data[i][j].size(), encoding), encodeArgs());
} }

View File

@ -0,0 +1,22 @@
pragma experimental ABIEncoderV2;
contract C {
function g(uint256[] calldata) external pure returns (bytes memory) {
return msg.data;
}
function f(uint256[][1] calldata s) external view returns (bool) {
bytes memory a = this.g(s[0]);
uint256[] memory m = s[0];
bytes memory b = this.g(m);
assert(a.length == b.length);
for (uint i = 0; i < a.length; i++)
assert(a[i] == b[i]);
return true;
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[][1]): 32, 32, 0 -> true
// f(uint256[][1]): 32, 32, 1, 42 -> true
// f(uint256[][1]): 32, 32, 8, 421, 422, 423, 424, 425, 426, 427, 428 -> true

View File

@ -0,0 +1,33 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(uint256[] calldata s) external view returns (bytes memory) {
return this.f(s);
}
function h(uint8[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function i(uint8[] calldata s) external view returns (bytes memory) {
return this.h(s);
}
function j(bytes calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function k(bytes calldata s) external view returns (bytes memory) {
return this.j(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87
// g(uint256[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87
// h(uint8[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87
// i(uint8[]): 32, 3, 23, 42, 87 -> 32, 160, 32, 3, 23, 42, 87
// h(uint8[]): 32, 3, 0xFF23, 0x1242, 0xAB87 -> FAILURE
// i(uint8[]): 32, 3, 0xAB23, 0x1242, 0xFF87 -> FAILURE
// j(bytes): 32, 3, hex"123456" -> 32, 96, 32, 3, left(0x123456)
// k(bytes): 32, 3, hex"AB33FF" -> 32, 96, 32, 3, left(0xAB33FF)

View File

@ -0,0 +1,34 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(uint256[][2] calldata s, uint256 which) external view returns (bytes memory) {
return this.f(s[which]);
}
function h(uint8[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function i(uint8[][2] calldata s, uint256 which) external view returns (bytes memory) {
return this.h(s[which]);
}
function j(bytes calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function k(bytes[2] calldata s, uint256 which) external view returns (bytes memory) {
return this.j(s[which]);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[]): 32, 3, 42, 23, 87 -> 32, 160, 32, 3, 42, 23, 87
// g(uint256[][2],uint256): 0x40, 0, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17 -> 32, 160, 32, 3, 42, 23, 87
// g(uint256[][2],uint256): 0x40, 1, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17, 27 -> 32, 192, 32, 4, 11, 13, 17, 27
// h(uint8[]): 32, 3, 42, 23, 87 -> 32, 160, 32, 3, 42, 23, 87
// i(uint8[][2],uint256): 0x40, 0, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17 -> 32, 160, 32, 3, 42, 23, 87
// i(uint8[][2],uint256): 0x40, 1, 0x40, 0xC0, 3, 42, 23, 87, 4, 11, 13, 17, 27 -> 32, 192, 32, 4, 11, 13, 17, 27
// j(bytes): 32, 3, hex"AB11FF" -> 32, 96, 32, 3, left(0xAB11FF)
// k(bytes[2],uint256): 0x40, 0, 0x40, 0x63, 3, hex"AB11FF", 4, hex"FF791432" -> 32, 96, 32, 3, left(0xAB11FF)
// k(bytes[2],uint256): 0x40, 1, 0x40, 0x63, 3, hex"AB11FF", 4, hex"FF791432" -> 32, 96, 32, 4, left(0xFF791432)

View File

@ -0,0 +1,49 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint8[][1][] calldata s) external pure returns (bytes memory) {
return msg.data;
}
function f2(uint256[][2][] calldata s) external pure returns (bytes memory) {
return msg.data;
}
function reenc_f(uint8[][1][] calldata s) external view returns (bytes memory) {
return this.f(s);
}
function reenc_f2(uint256[][2][] calldata s) external view returns (bytes memory) {
return this.f2(s);
}
function g() external returns (bytes memory) {
uint8[][1][] memory m = new uint8[][1][](1);
m[0][0] = new uint8[](1);
m[0][0][0] = 42;
return this.f(m);
}
function h() external returns (bytes memory) {
uint8[][1][] memory m = new uint8[][1][](1);
m[0][0] = new uint8[](1);
m[0][0][0] = 42;
return this.reenc_f(m);
}
function i() external returns (bytes memory) {
uint256[][2][] memory m = new uint256[][2][](1);
m[0][0] = new uint256[](1);
m[0][1] = new uint256[](1);
m[0][0][0] = 42;
m[0][1][0] = 42;
return this.f2(m);
}
function j() external returns (bytes memory) {
uint256[][2][] memory m = new uint256[][2][](1);
m[0][0] = new uint256[](1);
m[0][1] = new uint256[](1);
m[0][0][0] = 42;
m[0][1][0] = 42;
return this.reenc_f2(m);
}
}
// ====
// EVMVersion: >homestead
// ----
// g() -> 32, 196, hex"eccb829a", 32, 1, 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000"
// h() -> 32, 196, hex"eccb829a", 32, 1, 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000"

View File

@ -0,0 +1,30 @@
pragma experimental ABIEncoderV2;
contract C {
function f(function() external returns (uint)[] calldata s) external returns (uint, uint, uint) {
assert(s.length == 3);
return (s[0](), s[1](), s[2]());
}
function f_reenc(function() external returns (uint)[] calldata s) external returns (uint, uint, uint) {
return this.f(s);
}
function getter1() external returns (uint) {
return 23;
}
function getter2() external returns (uint) {
return 37;
}
function getter3() external returns (uint) {
return 71;
}
function g(bool reenc) external returns (uint, uint, uint) {
function() external returns (uint)[] memory a = new function() external returns (uint)[](3);
a[0] = this.getter1;
a[1] = this.getter2;
a[2] = this.getter3;
return reenc ? this.f_reenc(a) : this.f(a);
}
}
// ----
// g(bool): false -> 23, 37, 71
// g(bool): true -> 23, 37, 71

View File

@ -0,0 +1,31 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[][] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(uint256[][] calldata s) external view returns (bytes memory) {
return this.f(s);
}
function h(uint8[][] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function i(uint8[][] calldata s) external view returns (bytes memory) {
return this.h(s);
}
function j(bytes[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function k(bytes[] calldata s) external view returns (bytes memory) {
return this.j(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41
// g(uint256[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41
// h(uint8[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41
// i(uint8[][]): 0x20, 2, 0x40, 0xC0, 3, 13, 17, 23, 4, 27, 31, 37, 41 -> 32, 416, 32, 2, 64, 192, 3, 13, 17, 23, 4, 27, 31, 37, 41
// j(bytes[]): 0x20, 2, 0x40, 0x63, 3, hex"131723", 4, hex"27313741" -> 32, 256, 32, 2, 64, 128, 3, left(0x131723), 4, left(0x27313741)
// k(bytes[]): 0x20, 2, 0x40, 0x63, 3, hex"131723", 4, hex"27313741" -> 32, 256, 32, 2, 64, 128, 3, left(0x131723), 4, left(0x27313741)

View File

@ -0,0 +1,25 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[3] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(uint256[3] calldata s) external view returns (bytes memory) {
return this.f(s);
}
function h(uint8[3] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function i(uint8[3] calldata s) external view returns (bytes memory) {
return this.h(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// g(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// h(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// i(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// h(uint8[3]): 0xFF23, 0x1242, 0xAB87 -> FAILURE
// i(uint8[3]): 0xAB23, 0x1242, 0xFF87 -> FAILURE

View File

@ -0,0 +1,49 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint8[1][][1] calldata s) external pure returns (bytes memory) {
return msg.data;
}
function f2(uint256[2][][2] calldata s) external pure returns (bytes memory) {
return msg.data;
}
function reenc_f(uint8[1][][1] calldata s) external view returns (bytes memory) {
return this.f(s);
}
function reenc_f2(uint256[2][][2] calldata s) external view returns (bytes memory) {
return this.f2(s);
}
function g() external returns (bytes memory) {
uint8[1][][1] memory m = [new uint8[1][](1)];
m[0][0][0] = 42;
return this.f(m);
}
function h() external returns (bytes memory) {
uint8[1][][1] memory m = [new uint8[1][](1)];
m[0][0][0] = 42;
return this.reenc_f(m);
}
function i() external returns (bytes memory) {
uint256[2][][2] memory m = [new uint256[2][](1),new uint256[2][](1)];
m[0][0][0] = 0x00042;
m[0][0][1] = 0x00142;
m[1][0][0] = 0x10042;
m[1][0][1] = 0x10142;
return this.f2(m);
}
function j() external returns (bytes memory) {
uint256[2][][2] memory m = [new uint256[2][](1),new uint256[2][](1)];
m[0][0][0] = 0x00042;
m[0][0][1] = 0x00142;
m[1][0][0] = 0x10042;
m[1][0][1] = 0x10142;
return this.reenc_f2(m);
}
}
// ====
// EVMVersion: >homestead
// ----
// g() -> 32, 132, hex"15cfcc01", 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000"
// h() -> 32, 132, hex"15cfcc01", 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000"
// i() -> 32, 292, hex"dc0ee233", 32, 64, 160, 1, 0x42, 0x000142, 1, 0x010042, 0x010142, hex"00000000000000000000000000000000000000000000000000000000"
// j() -> 32, 292, hex"dc0ee233", 32, 64, 160, 1, 0x42, 0x000142, 1, 0x010042, 0x010142, hex"00000000000000000000000000000000000000000000000000000000"

View File

@ -0,0 +1,25 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[3] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(uint256[3][2] calldata s, uint256 which) external view returns (bytes memory) {
return this.f(s[which]);
}
function h(uint8[3] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function i(uint8[3][2] calldata s, uint256 which) external view returns (bytes memory) {
return this.h(s[which]);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// g(uint256[3][2],uint256): 23, 42, 87, 123, 142, 187, 0 -> 32, 96, 23, 42, 87
// g(uint256[3][2],uint256): 23, 42, 87, 123, 142, 187, 1 -> 32, 96, 123, 142, 187
// h(uint8[3]): 23, 42, 87 -> 32, 96, 23, 42, 87
// i(uint8[3][2],uint256): 23, 42, 87, 123, 142, 187, 0 -> 32, 96, 23, 42, 87
// i(uint8[3][2],uint256): 23, 42, 87, 123, 142, 187, 1 -> 32, 96, 123, 142, 187

View File

@ -0,0 +1,16 @@
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256[] a; }
function f(S[] calldata s) external pure returns (bytes memory) {
return abi.encode(s);
}
function g(S[] calldata s) external view returns (bytes memory) {
return this.f(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f((uint256[])[]): 32, 1, 32, 32, 3, 17, 42, 23 -> 32, 256, 32, 1, 32, 32, 3, 17, 42, 23
// g((uint256[])[]): 32, 1, 32, 32, 3, 17, 42, 23 -> 32, 256, 32, 1, 32, 32, 3, 17, 42, 23

View File

@ -0,0 +1,20 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[] calldata s1, uint256[] calldata s2, bool which) external pure returns (bytes memory) {
if (which)
return abi.encode(s1);
else
return abi.encode(s2);
}
function g(uint256[] calldata s1, uint256[] calldata s2, bool which) external view returns (bytes memory) {
return this.f(s1, s2, which);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[],uint256[],bool): 0x60, 0xE0, true, 3, 23, 42, 87, 2, 51, 72 -> 32, 160, 0x20, 3, 23, 42, 87
// f(uint256[],uint256[],bool): 0x60, 0xE0, false, 3, 23, 42, 87, 2, 51, 72 -> 32, 128, 0x20, 2, 51, 72
// g(uint256[],uint256[],bool): 0x60, 0xE0, true, 3, 23, 42, 87, 2, 51, 72 -> 32, 160, 0x20, 3, 23, 42, 87
// g(uint256[],uint256[],bool): 0x60, 0xE0, false, 3, 23, 42, 87, 2, 51, 72 -> 32, 128, 0x20, 2, 51, 72

View File

@ -0,0 +1,20 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint256[3] calldata s1, uint256[2] calldata s2, bool which) external pure returns (bytes memory) {
if (which)
return abi.encode(s1);
else
return abi.encode(s2);
}
function g(uint256[3] calldata s1, uint256[2] calldata s2, bool which) external view returns (bytes memory) {
return this.f(s1, s2, which);
}
}
// ====
// EVMVersion: >homestead
// ----
// f(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, true -> 32, 96, 23, 42, 87
// f(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, false -> 32, 64, 51, 72
// g(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, true -> 32, 96, 23, 42, 87
// g(uint256[3],uint256[2],bool): 23, 42, 87, 51, 72, false -> 32, 64, 51, 72

View File

@ -0,0 +1,18 @@
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256[] a; }
function f(S calldata s) external returns (bytes memory) {
return abi.encode(s);
}
function g(S calldata s) external returns (bytes memory) {
return this.f(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f((uint256[])): 0x20, 0x20, 3, 42, 23, 17 -> 32, 192, 0x20, 0x20, 3, 42, 23, 17
// g((uint256[])): 0x20, 0x20, 3, 42, 23, 17 -> 32, 192, 0x20, 0x20, 3, 42, 23, 17

View File

@ -0,0 +1,18 @@
pragma experimental ABIEncoderV2;
contract C {
struct S { uint256 a; }
function f(S calldata s) external returns (bytes memory) {
return abi.encode(s);
}
function g(S calldata s) external returns (bytes memory) {
return this.f(s);
}
}
// ====
// EVMVersion: >homestead
// ----
// f((uint256)): 3 -> 32, 32, 3
// g((uint256)): 3 -> 32, 32, 3