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
7f5e5d2
Add native support of EIP-712 struct typehash
k06a Apr 28, 2023
2cb0e98
Handle nested structs encoding for EIP-712
k06a May 4, 2023
088f6de
Improve typehash() computation for nested structs
k06a May 4, 2023
8278fad
Add missing includes
k06a May 4, 2023
06ccb05
Some fixes
k06a May 6, 2023
b190a38
Fix wrong failure
k06a May 6, 2023
2bafc7b
Extend type(X) validation to support structs
k06a May 6, 2023
afcd3ac
Fix false positive test for type(S).typehash and update error messages
k06a May 6, 2023
8506359
Add tests for type(S).typehash
k06a May 6, 2023
236b025
Fix test expected error
k06a May 6, 2023
2b33852
Simplify type(S).typehash tests
k06a May 6, 2023
f81651f
Rename methods encode* to eip712Encode*
k06a May 9, 2023
db4b9e8
Update libsolidity/ast/Types.cpp
k06a May 9, 2023
18c475a
Update libsolidity/codegen/ExpressionCompiler.cpp
k06a May 9, 2023
aa4ff53
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 9, 2023
b937354
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 9, 2023
b1db743
Fix compilation error
k06a May 9, 2023
04a2e05
Disallow to use typehash() for structs with nested mappings
k06a May 9, 2023
ed8c59d
Fix assert
k06a May 9, 2023
49b8e01
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 15, 2023
4d4e695
Update libsolidity/codegen/ir/IRGeneratorForStatements.cpp
k06a May 15, 2023
690869e
fixed EIP-712 encoding type.
Saw-mon-and-Natalie May 16, 2023
3251e06
fix coding style
Saw-mon-and-Natalie May 16, 2023
2a5d021
fixed typo
Saw-mon-and-Natalie May 16, 2023
5f5a623
Fix tests for compile-time checks
k06a May 18, 2023
e76b74b
added syntaxt tests and updated the TypeChecker logic.
Saw-mon-and-Natalie May 21, 2023
50d3795
fixed codestyle.
Saw-mon-and-Natalie May 21, 2023
46f4f8e
fixed syntax test error messages.
Saw-mon-and-Natalie May 21, 2023
52a38ab
more semantic and syntax tests
Saw-mon-and-Natalie May 21, 2023
9aed6d8
moved and created new semantic tests.
Saw-mon-and-Natalie May 22, 2023
237ca4f
Fix spaces to tabs
k06a May 25, 2023
67cdeb3
Add nested structs semantic tests for type(S).typehash
k06a Jul 5, 2023
081f825
Remove "compileToEwasm" option
k06a Jul 5, 2023
17e9401
Some fixes for PR comments
k06a Oct 2, 2023
3db9c82
Allow user defined value types to be part of typehash
k06a Apr 16, 2024
863887e
Refactor for loop to avoid index variable
k06a Apr 16, 2024
645db0d
Change return type to std::string
meditationduck Sep 14, 2025
d00614b
Add semantic tests for struct typehash with import alias
meditationduck Sep 14, 2025
22c6462
Update Changelog to include support for EIP-712 struct typehash
meditationduck Sep 14, 2025
bd1d84e
Add semantic test for recursive as reference in struct typehash
meditationduck Sep 14, 2025
11371e1
Add EIP-712 typehash support for recursive structs per EIP-712 specif…
meditationduck Sep 15, 2025
cf519a9
fix tests for current implementation and fix logic
meditationduck Sep 15, 2025
4d87329
Fix logic and add more tests
meditationduck Sep 16, 2025
69955c4
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.32 (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:

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 @@ -412,6 +412,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 @@ -3325,6 +3326,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