From bdc1ff8ec71bdf96e0706c951dd92def3aee4aa2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 1 Sep 2017 13:59:21 +0200 Subject: [PATCH 1/4] ABI decoder. --- Changelog.md | 2 + libsolidity/codegen/ABIFunctions.cpp | 425 ++++++++++++++++++++++- libsolidity/codegen/ABIFunctions.h | 47 +++ libsolidity/codegen/CompilerContext.cpp | 1 + libsolidity/codegen/CompilerUtils.cpp | 17 + libsolidity/codegen/CompilerUtils.h | 7 + libsolidity/codegen/ContractCompiler.cpp | 10 + 7 files changed, 505 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 27e728388..f69a39ce7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Features: * Allow constant variables to be used as array length + * Code Generator: New ABI decoder which supports structs and arbitrarily nested + arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``. * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index bb39cbbba..c9a9ff574 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,9 +22,13 @@ #include +#include +#include + #include -#include +#include +#include using namespace std; using namespace dev; @@ -99,6 +103,73 @@ string ABIFunctions::tupleEncoder( }); } +string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) +{ + string functionName = string("abi_decode_tuple_"); + for (auto const& t: _types) + functionName += t->identifier(); + if (_fromMemory) + functionName += "_fromMemory"; + + return createFunction(functionName, [&]() { + solAssert(!_types.empty(), ""); + + TypePointers decodingTypes; + for (auto const& t: _types) + decodingTypes.emplace_back(t->decodingType()); + + Whiskers templ(R"( + function (headStart, dataEnd) -> { + switch slt(sub(dataEnd, headStart), ) case 1 { revert(0, 0) } + + } + )"); + templ("functionName", functionName); + templ("minimumSize", to_string(headSize(decodingTypes))); + + string decodeElements; + vector valueReturnParams; + size_t headPos = 0; + size_t stackPos = 0; + for (size_t i = 0; i < _types.size(); ++i) + { + solAssert(_types[i], ""); + solAssert(decodingTypes[i], ""); + size_t sizeOnStack = _types[i]->sizeOnStack(); + solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), ""); + solAssert(sizeOnStack > 0, ""); + vector valueNamesLocal; + for (size_t j = 0; j < sizeOnStack; j++) + { + valueNamesLocal.push_back("value" + to_string(stackPos)); + valueReturnParams.push_back("value" + to_string(stackPos)); + stackPos++; + } + bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl(R"( + { + let offset := )" + string( + dynamic ? + "(add(headStart, ))" : + "" + ) + R"( + := (add(headStart, offset), dataEnd) + } + )"); + elementTempl("load", _fromMemory ? "mload" : "calldataload"); + elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); + elementTempl("pos", to_string(headPos)); + elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); + decodeElements += elementTempl.render(); + headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); + } + templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); + templ("decodeElements", decodeElements); + + return templ.render(); + }); +} + string ABIFunctions::requestedFunctions() { string result; @@ -141,10 +212,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Array: - solAssert(false, "Array cleanup requested."); - break; case Type::Category::Struct: - solAssert(false, "Struct cleanup requested."); + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); break; case Type::Category::FixedBytes: { @@ -367,6 +437,24 @@ string ABIFunctions::combineExternalFunctionIdFunction() }); } +string ABIFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function (combined) -> addr, selector { + combined := (combined) + selector := and(combined, 0xffffffff) + addr := (combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32, false)) + ("shr64", shiftRightFunction(64, false)) + .render(); + }); +} + string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -963,6 +1051,290 @@ string ABIFunctions::abiEncodingFunctionFunctionType( }); } +string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); + + if (auto arrayType = dynamic_cast(decodingType.get())) + { + if (arrayType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataArray(*arrayType); + } + else if (arrayType->isByteArray()) + return abiDecodingFunctionByteArray(*arrayType, _fromMemory); + else + return abiDecodingFunctionArray(*arrayType, _fromMemory); + } + else if (auto const* structType = dynamic_cast(decodingType.get())) + return abiDecodingFunctionStruct(*structType, _fromMemory); + else if (auto const* functionType = dynamic_cast(decodingType.get())) + return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + + solAssert(decodingType->sizeOnStack() == 1, ""); + solAssert(decodingType->isValueType(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); + solAssert(!decodingType->isDynamicallyEncoded(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function (offset, end) -> value { + value := ((offset)) + } + )"); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + // Cleanup itself should use the type and not decodingType, because e.g. + // the decoding type of an enum is a plain int. + templ("cleanup", cleanupFunction(_type, true)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(!_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solAssert(!_type.dataStoredIn(DataLocation::Storage), ""); + + return createFunction(functionName, [&]() { + string load = _fromMemory ? "mload" : "calldataload"; + bool dynamicBase = _type.baseType()->isDynamicallyEncoded(); + Whiskers templ( + R"( + // + function (offset, end) -> array { + let length := + array := ((length)) + let dst := array + // might update offset and dst + let src := offset + + for { let i := 0 } lt(i, length) { i := add(i, 1) } + { + let elementPos := + + mstore(dst, (elementPos, end)) + dst := add(dst, 0x20) + src := add(src, ) + } + } + )" + ); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + if (_type.isDynamicallySized()) + templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); + else + templ("storeLength", ""); + if (dynamicBase) + { + templ("staticBoundsCheck", ""); + // The dynamic bounds check might not be needed (because we have an additional check + // one level deeper), but we keep it in just in case. This at least prevents + // the part one level deeper from reading the length from an out of bounds position. + templ("dynamicBoundsCheck", "switch gt(elementPos, end) case 1 { revert(0, 0) }"); + templ("retrieveElementPos", "add(offset, " + load + "(src))"); + templ("baseEncodedSize", "0x20"); + } + else + { + string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); + templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }"); + templ("dynamicBoundsCheck", ""); + templ("retrieveElementPos", "src"); + templ("baseEncodedSize", baseEncodedSize); + } + templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) +{ + // This does not work with arrays of complex types - the array access + // is not yet implemented in Solidity. + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + if (!_type.isDynamicallySized()) + solAssert(_type.length() < u256("0xffffffffffffffff"), ""); + solAssert(!_type.baseType()->isDynamicallyEncoded(), ""); + solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); + + string functionName = + "abi_decode_" + + _type.identifier(); + return createFunction(functionName, [&]() { + string templ; + if (_type.isDynamicallySized()) + templ = R"( + // + function (offset, end) -> arrayPos, length { + length := calldataload(offset) + switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + arrayPos := add(offset, 0x20) + switch gt(add(arrayPos, mul(, )), end) case 1 { revert(0, 0) } + } + )"; + else + templ = R"( + // + function (offset, end) -> arrayPos { + arrayPos := offset + switch gt(add(arrayPos, mul(, )), end) case 1 { revert(0, 0) } + } + )"; + Whiskers w{templ}; + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); + w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + return w.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ( + R"( + function (offset, end) -> array { + let length := (offset) + array := ((length)) + mstore(array, length) + let src := add(offset, 0x20) + let dst := add(array, 0x20) + switch gt(add(src, length), end) case 1 { revert(0, 0) } + (src, dst, length) + } + )" + ); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) +{ + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + // + function (headStart, end) -> value { + switch slt(sub(end, headStart), ) case 1 { revert(0, 0) } + value := () + <#members> + { + // + + } + + } + )"); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("allocate", allocationFunction()); + solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); + size_t headPos = 0; + vector> members; + for (auto const& member: _type.members(nullptr)) + { + solAssert(member.type, ""); + solAssert(member.type->canLiveOutsideStorage(), ""); + auto decodingType = member.type->decodingType(); + solAssert(decodingType, ""); + bool dynamic = decodingType->isDynamicallyEncoded(); + Whiskers memberTempl(R"( + let offset := )" + string(dynamic ? "(add(headStart, ))" : "" ) + R"( + mstore(add(value, ), (add(headStart, offset), end)) + )"); + memberTempl("load", _fromMemory ? "mload" : "calldataload"); + memberTempl("pos", to_string(headPos)); + memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); + memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); + + members.push_back({}); + members.back()["decode"] = memberTempl.render(); + members.back()["memberName"] = member.name; + headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); + } + templ("members", members); + templ("minimumSize", toCompactHexWithPrefix(headPos)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) +{ + solAssert(_type.kind() == FunctionType::Kind::External, ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : "") + + (_forUseOnStack ? "_onStack" : ""); + + return createFunction(functionName, [&]() { + if (_forUseOnStack) + { + return Whiskers(R"( + function (offset, end) -> addr, function_selector { + addr, function_selector := ((offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("splitExtFun", splitExternalFunctionIdFunction()) + .render(); + } + else + { + return Whiskers(R"( + function (offset, end) -> fun { + fun := ((offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + .render(); + } + }); +} + string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) { string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; @@ -1098,6 +1470,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type) }); } +string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers w(R"( + function (length) -> size { + // Make sure we can allocate memory without overflow + switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + size := + + } + )"); + w("functionName", functionName); + if (_type.isByteArray()) + // Round up + w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); + else + w("allocationSize", "mul(length, 0x20)"); + if (_type.isDynamicallySized()) + w("addLengthSlot", "size := add(size, 0x20)"); + else + w("addLengthSlot", ""); + return w.render(); + }); +} + string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); @@ -1189,6 +1588,24 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) }); } +string ABIFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function (size) -> memPtr { + memPtr := mload() + let newFreePtr := add(memPtr, size) + switch lt(newFreePtr, memPtr) case 1 { revert(0, 0) } + mstore(, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + string ABIFunctions::createFunction(string const& _name, function const& _creator) { if (!m_requestedFunctions.count(_name)) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index e61f68bcc..855b2a116 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -66,6 +66,16 @@ public: bool _encodeAsLibraryTypes = false ); + /// @returns name of an assembly function to ABI-decode values of @a _types + /// into memory. If @a _fromMemory is true, decodes from memory instead of + /// from calldata. + /// Can allocate memory. + /// Inputs: (layout reversed on stack) + /// Outputs: ... + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); + /// @returns concatenation of all generated functions. std::string requestedFunctions(); @@ -87,6 +97,10 @@ private: /// for use in the ABI. std::string combineExternalFunctionIdFunction(); + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -146,6 +160,29 @@ private: bool _fromStack ); + /// @returns the name of the ABI decodinf function for the given type + /// and queues the generation of the function to the requested functions. + /// The caller has to ensure that no out of bounds access (at least to the static + /// part) can happen inside this function. + /// @param _fromMemory if decoding from memory instead of from calldata + /// @param _forUseOnStack if the decoded value is stored on stack or in memory. + std::string abiDecodingFunction( + Type const& _Type, + bool _fromMemory, + bool _forUseOnStack + ); + + /// Part of @a abiDecodingFunction for "regular" array types. + std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata array types. + std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); + /// Part of @a abiDecodingFunction for byte array types. + std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for struct types. + std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for array types. + std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); + /// @returns a function that copies raw bytes of dynamic length from calldata /// or memory to memory. /// Pads with zeros and might write more than exactly length. @@ -158,6 +195,10 @@ private: std::string roundUpFunction(); std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required + /// to store an array in memory given its length (internally encoded, not ABI encoded). + /// The function reverts for too large lengthes. + std::string arrayAllocationSizeFunction(ArrayType const& _type); /// @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 /// which is stored in that slot / memory area. @@ -166,6 +207,12 @@ private: /// Only works for memory arrays and storage arrays that store one item per slot. std::string nextArrayElementFunction(ArrayType const& _type); + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both /// cases. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ce9c3b7f6..35d763f1e 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -318,6 +318,7 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); +// cout << _assembly << endl; auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); auto parserResult = assembly::Parser(errorReporter).parse(scanner); #ifdef SOL_OUTPUT_ASM diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 053bed6a7..533aca5cb 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2( m_context << ret.tag(); } +void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) +{ + // stack: + auto ret = m_context.pushNewTag(); + m_context << Instruction::SWAP1; + if (_fromMemory) + // TODO pass correct size for the memory case + m_context << (u256(1) << 63); + else + m_context << Instruction::CALLDATASIZE; + m_context << Instruction::SWAP1; + string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); + m_context.appendJumpTo(m_context.namedTag(decoderName)); + m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); + m_context << ret.tag(); +} + void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { auto repeat = m_context.newTag(); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ad3989adf..3cde281bf 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -146,6 +146,13 @@ public: bool _encodeAsLibraryTypes = false ); + /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, + /// the data is taken from memory instead of from calldata. + /// Can allocate memory. + /// Stack pre: + /// Stack post: ... + void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); + /// Zero-initialises (the data part of) an already allocated memory array. /// Length has to be nonzero! /// Stack pre: diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 74565ae4b..1ae441659 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -322,6 +322,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // We do not check the calldata size, everything is zero-padded + if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + // Use the new JULIA-based decoding function + auto stackHeightBefore = m_context.stackHeight(); + CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory); + solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, ""); + return; + } + //@todo this does not yet support nested dynamic arrays // Retain the offset pointer as base_offset, the point from which the data offsets are computed. @@ -892,6 +901,7 @@ void ContractCompiler::appendMissingFunctions() } m_context.appendMissingLowLevelFunctions(); string abiFunctions = m_context.abiFunctions().requestedFunctions(); +// cout << abiFunctions << endl; if (!abiFunctions.empty()) m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true); } From 98c38108e8ce01888ee4dbf98a332aa5ba41f722 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Sep 2017 17:16:45 +0200 Subject: [PATCH 2/4] Decoder tests. --- test/ExecutionFramework.h | 16 +- test/boostTest.cpp | 1 + test/libsolidity/ABIDecoderTests.cpp | 790 +++++++++++++++++++++++++++ test/libsolidity/ABIEncoderTests.cpp | 50 +- test/libsolidity/ABITestsCommon.h | 43 ++ 5 files changed, 866 insertions(+), 34 deletions(-) create mode 100644 test/libsolidity/ABIDecoderTests.cpp create mode 100644 test/libsolidity/ABITestsCommon.h diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 2c61c0a64..8aa994738 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -84,12 +84,22 @@ public: return callFallbackWithValue(0); } + bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments) + { + FixedHash<4> hash(dev::keccak256(_sig)); + sendMessage(hash.asBytes() + _arguments, false, _value); + return m_output; + } + + bytes const& callContractFunctionNoEncoding(std::string _sig, bytes const& _arguments) + { + return callContractFunctionWithValueNoEncoding(_sig, 0, _arguments); + } + template bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) { - FixedHash<4> hash(dev::keccak256(_sig)); - sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value); - return m_output; + return callContractFunctionWithValueNoEncoding(_sig, _value, encodeArgs(_arguments...)); } template diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 7b452e061..a3cc51c5b 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -57,6 +57,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (dev::test::Options::get().disableIPC) { for (auto suite: { + "ABIDecoderTest", "ABIEncoderTest", "SolidityAuctionRegistrar", "SolidityFixedFeeRegistrar", diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp new file mode 100644 index 000000000..6a4b57232 --- /dev/null +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -0,0 +1,790 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Unit tests for Solidity's ABI decoder. + */ + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace std::placeholders; +using namespace dev::test; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) + +BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro) +{ + // This tests that the "both decoders macro" at least runs twice and + // modifies the source. + string sourceCode; + int runs = 0; + BOTH_ENCODERS(runs++;) + BOOST_CHECK(sourceCode == NewEncoderPragma); + BOOST_CHECK_EQUAL(runs, 2); +} + +BOOST_AUTO_TEST_CASE(value_types) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool e, C g) public returns (uint) { + if (a != 1) return 1; + if (b != 2) return 2; + if (c != 3) return 3; + if (d != 4) return 4; + if (x != "abc") return 5; + if (e != true) return 6; + if (g != this) return 7; + return 20; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction( + "f(uint256,uint16,uint24,int24,bytes3,bool,address)", + 1, 2, 3, 4, string("abc"), true, u160(m_contractAddress) + ), encodeArgs(u256(20))); + ) +} + +BOOST_AUTO_TEST_CASE(enums) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(E e) public pure returns (uint x) { + assembly { x := e } + } + } + )"; + bool newDecoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint8)", 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint8)", 1), encodeArgs(u256(1))); + // The old decoder was not as strict about enums + ABI_CHECK(callContractFunction("f(uint8)", 2), (newDecoder ? encodeArgs() : encodeArgs(2))); + ABI_CHECK(callContractFunction("f(uint8)", u256(-1)), (newDecoder? encodeArgs() : encodeArgs(u256(0xff)))); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(cleanup) +{ + string sourceCode = R"( + contract C { + function f(uint16 a, int16 b, address c, bytes3 d, bool e) + public pure returns (uint v, uint w, uint x, uint y, uint z) { + assembly { v := a w := b x := c y := d z := e} + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(uint16,int16,address,bytes3,bool)", 1, 2, 3, "a", true), + encodeArgs(u256(1), u256(2), u256(3), string("a"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4) + ), + encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) + ); + ) +} + +BOOST_AUTO_TEST_CASE(fixed_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint16[3] a, uint16[2][3] b, uint i, uint j, uint k) + public pure returns (uint, uint) { + return (a[i], b[j][k]); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 1, 2, 3, + 11, 12, + 21, 22, + 31, 32, + 1, 2, 1 + ); + ABI_CHECK( + callContractFunction("f(uint16[3],uint16[2][3],uint256,uint256,uint256)", args), + encodeArgs(u256(2), u256(32)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[] b, uint c) + public pure returns (uint, uint, uint) { + return (b.length, b[a], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, + 11, 12, 13, 14, 15, 16, 17 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint16[],uint256)", args), + encodeArgs(u256(7), u256(17), u256(9)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_nested_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[][] b, uint[2][][3] c, uint d) + public pure returns (uint, uint, uint, uint, uint, uint, uint) { + return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d); + } + function test() view returns (uint, uint, uint, uint, uint, uint, uint) { + uint16[][] memory b = new uint16[][](3); + b[0] = new uint16[](2); + b[0][0] = 0x55; + b[0][1] = 0x56; + b[1] = new uint16[](4); + b[1][0] = 0x65; + b[1][1] = 0x66; + b[1][2] = 0x67; + b[1][3] = 0x68; + + uint[2][][3] memory c; + c[0] = new uint[2][](1); + c[0][0][1] = 0x75; + c[1] = new uint[2][](5); + c[1][1][1] = 0x85; + + return this.f(0x12, b, c, 0x13); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 0x12, 4 * 0x20, 17 * 0x20, 0x13, + // b + 3, 3 * 0x20, 6 * 0x20, 11 * 0x20, + 2, 85, 86, + 4, 101, 102, 103, 104, + 0, + // c + 3 * 0x20, 6 * 0x20, 17 * 0x20, + 1, 0, 117, + 5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, + 0 + ); + + bytes expectation = encodeArgs(0x12, 3, 4, 0x66, 5, 0x85, 0x13); + ABI_CHECK(callContractFunction("test()"), expectation); + ABI_CHECK(callContractFunction("f(uint256,uint16[][],uint256[2][][3],uint256)", args), expectation); + ) +} + +BOOST_AUTO_TEST_CASE(byte_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, bytes b, uint c) + public pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + + function f_external(uint a, bytes b, uint c) + external pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, "abcdefg" + ); + ABI_CHECK( + callContractFunction("f(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ABI_CHECK( + callContractFunction("f_external(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ) +} + +BOOST_AUTO_TEST_CASE(calldata_arrays_too_large) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint[] b, uint c) external pure returns (uint) { + return 7; + } + } + )"; + bool newEncoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + (u256(1) << 255) + 2, 1, 2 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint256[],uint256)", args), + newEncoder ? encodeArgs() : encodeArgs(7) + ); + newEncoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_simple) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + function C(uint a, uint[] b) { + _a = a; + _b = b; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x40, + // b + 3, 0x21, 0x22, 0x23 + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type) +{ + string sourceCode = R"( + contract D { + function () external returns (uint) public _a; + function D(function () external returns (uint) a) { + _a = a; + } + } + contract C { + function f() returns (uint) { + return 3; + } + function g(function () external returns (uint) _f) returns (uint) { + return _f(); + } + // uses "decode from memory" + function test1() returns (uint) { + D d = new D(this.f); + return d._a()(); + } + // uses "decode from calldata" + function test2() returns (uint) { + return this.g(this.f); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2()"), encodeArgs(3)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type_array) +{ + string sourceCode = R"( + contract D { + function () external returns (uint)[] public _a; + function D(function () external returns (uint)[] a) { + _a = a; + } + } + contract E { + function () external returns (uint)[3] public _a; + function E(function () external returns (uint)[3] a) { + _a = a; + } + } + contract C { + function f1() public returns (uint) { + return 1; + } + function f2() public returns (uint) { + return 2; + } + function f3() public returns (uint) { + return 3; + } + function g(function () external returns (uint)[] _f, uint i) public returns (uint) { + return _f[i](); + } + function h(function () external returns (uint)[3] _f, uint i) public returns (uint) { + return _f[i](); + } + // uses "decode from memory" + function test1_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + D d = new D(x); + return d._a(2)(); + } + function test1_static() public returns (uint) { + E e = new E([this.f1, this.f2, this.f3]); + return e._a(2)(); + } + // uses "decode from calldata" + function test2_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + return this.g(x, 0); + } + function test2_static() public returns (uint) { + return this.h([this.f1, this.f2, this.f3], 0); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1_static()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test1_dynamic()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2_static()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("test2_dynamic()"), encodeArgs(1)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_complex) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + bytes[2] public _c; + function C(uint a, uint[] b, bytes[2] c) { + _a = a; + _b = b; + _c = c; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x60, 7 * 0x20, + // b + 3, 0x21, 0x22, 0x23, + // c + 0x40, 0x80, + 8, string("abcdefgh"), + 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ") + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ABI_CHECK(callContractFunction("_c(uint256)", 0), encodeArgs(0x20, 8, string("abcdefgh"))); + ABI_CHECK(callContractFunction("_c(uint256)", 1), encodeArgs(0x20, 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"))); + ABI_CHECK(callContractFunction("_c(uint256)", 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_value_type) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint b) public pure returns (uint) { return a; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_array) +{ + string sourceCode = R"( + contract C { + function f(uint[] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7)); + ) +} + +BOOST_AUTO_TEST_CASE(short_dynamic_input_array) +{ + string sourceCode = R"( + contract C { + function f(bytes[1] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[1])", encodeArgs(0x20)), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_bytes) +{ + string sourceCode = R"( + contract C { + function e(bytes a) public pure returns (uint) { return 7; } + function f(bytes[] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ) +} + +BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(uint16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function g(int16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function h(E[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256(-1))); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs(u256("0x0fff"))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 1), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(storage_ptr) +{ + string sourceCode = R"( + library L { + struct S { uint x; uint y; } + function f(uint[] storage r, S storage s) public returns (uint, uint, uint, uint) { + r[2] = 8; + s.x = 7; + return (r[0], r[1], s.x, s.y); + } + } + contract C { + uint8 x = 3; + L.S s; + uint[] r; + function f() public returns (uint, uint, uint, uint, uint, uint) { + r.length = 6; + r[0] = 1; + r[1] = 2; + r[2] = 3; + s.x = 11; + s.y = 12; + var (a, b, c, d) = L.f(r, s); + return (r[2], s.x, a, b, c, d); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "L"); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"L", m_contractAddress}}); + ABI_CHECK(callContractFunction("f()"), encodeArgs(8, 7, 1, 2, 7, 12)); + ) +} + +BOOST_AUTO_TEST_CASE(struct_simple) +{ + string sourceCode = R"( + contract C { + struct S { uint a; uint8 b; uint8 c; bytes2 d; } + function f(S s) public pure returns (uint a, uint b, uint c, uint d) { + a = s.a; + b = s.b; + c = s.c; + d = uint(s.d); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f((uint256,uint8,uint8,bytes2))", 1, 2, 3, "ab"), encodeArgs(1, 2, 3, 'a' * 0x100 + 'b')); + ) +} + +BOOST_AUTO_TEST_CASE(struct_cleanup) +{ + string sourceCode = R"( + contract C { + struct S { int16 a; uint8 b; bytes2 c; } + function f(S s) public pure returns (uint a, uint b, uint c) { + assembly { + a := mload(s) + b := mload(add(s, 0x20)) + c := mload(add(s, 0x40)) + } + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff0002, "abcd"), + encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010"), 2, "ab") + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_short) +{ + string sourceCode = R"( + contract C { + struct S { int a; uint b; bytes16 c; } + function f(S s) public pure returns (S q) { + q = s; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int256,uint256,bytes16))", 0xff010, 0xff0002, "abcd"), + encodeArgs(0xff010, 0xff0002, "abcd") + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(32, 0)), + encodeArgs(0xff010, 0xff0002, 0) + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(31, 0)), + encodeArgs() + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_function) +{ + string sourceCode = R"( + contract C { + struct S { function () external returns (uint) f; uint b; } + function f(S s) public returns (uint, uint) { + return (s.f(), s.b); + } + function test() public returns (uint, uint) { + return this.f(S(this.g, 3)); + } + function g() public returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs(7, 3)); + ) +} + +BOOST_AUTO_TEST_CASE(empty_struct) +{ + string sourceCode = R"( + contract C { + struct S { } + function f(uint a, S s, uint b) public pure returns (uint x, uint y) { + assembly { x := a y := b } + } + function g() public returns (uint, uint) { + return this.f(7, S(), 8); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256,(),uint256)", 7, 8), encodeArgs(7, 8)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(7, 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address)[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, u256(u160(m_contractAddress)), 0, 8 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre2_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; uint[] x; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,uint256[])[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, 0x60, 8, + 0x40, 7 * 0x20, + u256(u160(m_contractAddress)), 0x40, + 2, 0x11, 0x12, + 0x99, 0x40, + 4, 0x31, 0x32, 0x34, 0x35 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(complex_struct) +{ + string sourceCode = R"( + contract C { + enum E {A, B, C} + struct T { uint x; E e; uint8 y; } + struct S { C c; T[] t;} + function f(uint a, S[2] s1, S[] s2, uint b) public returns + (uint r1, C r2, uint r3, uint r4, C r5, uint r6, E r7, uint8 r8) { + r1 = a; + r2 = s1[0].c; + r3 = b; + r4 = s2.length; + r5 = s2[1].c; + r6 = s2[1].t.length; + r7 = s2[1].t[1].e; + r8 = s2[1].t[1].y; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,(uint256,uint8,uint8)[])[2],(address,(uint256,uint8,uint8)[])[],uint256)"; + bytes args = encodeArgs( + 7, 0x80, 0x1e0, 8, + // S[2] s1 + 0x40, + 0x100, + // S s1[0] + u256(u160(m_contractAddress)), + 0x40, + // T s1[0].t + 1, // length + // s1[0].t[0] + 0x11, 1, 0x12, + // S s1[1] + 0, 0x40, + // T s1[1].t + 0, + // S[] s2 (0x1e0) + 2, // length + 0x40, 0xa0, + // S s2[0] + 0, 0x40, 0, + // S s2[1] + 0x1234, 0x40, + // s2[1].t + 3, // length + 0, 0, 0, + 0x21, 2, 0x22, + 0, 0, 0 + ); + ABI_CHECK(callContractFunction(sig, args), encodeArgs(7, u256(u160(m_contractAddress)), 8, 2, 0x1234, 3, 2, 0x22)); + // invalid enum value + args.data()[0x20 * 28] = 3; + ABI_CHECK(callContractFunction(sig, args), encodeArgs()); + ) +} + + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index af51edcc6..49db9ce1c 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -25,6 +25,8 @@ #include #include +#include + using namespace std; using namespace std::placeholders; using namespace dev::test; @@ -42,20 +44,6 @@ namespace test BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \ } while (false) -static string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; - -#define NEW_ENCODER(CODE) \ -{ \ - sourceCode = NewEncoderPragma + sourceCode; \ - { CODE } \ -} - -#define BOTH_ENCODERS(CODE) \ -{ \ - { CODE } \ - NEW_ENCODER(CODE) \ -} - BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework) BOOST_AUTO_TEST_CASE(both_encoders_macro) @@ -74,7 +62,7 @@ BOOST_AUTO_TEST_CASE(value_types) string sourceCode = R"( contract C { event E(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool, C); - function f() { + function f() public { bytes6 x = hex"1bababababa2"; bool b; assembly { b := 7 } @@ -98,7 +86,7 @@ BOOST_AUTO_TEST_CASE(string_literal) string sourceCode = R"( contract C { event E(string, bytes20, string); - function f() { + function f() public { E("abcdef", "abcde", "abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl"); } } @@ -120,7 +108,7 @@ BOOST_AUTO_TEST_CASE(enum_type_cleanup) string sourceCode = R"( contract C { enum E { A, B } - function f(uint x) returns (E en) { + function f(uint x) public returns (E en) { assembly { en := x } } } @@ -138,7 +126,7 @@ BOOST_AUTO_TEST_CASE(conversion) string sourceCode = R"( contract C { event E(bytes4, bytes4, uint16, uint8, int16, int8); - function f() { + function f() public { bytes2 x; assembly { x := 0xf1f2f3f400000000000000000000000000000000000000000000000000000000 } uint8 a; uint16 b = 0x1ff; @@ -164,7 +152,7 @@ BOOST_AUTO_TEST_CASE(memory_array_one_dim) string sourceCode = R"( contract C { event E(uint a, int16[] b, uint c); - function f() { + function f() public { int16[] memory x = new int16[](3); assembly { for { let i := 0 } lt(i, 3) { i := add(i, 1) } { @@ -191,7 +179,7 @@ BOOST_AUTO_TEST_CASE(memory_array_two_dim) string sourceCode = R"( contract C { event E(uint a, int16[][2] b, uint c); - function f() { + function f() public { int16[][2] memory x; x[0] = new int16[](3); x[1] = new int16[](2); @@ -216,7 +204,7 @@ BOOST_AUTO_TEST_CASE(memory_byte_array) string sourceCode = R"( contract C { event E(uint a, bytes[] b, uint c); - function f() { + function f() public { bytes[] memory x = new bytes[](2); x[0] = "abcabcdefghjklmnopqrsuvwabcdefgijklmnopqrstuwabcdefgijklmnoprstuvw"; x[1] = "abcdefghijklmnopqrtuvwabcfghijklmnopqstuvwabcdeghijklmopqrstuvw"; @@ -243,7 +231,7 @@ BOOST_AUTO_TEST_CASE(storage_byte_array) bytes short; bytes long; event E(bytes s, bytes l); - function f() { + function f() public { short = "123456789012345678901234567890a"; long = "ffff123456789012345678901234567890afffffffff123456789012345678901234567890a"; E(short, long); @@ -267,7 +255,7 @@ BOOST_AUTO_TEST_CASE(storage_array) contract C { address[3] addr; event E(address[3] a); - function f() { + function f() public { assembly { sstore(0, sub(0, 1)) sstore(1, sub(0, 2)) @@ -290,7 +278,7 @@ BOOST_AUTO_TEST_CASE(storage_array_dyn) contract C { address[] addr; event E(address[] a); - function f() { + function f() public { addr.push(1); addr.push(2); addr.push(3); @@ -311,7 +299,7 @@ BOOST_AUTO_TEST_CASE(storage_array_compact) contract C { int72[] x; event E(int72[]); - function f() { + function f() public { x.push(-1); x.push(2); x.push(-3); @@ -339,7 +327,7 @@ BOOST_AUTO_TEST_CASE(external_function) contract C { event E(function(uint) external returns (uint), function(uint) external returns (uint)); function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { g = this.f; E(this.f, g); } @@ -347,7 +335,7 @@ BOOST_AUTO_TEST_CASE(external_function) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); string functionIdF = asString(m_contractAddress.ref()) + asString(FixedHash<4>(dev::keccak256("f(uint256)")).ref()); REQUIRE_LOG_DATA(encodeArgs(functionIdF, functionIdF)); ) @@ -360,7 +348,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) event E(function(uint) external returns (uint), function(uint) external returns (uint)); // This test relies on the fact that g is stored in slot zero. function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { function(uint) external returns (uint)[1] memory h; assembly { sstore(0, sub(0, 1)) mstore(h, sub(0, 1)) } E(h[0], g); @@ -369,7 +357,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); REQUIRE_LOG_DATA(encodeArgs(string(24, char(-1)), string(24, char(-1)))); ) } @@ -404,7 +392,7 @@ BOOST_AUTO_TEST_CASE(function_name_collision) // and by the ABI encoder string sourceCode = R"( contract C { - function f(uint x) returns (uint) { + function f(uint x) public returns (uint) { assembly { function abi_encode_t_uint256_to_t_uint256() { mstore(0, 7) @@ -432,7 +420,7 @@ BOOST_AUTO_TEST_CASE(structs) struct T { uint64[2] x; } S s; event e(uint16, S); - function f() returns (uint, S) { + function f() public returns (uint, S) { uint16 x = 7; s.a = 8; s.b = 9; diff --git a/test/libsolidity/ABITestsCommon.h b/test/libsolidity/ABITestsCommon.h new file mode 100644 index 000000000..2ef555f3b --- /dev/null +++ b/test/libsolidity/ABITestsCommon.h @@ -0,0 +1,43 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +static std::string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; + +#define NEW_ENCODER(CODE) \ +{ \ + sourceCode = NewEncoderPragma + sourceCode; \ + { CODE } \ +} + +#define BOTH_ENCODERS(CODE) \ +{ \ + { CODE } \ + NEW_ENCODER(CODE) \ +} + +} +} +} // end namespaces From 5a3dbb0269b3ff6b443a3cb4ccfc4f00eaba26b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 13 Oct 2017 17:29:09 +0200 Subject: [PATCH 3/4] Cleanup and overflow checks for data pointers. --- libsolidity/codegen/ABIFunctions.cpp | 64 ++++++++++++++++-------- libsolidity/codegen/ABIFunctions.h | 4 +- libsolidity/codegen/CompilerContext.cpp | 1 - libsolidity/codegen/ContractCompiler.cpp | 1 - test/libsolidity/ABIDecoderTests.cpp | 2 +- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index c9a9ff574..6648be066 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -111,9 +111,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) if (_fromMemory) functionName += "_fromMemory"; - return createFunction(functionName, [&]() { - solAssert(!_types.empty(), ""); + solAssert(!_types.empty(), ""); + return createFunction(functionName, [&]() { TypePointers decodingTypes; for (auto const& t: _types) decodingTypes.emplace_back(t->decodingType()); @@ -146,16 +146,22 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) stackPos++; } bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl(R"( + Whiskers elementTempl( + dynamic ? + R"( { - let offset := )" + string( - dynamic ? - "(add(headStart, ))" : - "" - ) + R"( + let offset := (add(headStart, )) + switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } := (add(headStart, offset), dataEnd) } - )"); + )" : + R"( + { + let offset := + := (add(headStart, offset), dataEnd) + } + )" + ); elementTempl("load", _fromMemory ? "mload" : "calldataload"); elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); elementTempl("pos", to_string(headPos)); @@ -1053,6 +1059,10 @@ string ABIFunctions::abiEncodingFunctionFunctionType( string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) { + // The decoding function has to perform bounds checks unless it decodes a value type. + // Conversely, bounds checks have to be performed before the decoding function + // of a value type is called. + TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); @@ -1072,7 +1082,14 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo return abiDecodingFunctionStruct(*structType, _fromMemory); else if (auto const* functionType = dynamic_cast(decodingType.get())) return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + else + return abiDecodingFunctionValueType(_type, _fromMemory); +} +string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); solAssert(decodingType->sizeOnStack() == 1, ""); solAssert(decodingType->isValueType(), ""); solAssert(decodingType->calldataEncodedSize() == 32, ""); @@ -1095,6 +1112,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo templ("cleanup", cleanupFunction(_type, true)); return templ.render(); }); + } string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) @@ -1116,6 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from R"( // function (offset, end) -> array { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } let length := array := ((length)) let dst := array @@ -1125,7 +1144,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from for { let i := 0 } lt(i, length) { i := add(i, 1) } { let elementPos := - mstore(dst, (elementPos, end)) dst := add(dst, 0x20) src := add(src, ) @@ -1145,10 +1163,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from if (dynamicBase) { templ("staticBoundsCheck", ""); - // The dynamic bounds check might not be needed (because we have an additional check - // one level deeper), but we keep it in just in case. This at least prevents - // the part one level deeper from reading the length from an out of bounds position. - templ("dynamicBoundsCheck", "switch gt(elementPos, end) case 1 { revert(0, 0) }"); templ("retrieveElementPos", "add(offset, " + load + "(src))"); templ("baseEncodedSize", "0x20"); } @@ -1156,7 +1170,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from { string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }"); - templ("dynamicBoundsCheck", ""); templ("retrieveElementPos", "src"); templ("baseEncodedSize", baseEncodedSize); } @@ -1184,6 +1197,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) templ = R"( // function (offset, end) -> arrayPos, length { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } length := calldataload(offset) switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } arrayPos := add(offset, 0x20) @@ -1221,6 +1235,7 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ Whiskers templ( R"( function (offset, end) -> array { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } let length := (offset) array := ((length)) mstore(array, length) @@ -1277,10 +1292,18 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr auto decodingType = member.type->decodingType(); solAssert(decodingType, ""); bool dynamic = decodingType->isDynamicallyEncoded(); - Whiskers memberTempl(R"( - let offset := )" + string(dynamic ? "(add(headStart, ))" : "" ) + R"( - mstore(add(value, ), (add(headStart, offset), end)) - )"); + Whiskers memberTempl( + dynamic ? + R"( + let offset := (add(headStart, )) + switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } + mstore(add(value, ), (add(headStart, offset), end)) + )" : + R"( + let offset := + mstore(add(value, ), (add(headStart, offset), end)) + )" + ); memberTempl("load", _fromMemory ? "mload" : "calldataload"); memberTempl("pos", to_string(headPos)); memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); @@ -1596,7 +1619,8 @@ string ABIFunctions::allocationFunction() function (size) -> memPtr { memPtr := mload() let newFreePtr := add(memPtr, size) - switch lt(newFreePtr, memPtr) case 1 { revert(0, 0) } + // protect against overflow + switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) } mstore(, newFreePtr) } )") diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 855b2a116..2b582e848 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -160,7 +160,7 @@ private: bool _fromStack ); - /// @returns the name of the ABI decodinf function for the given type + /// @returns the name of the ABI decoding function for the given type /// and queues the generation of the function to the requested functions. /// The caller has to ensure that no out of bounds access (at least to the static /// part) can happen inside this function. @@ -172,6 +172,8 @@ private: bool _forUseOnStack ); + /// Part of @a abiDecodingFunction for value types. + std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory); /// Part of @a abiDecodingFunction for "regular" array types. std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); /// Part of @a abiDecodingFunction for calldata array types. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 35d763f1e..ce9c3b7f6 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -318,7 +318,6 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); -// cout << _assembly << endl; auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); auto parserResult = assembly::Parser(errorReporter).parse(scanner); #ifdef SOL_OUTPUT_ASM diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 1ae441659..a81ba518c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -901,7 +901,6 @@ void ContractCompiler::appendMissingFunctions() } m_context.appendMissingLowLevelFunctions(); string abiFunctions = m_context.abiFunctions().requestedFunctions(); -// cout << abiFunctions << endl; if (!abiFunctions.empty()) m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true); } diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp index 6a4b57232..c0c017e4c 100644 --- a/test/libsolidity/ABIDecoderTests.cpp +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -40,7 +40,7 @@ namespace test BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) -BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro) +BOOST_AUTO_TEST_CASE(both_encoders_macro) { // This tests that the "both decoders macro" at least runs twice and // modifies the source. From 9d8e3ff395006c0c6285f70c13bd470e9374bda3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Nov 2017 19:14:35 +0100 Subject: [PATCH 4/4] Also test short input for old decoder. --- test/libsolidity/ABIDecoderTests.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp index c0c017e4c..15c04b373 100644 --- a/test/libsolidity/ABIDecoderTests.cpp +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -449,11 +449,13 @@ BOOST_AUTO_TEST_CASE(short_input_value_type) function f(uint a, uint b) public pure returns (uint) { return a; } } )"; - NEW_ENCODER( + bool newDecoder = false; + BOTH_ENCODERS( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), newDecoder ? encodeArgs() : encodeArgs(0)); + newDecoder = true; ) } @@ -464,13 +466,15 @@ BOOST_AUTO_TEST_CASE(short_input_array) function f(uint[] a) public pure returns (uint) { return 7; } } )"; - NEW_ENCODER( + bool newDecoder = false; + BOTH_ENCODERS( compileAndRun(sourceCode); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7)); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), encodeArgs()); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), newDecoder ? encodeArgs() : encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), newDecoder ? encodeArgs() : encodeArgs(7)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7)); + newDecoder = true; ) }