Add native support of EIP-712 struct typehash

This commit is contained in:
Anton Bukov 2023-04-28 10:54:52 +02:00
parent 5d7533b540
commit 54b46ce39e
11 changed files with 54 additions and 0 deletions

View File

@ -119,6 +119,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>`.

View File

@ -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 <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``

View File

@ -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")

View File

@ -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"},
};

View File

@ -389,6 +389,17 @@ std::vector<std::pair<ASTPointer<IdentifierPath>, std::optional<Token>>> UsingFo
return ranges::zip_view(m_functionsOrLibrary, m_operators) | ranges::to<vector>;
}
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.");

View File

@ -741,6 +741,9 @@ public:
std::vector<ASTPointer<VariableDeclaration>> 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; }

View File

@ -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<StructType const*>(m_typeArgument);
return MemberList::MemberMap({
{"typehash", structTypePointer},
});
}
else if (m_typeArgument->category() == Type::Category::Integer)
{
IntegerType const* integerTypePointer = dynamic_cast<IntegerType const*>(m_typeArgument);

View File

@ -1931,6 +1931,12 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
m_context << (u256{contract.interfaceId()} << (256 - 32));
}
else if (member == "typehash")
{
Type const* arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
StructDefinition const& struct_ = dynamic_cast<StructType const&>(*arg).structDefinition();
m_context << struct_.typehash();
}
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);

View File

@ -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<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
auto const& structType = dynamic_cast<StructType const&>(*arg);
StructDefinition const& struct_ = structType.structDefinition();
define(_memberAccess) << formatNumber(struct_.typehash()) << "\n";
}
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);

View File

@ -1409,6 +1409,11 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition();
defineExpr(_memberAccess, contract.interfaceId());
}
else if (memberName == "typehash")
{
StructDefinition const& structDef = dynamic_cast<StructType const&>(*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

View File

@ -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;