Skip to content

Commit a99b6d8

Browse files
Merge pull request #15968 from argotorg/builtinERC7201
Add builtin erc7201
2 parents 9f4d0b8 + fcd94ee commit a99b6d8

79 files changed

Lines changed: 1076 additions & 71 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### 0.8.35 (unreleased)
22

33
Language Features:
4+
* General: Add a builtin that computes the base slot of a storage namespace using the `erc7201` formula from ERC-7201.
45

56
Compiler Features:
67
* Commandline Interface: Introduce `--experimental` flag required for enabling the experimental mode.

‎docs/cheatsheet.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Validations and Assertions
9797
- ``revert()``: abort execution and revert state changes
9898
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
9999

100-
.. index:: cryptography, keccak256, sha256, ripemd160, ecrecover, addmod, mulmod
100+
.. index:: cryptography, keccak256, sha256, ripemd160, ecrecover, addmod, mulmod, erc7201
101101

102102
Mathematical and Cryptographic Functions
103103
========================================
@@ -111,6 +111,8 @@ Mathematical and Cryptographic Functions
111111
arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
112112
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
113113
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
114+
- ``erc7201(string memory id) returns (uint)``: compute the base slot of an ``erc7201`` storage namespace.
115+
Can be used in compile time context.
114116

115117
.. index:: this, super, selfdestruct
116118

‎docs/contracts/custom-storage-layout.rst‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ and is located in the header of a contract definition.
2525
The layout specifier can be placed either before or after the inheritance specifier, and can appear at most once.
2626
The ``base-slot-expression`` must be an :ref:`integer literal<rational_literals>` expression
2727
that can be evaluated at compilation time and yields a value in the range of ``uint256``.
28-
The use of constants initialized using such expressions is also allowed.
28+
The use of constants initialized using such expressions and
29+
the :ref:`built-in function erc7201<mathematical-and-cryptographic-functions>` is also allowed.
2930

3031
A custom layout cannot make contract's storage "wrap around".
3132
If the selected base slot would push the static variables past the end of storage,

‎docs/units-and-global-variables.rst‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ more details on error handling and when to use which function.
193193
``revert(string memory reason)``
194194
abort execution and revert state changes, providing an explanatory string
195195

196-
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
196+
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, erc7201,
197197

198198
.. _mathematical-and-cryptographic-functions:
199199

@@ -246,6 +246,12 @@ Mathematical and Cryptographic Functions
246246

247247
When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net.
248248

249+
``erc7201(string memory id) returns (uint)``
250+
compute the base slot of a storage namespace of a given ``id`` according to the ``erc7201`` formula defined by `ERC-7201<https://eips.ethereum.org/EIPS/eip-7201>`.
251+
The formula is equivalent to ``keccak256(keccak256(id) - 1) & ~0xff``.
252+
The builtin accepts arbitrary strings, including ones containing whitespace.
253+
The function can be used in compile-time context.
254+
249255
.. index:: balance, codehash, send, transfer, call, callcode, delegatecall, staticcall
250256

251257
.. _address_related:

‎libsolidity/analysis/ConstantEvaluator.cpp‎

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@
2626
#include <libsolidity/ast/AST.h>
2727
#include <libsolidity/ast/TypeProvider.h>
2828
#include <liblangutil/ErrorReporter.h>
29+
#include <libsolutil/Keccak256.h>
30+
#include <libsolutil/StringUtils.h>
31+
#include <libsolutil/FixedHash.h>
2932

3033
#include <limits>
3134

3235
using namespace solidity;
3336
using namespace solidity::frontend;
3437
using namespace solidity::langutil;
38+
using namespace solidity::util;
3539

36-
using TypedRational = ConstantEvaluator::TypedRational;
40+
using TypedValue = ConstantEvaluator::TypedValue;
3741

3842
namespace
3943
{
@@ -231,45 +235,67 @@ std::optional<rational> ConstantEvaluator::evaluateUnaryOperator(Token _operator
231235
namespace
232236
{
233237

234-
std::optional<TypedRational> convertType(rational const& _value, Type const& _type)
238+
TypedValue convertType(rational const& _value, Type const& _type)
235239
{
236240
if (_type.category() == Type::Category::RationalNumber)
237-
return TypedRational{TypeProvider::rationalNumber(_value), _value};
241+
return TypedValue{TypeProvider::rationalNumber(_value), _value};
238242
else if (auto const* integerType = dynamic_cast<IntegerType const*>(&_type))
239243
{
240244
if (_value > integerType->maxValue() || _value < integerType->minValue())
241-
return std::nullopt;
245+
return TypedValue{};
242246
else
243-
return TypedRational{&_type, _value.numerator() / _value.denominator()};
247+
return TypedValue{&_type, _value.numerator() / _value.denominator()};
244248
}
245249
else
246-
return std::nullopt;
250+
return TypedValue{};
251+
}
252+
253+
TypedValue convertType(std::string const& _value, Type const& _type)
254+
{
255+
if (
256+
_type.category() != Type::Category::StringLiteral &&
257+
_type.category() != Type::Category::Array
258+
)
259+
return TypedValue{};
260+
return TypedValue{&_type, _value};
247261
}
248262

249-
std::optional<TypedRational> convertType(std::optional<TypedRational> const& _value, Type const& _type)
263+
TypedValue convertType(TypedValue const& _value, Type const& _type)
250264
{
251-
return _value ? convertType(_value->value, _type) : std::nullopt;
265+
return std::visit(util::GenericVisitor{
266+
[&](std::string const& value) {
267+
return convertType(value, _type);
268+
},
269+
[&](rational const& value) {
270+
return convertType(value, _type);
271+
},
272+
[&](std::monostate const&) {
273+
return TypedValue{};
274+
}
275+
}, _value.value);
252276
}
253277

254-
std::optional<TypedRational> constantToTypedValue(Type const& _type)
278+
TypedValue constantToTypedValue(Type const& _type)
255279
{
256280
if (_type.category() == Type::Category::RationalNumber)
257-
return TypedRational{&_type, dynamic_cast<RationalNumberType const&>(_type).value()};
258-
else
259-
return std::nullopt;
281+
return TypedValue{&_type, dynamic_cast<RationalNumberType const&>(_type).value()};
282+
else if (_type.category() == Type::Category::StringLiteral)
283+
return TypedValue{&_type, dynamic_cast<StringLiteralType const&>(_type).value()};
284+
285+
return TypedValue{};
260286
}
261287

262288
}
263289

264-
std::optional<TypedRational> ConstantEvaluator::evaluate(
290+
TypedValue ConstantEvaluator::evaluate(
265291
langutil::ErrorReporter& _errorReporter,
266292
Expression const& _expr
267293
)
268294
{
269295
return ConstantEvaluator{_errorReporter}.evaluate(_expr);
270296
}
271297

272-
std::optional<TypedRational> ConstantEvaluator::tryEvaluate(Expression const& _expr)
298+
TypedValue ConstantEvaluator::tryEvaluate(Expression const& _expr)
273299
{
274300
ErrorList errorList;
275301
ErrorReporter errorReporter(errorList);
@@ -279,12 +305,12 @@ std::optional<TypedRational> ConstantEvaluator::tryEvaluate(Expression const& _e
279305
}
280306
catch (FatalError const&)
281307
{
282-
return std::nullopt;
308+
return TypedValue{};
283309
}
284310
}
285311

286312

287-
std::optional<TypedRational> ConstantEvaluator::evaluate(ASTNode const& _node)
313+
TypedValue ConstantEvaluator::evaluate(ASTNode const& _node)
288314
{
289315
if (!m_values.count(&_node))
290316
{
@@ -293,7 +319,7 @@ std::optional<TypedRational> ConstantEvaluator::evaluate(ASTNode const& _node)
293319
solAssert(varDecl->isConstant(), "");
294320
// In some circumstances, we do not yet have a type for the variable.
295321
if (!varDecl->value() || !varDecl->type())
296-
m_values[&_node] = std::nullopt;
322+
m_values[&_node] = TypedValue{};
297323
else
298324
{
299325
m_depth++;
@@ -311,29 +337,29 @@ std::optional<TypedRational> ConstantEvaluator::evaluate(ASTNode const& _node)
311337
{
312338
expression->accept(*this);
313339
if (!m_values.count(&_node))
314-
m_values[&_node] = std::nullopt;
340+
m_values[&_node] = TypedValue{};
315341
}
316342
}
317343
return m_values.at(&_node);
318344
}
319345

320346
void ConstantEvaluator::endVisit(UnaryOperation const& _operation)
321347
{
322-
std::optional<TypedRational> value = evaluate(_operation.subExpression());
323-
if (!value)
348+
TypedValue value = evaluate(_operation.subExpression());
349+
if (!value.type)
324350
return;
325351

326-
Type const* resultType = value->type->unaryOperatorResult(_operation.getOperator());
352+
Type const* resultType = value.type->unaryOperatorResult(_operation.getOperator());
327353
if (!resultType)
328354
return;
329355
value = convertType(value, *resultType);
330-
if (!value)
356+
if (!std::holds_alternative<rational>(value.value))
331357
return;
332358

333-
if (std::optional<rational> result = evaluateUnaryOperator(_operation.getOperator(), value->value))
359+
if (std::optional<rational> result = evaluateUnaryOperator(_operation.getOperator(), std::get<rational>(value.value)))
334360
{
335-
std::optional<TypedRational> convertedValue = convertType(*result, *resultType);
336-
if (!convertedValue)
361+
TypedValue convertedValue = convertType(*result, *resultType);
362+
if (!convertedValue.type)
337363
m_errorReporter.fatalTypeError(
338364
3667_error,
339365
_operation.location(),
@@ -345,17 +371,17 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation)
345371

346372
void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
347373
{
348-
std::optional<TypedRational> left = evaluate(_operation.leftExpression());
349-
std::optional<TypedRational> right = evaluate(_operation.rightExpression());
350-
if (!left || !right)
374+
TypedValue left = evaluate(_operation.leftExpression());
375+
TypedValue right = evaluate(_operation.rightExpression());
376+
if (!left.type || !right.type)
351377
return;
352378

353379
// If this is implemented in the future: Comparison operators have a "binaryOperatorResult"
354380
// that is non-bool, but the result has to be bool.
355381
if (TokenTraits::isCompareOp(_operation.getOperator()))
356382
return;
357383

358-
Type const* resultType = left->type->binaryOperatorResult(_operation.getOperator(), right->type);
384+
Type const* resultType = left.type->binaryOperatorResult(_operation.getOperator(), right.type);
359385
if (!resultType)
360386
{
361387
m_errorReporter.fatalTypeError(
@@ -364,22 +390,29 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
364390
"Operator " +
365391
std::string(TokenTraits::toString(_operation.getOperator())) +
366392
" not compatible with types " +
367-
left->type->toString() +
393+
left.type->toString() +
368394
" and " +
369-
right->type->toString()
395+
right.type->toString()
370396
);
371397
return;
372398
}
373399

374400
left = convertType(left, *resultType);
375401
right = convertType(right, *resultType);
376-
if (!left || !right)
402+
if (
403+
!std::holds_alternative<rational>(left.value) ||
404+
!std::holds_alternative<rational>(right.value)
405+
)
377406
return;
378407

379-
if (std::optional<rational> value = evaluateBinaryOperator(_operation.getOperator(), left->value, right->value))
408+
if (std::optional<rational> value = evaluateBinaryOperator(
409+
_operation.getOperator(),
410+
std::get<rational>(left.value),
411+
std::get<rational>(right.value)
412+
))
380413
{
381-
std::optional<TypedRational> convertedValue = convertType(*value, *resultType);
382-
if (!convertedValue)
414+
TypedValue convertedValue = convertType(*value, *resultType);
415+
if (!convertedValue.type)
383416
m_errorReporter.fatalTypeError(
384417
2643_error,
385418
_operation.location(),
@@ -407,3 +440,33 @@ void ConstantEvaluator::endVisit(TupleExpression const& _tuple)
407440
if (!_tuple.isInlineArray() && _tuple.components().size() == 1)
408441
m_values[&_tuple] = evaluate(*_tuple.components().front());
409442
}
443+
444+
void ConstantEvaluator::endVisit(FunctionCall const& _functionCall)
445+
{
446+
auto const* builtinFunction = dynamic_cast<MagicVariableDeclaration const*>(ASTNode::referencedDeclaration(_functionCall.expression()));
447+
if (!builtinFunction)
448+
return;
449+
450+
auto const* functionType = builtinFunction->functionType(true);
451+
solAssert(functionType);
452+
switch (functionType->kind())
453+
{
454+
case FunctionType::Kind::ERC7201:
455+
{
456+
solAssert(_functionCall.arguments().size() == 1);
457+
auto stringArg = evaluate(*(_functionCall.arguments()[0].get()));
458+
if (!std::holds_alternative<std::string>(stringArg.value))
459+
return;
460+
461+
h256 innerKeccak = keccak256(std::get<std::string>(stringArg.value));
462+
h256 outerKeccak = keccak256(h256(u256(innerKeccak) - 1));
463+
outerKeccak.data()[31] = 0;
464+
u256 slot = outerKeccak;
465+
solAssert(functionType->returnParameterTypes().size() == 1);
466+
m_values[&_functionCall] = TypedValue{functionType->returnParameterTypes()[0], rational{slot}};
467+
break;
468+
}
469+
default:
470+
break;
471+
}
472+
}

‎libsolidity/analysis/ConstantEvaluator.h‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,21 @@ class TypeChecker;
4646
class ConstantEvaluator: private ASTConstVisitor
4747
{
4848
public:
49-
struct TypedRational
49+
struct TypedValue
5050
{
51+
// Type may be RationalType or IntegerType for value rational
5152
Type const* type;
52-
rational value;
53+
std::variant<std::monostate, rational, std::string> value;
5354
};
5455

55-
static std::optional<TypedRational> evaluate(
56+
static TypedValue evaluate(
5657
langutil::ErrorReporter& _errorReporter,
5758
Expression const& _expr
5859
);
5960

6061
/// Works the same as `evaluate` but swallows any errors that might occur in the evaluation and simply returns
61-
/// `std::nullopt` instead.
62-
static std::optional<TypedRational> tryEvaluate(Expression const& _expr);
62+
/// `TypedValue` containing `std::monostate` instead.
63+
static TypedValue tryEvaluate(Expression const& _expr);
6364

6465
/// Performs arbitrary-precision evaluation of a binary operator. Returns nullopt on cases like
6566
/// division by zero or e.g. bit operators applied to fractional values.
@@ -72,19 +73,20 @@ class ConstantEvaluator: private ASTConstVisitor
7273
private:
7374
explicit ConstantEvaluator(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
7475

75-
std::optional<TypedRational> evaluate(ASTNode const& _node);
76+
TypedValue evaluate(ASTNode const& _node);
7677

7778
void endVisit(BinaryOperation const& _operation) override;
7879
void endVisit(UnaryOperation const& _operation) override;
7980
void endVisit(Literal const& _literal) override;
8081
void endVisit(Identifier const& _identifier) override;
8182
void endVisit(TupleExpression const& _tuple) override;
83+
void endVisit(FunctionCall const& _functionCall) override;
8284

8385
langutil::ErrorReporter& m_errorReporter;
8486
/// Current recursion depth.
8587
size_t m_depth = 0;
8688
/// Values of sub-expressions and variable declarations.
87-
std::map<ASTNode const*, std::optional<TypedRational>> m_values;
89+
std::map<ASTNode const*, TypedValue> m_values;
8890
};
8991

9092
}

‎libsolidity/analysis/DeclarationTypeChecker.cpp‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,10 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
338338
std::optional<rational> lengthValue;
339339
if (length->annotation().type && length->annotation().type->category() == Type::Category::RationalNumber)
340340
lengthValue = dynamic_cast<RationalNumberType const&>(*length->annotation().type).value();
341-
else if (std::optional<ConstantEvaluator::TypedRational> value = ConstantEvaluator::evaluate(m_errorReporter, *length))
342-
lengthValue = value->value;
341+
else if (ConstantEvaluator::TypedValue value = ConstantEvaluator::evaluate(m_errorReporter, *length);
342+
std::holds_alternative<rational>(value.value)
343+
)
344+
lengthValue = std::get<rational>(value.value);
343345

344346
if (!lengthValue)
345347
m_errorReporter.typeError(

‎libsolidity/analysis/GlobalContext.cpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ int magicVariableToID(std::string const& _name)
6262
{"tx", -26},
6363
{"type", -27},
6464
{"this", -28},
65-
{"blobhash", -29}
65+
{"blobhash", -29},
66+
{"erc7201", -30}
6667
};
6768

6869
if (auto id = magicVariables.find(_name); id != magicVariables.end())
@@ -83,6 +84,7 @@ inline std::vector<std::shared_ptr<MagicVariableDeclaration const>> constructMag
8384
magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)),
8485
magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, StateMutability::View)),
8586
magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, StateMutability::Pure)),
87+
magicVarDecl("erc7201", TypeProvider::function(strings{"string memory"}, strings{"uint256"}, FunctionType::Kind::ERC7201, StateMutability::Pure)),
8688
magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, StateMutability::View)),
8789
magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, StateMutability::Pure)),
8890
magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)),

0 commit comments

Comments
 (0)