diff --git a/docs/cheatsheet.rst b/docs/cheatsheet.rst index 34ad46df6..cba123c77 100644 --- a/docs/cheatsheet.rst +++ b/docs/cheatsheet.rst @@ -119,6 +119,7 @@ Type Information - ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. - ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information`. - ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information`. +- ``type(S).typehash`` (``bytes32``): the typehash of the given struct type ``S``, see :ref:`Type Information`. - ``type(T).min`` (``T``): the minimum value representable by the integer type ``T``, see :ref:`Type Information`. - ``type(T).max`` (``T``): the maximum value representable by the integer type ``T``, see :ref:`Type Information`. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index e70299679..d11458dff 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -385,6 +385,13 @@ for an interface type ``I``: interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all function selectors defined within the interface itself - excluding all inherited functions. +The following properties are available for an struct type ``S``: + +``type(S).typehash``: + A ``bytes32`` value containing the `EIP-712 `_ + typehash of the given structure ``S``. This identifier is defined as ``keccak256`` of + structure name and all the fields with their types, wrapped in braces and separated by commas. + The following properties are available for an integer type ``T``: ``type(T).min`` diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index e7721fd9e..a239df58c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3391,6 +3391,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) annotation.isPure = true; else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "typehash") + annotation.isPure = true; else if ( magicType->kind() == MagicType::Kind::MetaType && (memberName == "min" || memberName == "max") diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index c370f8f9f..608ef3c9f 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -398,6 +398,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::MetaType, "runtimeCode"}, {MagicType::Kind::MetaType, "name"}, {MagicType::Kind::MetaType, "interfaceId"}, + {MagicType::Kind::MetaType, "typehash"}, {MagicType::Kind::MetaType, "min"}, {MagicType::Kind::MetaType, "max"}, }; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 833ba739e..d75c0d63d 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -389,6 +389,17 @@ std::vector, std::optional>> UsingFo return ranges::zip_view(m_functionsOrLibrary, m_operators) | ranges::to; } +util::h256 StructDefinition::typehash() const +{ + std::string str = name() + "("; + for (size_t i = 0; i < m_members.size(); i++) + { + str += i == 0 ? "" : ","; + str += m_members[i]->type()->canonicalName() + " " + m_members[i]->name(); + } + return util::keccak256(str + ")"); +} + Type const* StructDefinition::type() const { solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker."); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9d754202d..88171361d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -741,6 +741,9 @@ public: std::vector> const& members() const { return m_members; } + /// @returns the EIP-712 compatible typehash of this struct. + util::h256 typehash() const; + Type const* type() const override; bool isVisibleInDerivedContracts() const override { return true; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6308dbe3d..facbb35d3 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -4213,6 +4213,13 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"name", TypeProvider::stringMemory()}, }); } + else if (m_typeArgument->category() == Type::Category::Struct) + { + StructType const* structTypePointer = dynamic_cast(m_typeArgument); + return MemberList::MemberMap({ + {"typehash", structTypePointer}, + }); + } else if (m_typeArgument->category() == Type::Category::Integer) { IntegerType const* integerTypePointer = dynamic_cast(m_typeArgument); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 25bd15d5a..10311933f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1931,6 +1931,12 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); m_context << (u256{contract.interfaceId()} << (256 - 32)); } + else if (member == "typehash") + { + Type const* arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + StructDefinition const& struct_ = dynamic_cast(*arg).structDefinition(); + m_context << struct_.typehash(); + } else if (member == "min" || member == "max") { MagicType const* arg = dynamic_cast(_memberAccess.expression().annotation().type); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 509cc28c7..2cbecadd3 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1941,6 +1941,13 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) ContractDefinition const& contract = contractType.contractDefinition(); define(_memberAccess) << formatNumber(u256{contract.interfaceId()} << (256 - 32)) << "\n"; } + else if (member == "typehash") + { + Type const* arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + auto const& structType = dynamic_cast(*arg); + StructDefinition const& struct_ = structType.structDefinition(); + define(_memberAccess) << formatNumber(struct_.typehash()) << "\n"; + } else if (member == "min" || member == "max") { MagicType const* arg = dynamic_cast(_memberAccess.expression().annotation().type); diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index a328a7781..c0b73c34c 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -1409,6 +1409,11 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess) ContractDefinition const& contract = dynamic_cast(*magicType->typeArgument()).contractDefinition(); defineExpr(_memberAccess, contract.interfaceId()); } + else if (memberName == "typehash") + { + StructDefinition const& structDef = dynamic_cast(*magicType->typeArgument()).structDefinition(); + defineExpr(_memberAccess, u256(structDef.typehash())); + } else // NOTE: supporting name, creationCode, runtimeCode would be easy enough, but the bytes/string they return are not // at all usable in the SMT checker currently diff --git a/test/libsolidity/analysis/FunctionCallGraph.cpp b/test/libsolidity/analysis/FunctionCallGraph.cpp index da17fc433..96e92d7a9 100644 --- a/test/libsolidity/analysis/FunctionCallGraph.cpp +++ b/test/libsolidity/analysis/FunctionCallGraph.cpp @@ -1916,6 +1916,9 @@ BOOST_AUTO_TEST_CASE(builtins) interface I {} contract C { + struct S { + uint x; + } function accessBuiltin() public payable { abi.decode; abi.encode; @@ -1956,6 +1959,7 @@ BOOST_AUTO_TEST_CASE(builtins) address(0).staticcall; type(C).name; type(I).interfaceId; + type(S).typehash; type(uint).min; type(uint).max; assert;