diff options
18 files changed, 384 insertions, 8 deletions
diff --git a/Changelog.md b/Changelog.md index 4fcaad4d..177a071b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -77,6 +77,7 @@ Language Features: * General: Allow ``enum``s in interfaces. * General: Allow ``mapping`` storage pointers as arguments and return values in all internal functions. * General: Allow ``struct``s in interfaces. + * General: Provide access to the ABI decoder through ``abi.decode(bytes memory data, (...))``. Compiler Features: * C API (``libsolc``): Export the ``solidity_license``, ``solidity_version`` and ``solidity_compile`` methods. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index c0c7cb1b..e8631224 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -318,6 +318,7 @@ The following is the order of precedence for operators, listed in order of evalu Global Variables ================ +- ``abi.decode(bytes encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes the provided data. The types are given in parentheses as second argument. Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` - ``abi.encode(...) returns (bytes)``: :ref:`ABI <ABI>`-encodes the given arguments - ``abi.encodePacked(...) returns (bytes)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments - ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)``: :ref:`ABI <ABI>`-encodes the given arguments diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 6eae2804..ceabee4e 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -96,9 +96,10 @@ Block and Transaction Properties .. index:: abi, encoding, packed -ABI Encoding Functions ----------------------- +ABI Encoding and Decoding Functions +----------------------------------- +- ``abi.decode(bytes encodedData, (...)) returns (...)``: ABI-decodes the given data, while the types are given in parentheses as second argument. Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` - ``abi.encode(...) returns (bytes)``: ABI-encodes the given arguments - ``abi.encodePacked(...) returns (bytes)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments - ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)``: ABI-encodes the given arguments starting from the second and prepends the given four-byte selector diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 8b609a31..43e894e5 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -525,6 +525,75 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) ); } +TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2) +{ + vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); + if (arguments.size() != 2) + m_errorReporter.typeError( + _functionCall.location(), + "This function takes two arguments, but " + + toString(arguments.size()) + + " were provided." + ); + if (arguments.size() >= 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory))) + m_errorReporter.typeError( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*arguments.front())->toString() + + " to bytes memory requested." + ); + + TypePointer returnType = make_shared<TupleType>(); + + if (arguments.size() < 2) + return returnType; + + // The following is a rather syntactic restriction, but we check it here anyway: + // The second argument has to be a tuple expression containing type names. + TupleExpression const* tupleExpression = dynamic_cast<TupleExpression const*>(arguments[1].get()); + if (!tupleExpression) + { + m_errorReporter.typeError( + arguments[1]->location(), + "The second argument to \"abi.decode\" has to be a tuple of types." + ); + return returnType; + } + + vector<TypePointer> components; + for (auto const& typeArgument: tupleExpression->components()) + { + solAssert(typeArgument, ""); + if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument).get())) + { + TypePointer actualType = argTypeType->actualType(); + solAssert(actualType, ""); + // We force memory because the parser currently cannot handle + // data locations. Furthermore, storage can be a little dangerous and + // calldata is not really implemented anyway. + actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType); + solAssert( + !actualType->dataStoredIn(DataLocation::CallData) && + !actualType->dataStoredIn(DataLocation::Storage), + "" + ); + if (!actualType->fullEncodingType(false, _abiEncoderV2, false)) + m_errorReporter.typeError( + typeArgument->location(), + "Decoding type " + actualType->toString(false) + " not supported." + ); + components.push_back(actualType); + } + else + { + m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name."); + components.push_back(make_shared<TupleType>()); + } + } + return make_shared<TupleType>(components); +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); @@ -1727,7 +1796,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } } - if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size()) + bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2); + + if (functionType->kind() == FunctionType::Kind::ABIDecode) + _functionCall.annotation().type = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2); + else if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size()) { solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, ""); m_errorReporter.typeError( @@ -1782,8 +1855,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } else if (isPositionalCall) { - bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2); - for (size_t i = 0; i < arguments.size(); ++i) { auto const& argType = type(*arguments[i]); @@ -1796,7 +1867,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); errored = true; } - if (!errored && !argType->fullEncodingType(false, abiEncodeV2, !functionType->padArguments())) + if (!errored && !argType->fullEncodingType(false, abiEncoderV2, !functionType->padArguments())) m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded."); } else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index ed316f9c..4be0d1e4 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -91,6 +91,11 @@ private: // and reports an error, if not. void checkExpressionAssignment(Type const& _type, Expression const& _expression); + /// Performs type checks for ``abi.decode(bytes memory, (...))`` and returns the + /// return type (which is basically the second argument) if successful. It returns + /// the empty tuple type or error. + TypePointer typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2); + virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; virtual bool visit(StructDefinition const& _struct) override; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index d936ada0..e92ad2fa 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -295,7 +295,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) { // we can ignore the kind of magic and only look at the name of the member set<string> static const pureMembers{ - "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash" + "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode", "data", "sig", "blockhash" }; if (!pureMembers.count(member)) mutability = StateMutability::View; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 68c201a8..c9dca126 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2553,6 +2553,7 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodePacked: id += "abiencodepacked"; break; case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; + case Kind::ABIDecode: id += "abidecode"; break; default: solAssert(false, "Unknown function location."); break; } id += "_" + stateMutabilityToString(m_stateMutability); @@ -2959,7 +2960,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIEncode || m_kind == Kind::ABIEncodePacked || m_kind == Kind::ABIEncodeWithSelector || - m_kind == Kind::ABIEncodeWithSignature; + m_kind == Kind::ABIEncodeWithSignature || + m_kind == Kind::ABIDecode; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -3315,6 +3317,15 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const FunctionType::Kind::ABIEncodeWithSignature, true, StateMutability::Pure + )}, + {"decode", make_shared<FunctionType>( + TypePointers(), + TypePointers(), + strings{}, + strings{}, + FunctionType::Kind::ABIDecode, + true, + StateMutability::Pure )} }); default: diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 34f862c3..d8e73ab9 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -934,6 +934,7 @@ public: ABIEncodePacked, ABIEncodeWithSelector, ABIEncodeWithSignature, + ABIDecode, GasLeft ///< gasleft() }; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 412a7255..7a4548f5 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1070,6 +1070,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack now: <memory pointer> break; } + case FunctionType::Kind::ABIDecode: + { + arguments.front()->accept(*this); + TypePointer firstArgType = arguments.front()->annotation().type; + TypePointers const& targetTypes = dynamic_cast<TupleType const&>(*_functionCall.annotation().type).components(); + if ( + *firstArgType == ArrayType(DataLocation::CallData) || + *firstArgType == ArrayType(DataLocation::CallData, true) + ) + utils().abiDecode(targetTypes, false); + else + { + utils().convertType(*firstArgType, ArrayType(DataLocation::Memory)); + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + m_context << Instruction::SWAP1 << Instruction::MLOAD; + // stack now: <mem_pos> <length> + + utils().abiDecode(targetTypes, true); + } + break; + } case FunctionType::Kind::GasLeft: m_context << Instruction::GAS; break; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index af2b3485..8a334e5e 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -13195,6 +13195,203 @@ BOOST_AUTO_TEST_CASE(senders_balance) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(27))); } +BOOST_AUTO_TEST_CASE(abi_decode_trivial) +{ + char const* sourceCode = R"( + contract C { + function f(bytes memory data) public pure returns (uint) { + return abi.decode(data, (uint)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(bytes)", 0x20, 0x20, 33), encodeArgs(u256(33))); +} + +BOOST_AUTO_TEST_CASE(abi_encode_decode_simple) +{ + char const* sourceCode = R"XX( + contract C { + function f() public pure returns (uint, bytes memory) { + bytes memory arg = "abcdefg"; + return abi.decode(abi.encode(uint(33), arg), (uint, bytes)); + } + } + )XX"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f()"), + encodeArgs(33, 0x40, 7, "abcdefg") + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_simple) +{ + char const* sourceCode = R"( + contract C { + function f(bytes memory data) public pure returns (uint, bytes memory) { + return abi.decode(data, (uint, bytes)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 0x20 * 4, 33, 0x40, 7, "abcdefg"), + encodeArgs(33, 0x40, 7, "abcdefg") + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_v2) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + struct S { uint a; uint[] b; } + function f() public pure returns (S memory) { + S memory s; + s.a = 8; + s.b = new uint[](3); + s.b[0] = 9; + s.b[1] = 10; + s.b[2] = 11; + return abi.decode(abi.encode(s), (S)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f()"), + encodeArgs(0x20, 8, 0x40, 3, 9, 10, 11) + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_simple_storage) +{ + char const* sourceCode = R"( + contract C { + bytes data; + function f(bytes memory _data) public returns (uint, bytes memory) { + data = _data; + return abi.decode(data, (uint, bytes)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 0x20 * 4, 33, 0x40, 7, "abcdefg"), + encodeArgs(33, 0x40, 7, "abcdefg") + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_v2_storage) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + bytes data; + struct S { uint a; uint[] b; } + function f() public returns (S memory) { + S memory s; + s.a = 8; + s.b = new uint[](3); + s.b[0] = 9; + s.b[1] = 10; + s.b[2] = 11; + data = abi.encode(s); + return abi.decode(data, (S)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f()"), + encodeArgs(0x20, 8, 0x40, 3, 9, 10, 11) + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_calldata) +{ + char const* sourceCode = R"( + contract C { + function f(bytes calldata data) external pure returns (uint, bytes memory r) { + return abi.decode(data, (uint, bytes)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 0x20 * 4, 33, 0x40, 7, "abcdefg"), + encodeArgs(33, 0x40, 7, "abcdefg") + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_v2_calldata) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + struct S { uint a; uint[] b; } + function f(bytes calldata data) external pure returns (S memory) { + return abi.decode(data, (S)); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 0x20 * 7, 0x20, 33, 0x40, 3, 10, 11, 12), + encodeArgs(0x20, 33, 0x40, 3, 10, 11, 12) + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_static_array) +{ + char const* sourceCode = R"( + contract C { + function f(bytes calldata data) external pure returns (uint[2][3] memory) { + return abi.decode(data, (uint[2][3])); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 6 * 0x20, 1, 2, 3, 4, 5, 6), + encodeArgs(1, 2, 3, 4, 5, 6) + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_static_array_v2) +{ + char const* sourceCode = R"( + pragma experimental ABIEncoderV2; + contract C { + function f(bytes calldata data) external pure returns (uint[2][3] memory) { + return abi.decode(data, (uint[2][3])); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 6 * 0x20, 1, 2, 3, 4, 5, 6), + encodeArgs(1, 2, 3, 4, 5, 6) + ); +} + +BOOST_AUTO_TEST_CASE(abi_decode_dynamic_array) +{ + char const* sourceCode = R"( + contract C { + function f(bytes calldata data) external pure returns (uint[] memory) { + return abi.decode(data, (uint[])); + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(bytes)", 0x20, 6 * 0x20, 0x20, 4, 3, 4, 5, 6), + encodeArgs(0x20, 4, 3, 4, 5, 6) + ); +} + BOOST_AUTO_TEST_CASE(write_storage_external) { char const* sourceCode = R"( diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_calldata.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_calldata.sol new file mode 100644 index 00000000..28d2f2e7 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_calldata.sol @@ -0,0 +1,8 @@ +// This restriction might be lifted in the future +contract C { + function f() public pure { + abi.decode("abc", (bytes calldata)); + } +} +// ---- +// ParserError: (121-129): Expected ',' but got 'calldata' diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_count.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_count.sol new file mode 100644 index 00000000..f903e1ed --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_invalid_arg_count.sol @@ -0,0 +1,12 @@ +contract C { + function f() public pure { + abi.decode(); + abi.decode(msg.data); + abi.decode(msg.data, uint, uint); + } +} +// ---- +// TypeError: (46-58): This function takes two arguments, but 0 were provided. +// TypeError: (64-84): This function takes two arguments, but 1 were provided. +// TypeError: (90-122): This function takes two arguments, but 3 were provided. +// TypeError: (111-115): The second argument to "abi.decode" has to be a tuple of types. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory.sol new file mode 100644 index 00000000..e4667e34 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + abi.decode("abc", (bytes memory, uint[][2] memory)); + } +} +// ---- +// ParserError: (71-77): Expected ',' but got 'memory' diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory_v2.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory_v2.sol new file mode 100644 index 00000000..8a7462d1 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_memory_v2.sol @@ -0,0 +1,10 @@ +pragma experimental "ABIEncoderV2"; + +contract C { + struct S { uint x; uint[] b; } + function f() public pure returns (S memory, bytes memory, uint[][2] memory) { + return abi.decode("abc", (S, bytes, uint[][2])); + } +} +// ---- +// Warning: (0-35): Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nontuple.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nontuple.sol new file mode 100644 index 00000000..d813c712 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nontuple.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + abi.decode("abc", uint); + abi.decode("abc", this); + abi.decode("abc", f()); + } +} +// ---- +// TypeError: (64-68): The second argument to "abi.decode" has to be a tuple of types. +// TypeError: (93-97): The second argument to "abi.decode" has to be a tuple of types. +// TypeError: (122-125): The second argument to "abi.decode" has to be a tuple of types. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_simple.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_simple.sol new file mode 100644 index 00000000..356ee91c --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_simple.sol @@ -0,0 +1,5 @@ +contract C { + function f() public pure returns (uint, bytes32, C) { + return abi.decode("abc", (uint, bytes32, C)); + } +} diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_singletontuple.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_singletontuple.sol new file mode 100644 index 00000000..57eccacf --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_singletontuple.sol @@ -0,0 +1,6 @@ +contract C { + function f() public pure returns (uint) { + return abi.decode("abc", (uint)); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_storage.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_storage.sol new file mode 100644 index 00000000..d9910b64 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_storage.sol @@ -0,0 +1,8 @@ +// This restriction might be lifted in the future +contract C { + function f() { + abi.decode("abc", (bytes storage)); + } +} +// ---- +// ParserError: (109-116): Expected ',' but got 'storage' |