Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d02fdc0
Add native support of EIP-712 struct typehash
k06a Apr 28, 2023
03f24a7
Handle nested structs encoding for EIP-712
k06a May 4, 2023
4d3704b
Improve typehash() computation for nested structs
k06a May 4, 2023
f4b46e5
Add missing includes
k06a May 4, 2023
05eb9df
Some fixes
k06a May 6, 2023
9f2813b
Fix wrong failure
k06a May 6, 2023
dfb4be5
Extend type(X) validation to support structs
k06a May 6, 2023
b7ee174
Fix false positive test for type(S).typehash and update error messages
k06a May 6, 2023
e40c106
Add tests for type(S).typehash
k06a May 6, 2023
f122781
Fix test expected error
k06a May 6, 2023
37b72e4
Simplify type(S).typehash tests
k06a May 6, 2023
e5f6fe5
Rename methods encode* to eip712Encode*
k06a May 9, 2023
ad9f08a
Update libsolidity/ast/Types.cpp
k06a May 9, 2023
ac9df06
Update libsolidity/codegen/ExpressionCompiler.cpp
k06a May 9, 2023
0a4e2e4
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 9, 2023
da52701
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 9, 2023
a7664c6
Fix compilation error
k06a May 9, 2023
9259448
Disallow to use typehash() for structs with nested mappings
k06a May 9, 2023
6f5f9c2
Fix assert
k06a May 9, 2023
d29aaf8
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 15, 2023
dd60917
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 15, 2023
d9d0416
fixed EIP-712 encoding type.
Saw-mon-and-Natalie May 16, 2023
859dd10
fix coding style
Saw-mon-and-Natalie May 16, 2023
ffa5201
fixed typo
Saw-mon-and-Natalie May 16, 2023
27f3530
Fix tests for compile-time checks
k06a May 18, 2023
94e314a
added syntaxt tests and updated the TypeChecker logic.
Saw-mon-and-Natalie May 21, 2023
baa3184
fixed codestyle.
Saw-mon-and-Natalie May 21, 2023
728e0b5
fixed syntax test error messages.
Saw-mon-and-Natalie May 21, 2023
99fa3b8
more semantic and syntax tests
Saw-mon-and-Natalie May 21, 2023
5cc046a
moved and created new semantic tests.
Saw-mon-and-Natalie May 22, 2023
00a4d2d
Fix spaces to tabs
k06a May 25, 2023
6a88739
Add nested structs semantic tests for type(S).typehash
k06a Jul 5, 2023
0ada0ca
Remove "compileToEwasm" option
k06a Jul 5, 2023
6035184
Some fixes for PR comments
k06a Oct 2, 2023
54d84a4
Allow user defined value types to be part of typehash
k06a Apr 16, 2024
4201294
Refactor for loop to avoid index variable
k06a Apr 16, 2024
06a3d7b
Change return type to std::string
meditationduck Sep 14, 2025
f147834
Add semantic tests for struct typehash with import alias
meditationduck Sep 14, 2025
fc2a00c
Update Changelog to include support for EIP-712 struct typehash
meditationduck Sep 14, 2025
a43d5c7
Add semantic test for recursive as reference in struct typehash
meditationduck Sep 14, 2025
293b752
Add EIP-712 typehash support for recursive structs per EIP-712 specif…
meditationduck Sep 15, 2025
5f985cd
fix tests for current implementation and fix logic
meditationduck Sep 15, 2025
1962239
Fix logic and add more tests
meditationduck Sep 16, 2025
fe52fcb
Add EIP-712 support for Enum type in structs by using uint8 and inclu…
meditationduck Sep 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### 0.8.31 (unreleased)

Language Features:
* Support for EIP-712 struct typehash via ``type(S).typehash`` for computing the keccak256 hash of the EIP-712 encoding of struct type ``S``.

Compiler Features:
* ethdebug: Experimental support for instructions and source locations under EOF.
Expand Down
1 change: 1 addition & 0 deletions docs/cheatsheet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Type Information
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information<meta-type>`.
- ``type(S).typehash`` (``bytes32``): the typehash of the given struct type ``S``, see :ref:`Type Information<meta-type>`.
- ``type(T).min`` (``T``): the minimum value representable by the integer type ``T``, see :ref:`Type Information<meta-type>`.
- ``type(T).max`` (``T``): the maximum value representable by the integer type ``T``, see :ref:`Type Information<meta-type>`.

Expand Down
7 changes: 7 additions & 0 deletions docs/units-and-global-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,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 <https://eips.ethereum.org/EIPS/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``
Expand Down
24 changes: 23 additions & 1 deletion libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
wrongType = contractType->isSuper();
else if (
typeCategory != Type::Category::Integer &&
typeCategory != Type::Category::Struct &&
typeCategory != Type::Category::Enum
)
wrongType = true;
Expand All @@ -222,7 +223,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
4259_error,
arguments.front()->location(),
"Invalid type for argument in the function call. "
"An enum type, contract type or an integer type is required, but " +
"An enum type, contract type, struct type or an integer type is required, but " +
type(*arguments.front())->humanReadableName() + " provided."
);

Expand Down Expand Up @@ -3302,6 +3303,27 @@ 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;
auto accessedStructType = dynamic_cast<StructType const*>(magicType->typeArgument());
solAssert(accessedStructType, "typehash requested on a non struct type.");

// Direct recursion is already rejected by DeclarationTypeChecker,
// so only dynamic array based recursion can reach here. which is valid.

for (auto const& member: accessedStructType->members(currentDefinitionScope()))
{
if (!member.type->isEIP712AllowedStructMemberType())
{
m_errorReporter.typeError(
9518_error,
_memberAccess.location(),
"\"typehash\" cannot be used for structs with members of \"" + member.type->humanReadableName() + "\" type."
);
}
}
}
else if (
magicType->kind() == MagicType::Kind::MetaType &&
(memberName == "min" || memberName == "max")
Expand Down
1 change: 1 addition & 0 deletions libsolidity/analysis/ViewPureChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
};
Expand Down
71 changes: 71 additions & 0 deletions libsolidity/ast/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

#include <functional>
#include <utility>
#include <numeric>
#include <set>

using namespace solidity;
using namespace solidity::frontend;
Expand Down Expand Up @@ -409,6 +411,75 @@ std::vector<std::pair<ASTPointer<IdentifierPath>, std::optional<Token>>> UsingFo
return ranges::zip_view(m_functionsOrLibrary, m_operators) | ranges::to<std::vector>;
}

void StructDefinition::insertEip712EncodedSubtypes(std::set<std::string>& subtypes) const
{
std::set<StructDefinition const*> processedStructs;
collectEip712SubtypesWithCycleTracking(subtypes, processedStructs, this);
}

void StructDefinition::collectEip712SubtypesWithCycleTracking(std::set<std::string>& subtypes, std::set<StructDefinition const*>& processedStructs, StructDefinition const* rootStruct) const
{
if (processedStructs.count(this))
return;

processedStructs.insert(this);

for (auto const& member: m_members)
{
Declaration const* declaration = nullptr;

switch (member->type()->category())
{
case Type::Category::Struct:
declaration = member->type()->typeDefinition();
break;
case Type::Category::Array:
if (auto const* arrayType = dynamic_cast<ArrayType const*>(member->type()))
if (auto finalBaseType = dynamic_cast<StructType const*>(arrayType->finalBaseType(false)))
{
declaration = finalBaseType->typeDefinition();
}
break;
default:
continue;
}

if (!declaration)
continue;

if (auto const* structDef = dynamic_cast<StructDefinition const*>(declaration))
if (structDef != rootStruct)
{
subtypes.insert(structDef->eip712EncodeTypeWithoutSubtypes());
structDef->collectEip712SubtypesWithCycleTracking(subtypes, processedStructs, rootStruct);
}
}
}

std::string StructDefinition::eip712EncodeTypeWithoutSubtypes() const
{
std::string str = name() + "(";
for (size_t i = 0; i < m_members.size(); i++)
{
str += i == 0 ? "" : ",";
str += m_members[i]->type()->eip712TypeName() + " " + m_members[i]->name();
}
return str + ")";
}

std::string StructDefinition::eip712EncodeType() const
{
// std::set enables duplicates elimination and ordered enumeration
std::set<std::string> subtypes;
insertEip712EncodedSubtypes(subtypes);
return std::accumulate(subtypes.begin(), subtypes.end(), eip712EncodeTypeWithoutSubtypes());
}

util::h256 StructDefinition::typehash() const
{
return util::keccak256(eip712EncodeType());
}

Type const* StructDefinition::type() const
{
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
Expand Down
17 changes: 17 additions & 0 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,23 @@ class StructDefinition: public Declaration, public StructurallyDocumented, publi

std::vector<ASTPointer<VariableDeclaration>> const& members() const { return m_members; }

/// Fills set with the EIP-712 compatible struct encodings without subtypes concatenated.
void insertEip712EncodedSubtypes(std::set<std::string>& subtypes) const;

private:
void collectEip712SubtypesWithCycleTracking(std::set<std::string>& subtypes, std::set<StructDefinition const*>& processedStructs, StructDefinition const* rootStruct) const;

public:

/// @returns the EIP-712 compatible struct encoding but without subtypes concatenated.
std::string eip712EncodeTypeWithoutSubtypes() const;

/// @returns the EIP-712 compatible struct encoding with subtypes sorted and concatenated.
std::string eip712EncodeType() const;

/// @returns the EIP-712 compatible typehash of this struct.
util::h256 typehash() const;

Type const* type() const override;

bool isVisibleInDerivedContracts() const override { return true; }
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/ast/TypeProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,10 +563,11 @@ MagicType const* TypeProvider::meta(Type const* _type)
solAssert(
_type && (
_type->category() == Type::Category::Contract ||
_type->category() == Type::Category::Struct ||
_type->category() == Type::Category::Integer ||
_type->category() == Type::Category::Enum
),
"Only enum, contracts or integer types supported for now."
"Only enum, contract, struct or integer types supported for now."
);
return createAndGet<MagicType>(_type);
}
Expand Down
47 changes: 38 additions & 9 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,23 @@ std::string ArrayType::canonicalName() const
return ret;
}

std::string ArrayType::eip712TypeName() const
{
std::string ret;
if (isString())
ret = "string";
else if (isByteArrayOrString())
ret = "bytes";
else
{
ret = baseType()->eip712TypeName() + "[";
if (!isDynamicallySized())
ret += length().str();
ret += "]";
}
return ret;
}

std::string ArrayType::signatureInExternalFunction(bool _structsByName) const
{
if (isByteArrayOrString())
Expand Down Expand Up @@ -2548,6 +2565,11 @@ std::string StructType::canonicalName() const
return *m_struct.annotation().canonicalName;
}

std::string StructType::eip712TypeName() const
{
return this->typeDefinition()->name();
}

FunctionTypePointer StructType::constructorType() const
{
TypePointers paramTypes;
Expand Down Expand Up @@ -2666,6 +2688,11 @@ std::string EnumType::canonicalName() const
return *m_enum.annotation().canonicalName;
}

std::string EnumType::eip712TypeName() const
{
return "uint8";
}

size_t EnumType::numberOfMembers() const
{
return m_enum.members().size();
Expand Down Expand Up @@ -4255,15 +4282,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
return {};
case Kind::MetaType:
{
solAssert(
m_typeArgument && (
m_typeArgument->category() == Type::Category::Contract ||
m_typeArgument->category() == Type::Category::Integer ||
m_typeArgument->category() == Type::Category::Enum
),
"Only enums, contracts or integer types supported for now"
);

solAssert(m_typeArgument, "");
if (m_typeArgument->category() == Type::Category::Contract)
{
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition();
Expand All @@ -4279,6 +4298,12 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
{"name", TypeProvider::stringMemory()},
});
}
else if (m_typeArgument->category() == Type::Category::Struct)
{
return MemberList::MemberMap({
{"typehash", TypeProvider::fixedBytes(32)},
});
}
else if (m_typeArgument->category() == Type::Category::Integer)
{
IntegerType const* integerTypePointer = dynamic_cast<IntegerType const*>(m_typeArgument);
Expand All @@ -4295,6 +4320,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
{"max", enumTypePointer},
});
}
else
{
solAssert(false, "Only enums, contracts, structs or integer types supported for now");
}
}
}
solAssert(false, "Unknown kind of magic.");
Expand Down
Loading