Skip to content

Commit ce4be6e

Browse files
authored
Merge pull request #15174 from ethereum/require-with-error-legacy
Require with custom error for legacy pipeline
2 parents 74399b6 + 2021301 commit ce4be6e

20 files changed

+140
-41
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
Language Features:
44
* Accept declarations of state variables with ``transient`` data location (parser support only, no code generation yet).
5+
* Make ``require(bool, Error)`` available when using the legacy pipeline.
56

67

78
Compiler Features:

docs/contracts/errors.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ as well as the newer approach with ``require`` in function ``transferWithRequire
1919
.. code-block:: solidity
2020
2121
// SPDX-License-Identifier: GPL-3.0
22-
pragma solidity ^0.8.26;
22+
pragma solidity ^0.8.27;
2323
2424
/// Insufficient balance for transfer. Needed `required` but only
2525
/// `available` available.
2626
/// @param available balance available.
2727
/// @param required requested amount to transfer.
2828
error InsufficientBalance(uint256 available, uint256 required);
2929
30-
// This will only compile via IR
3130
contract TestToken {
3231
mapping(address => uint) balance;
3332
function transferWithRevertError(address to, uint256 amount) public {

docs/control-structures.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,6 @@ The ``require`` function provides three overloads:
639639
For example, in ``require(condition, CustomError(f()));`` and ``require(condition, f());``,
640640
function ``f()`` will be called regardless of whether the supplied condition is ``true`` or ``false``.
641641

642-
.. note::
643-
Using custom errors with ``require`` is only supported by the via IR pipeline, i.e. compilation via Yul.
644-
For the legacy pipeline, please use ``if (!condition) revert CustomError();`` instead.
645-
646642
An ``Error(string)`` exception (or an exception without data) is generated
647643
by the compiler in the following situations:
648644

libsolidity/codegen/ExpressionCompiler.cpp

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
990990
}
991991
case FunctionType::Kind::Error:
992992
{
993+
// This case is part of the ``revert <error>`` path of the codegen.
994+
// For ``require(bool, <error>)`` refer to the ``Kind::Require`` case.
993995
_functionCall.expression().accept(*this);
994996
std::vector<Type const*> argumentTypes;
995997
for (ASTPointer<Expression const> const& arg: _functionCall.sortedArguments())
@@ -1249,21 +1251,58 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
12491251
case FunctionType::Kind::Require:
12501252
{
12511253
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
1252-
1254+
// stack: <condition>
12531255
bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip;
1254-
12551256
if (arguments.size() > 1)
12561257
{
12571258
// Users probably expect the second argument to be evaluated
12581259
// even if the condition is false, as would be the case for an actual
12591260
// function call.
12601261
solAssert(arguments.size() == 2, "");
12611262
solAssert(function.kind() == FunctionType::Kind::Require, "");
1262-
// Check if the function call matches ``require(bool, error)``, and throw an unimplemented error,
1263-
// as require with custom errors is not supported in legacy codegen.
12641263
auto const* magicType = dynamic_cast<MagicType const*>(arguments[1]->annotation().type);
12651264
if (magicType && magicType->kind() == MagicType::Kind::Error)
1266-
solUnimplemented("Require with a custom error is only available using the via-ir pipeline.");
1265+
{
1266+
// Make sure that error constructor arguments are evaluated regardless of the require condition
1267+
auto const& errorConstructorCall = dynamic_cast<FunctionCall const&>(*arguments[1]);
1268+
errorConstructorCall.expression().accept(*this);
1269+
std::vector<Type const*> errorConstructorArgumentTypes{};
1270+
for (ASTPointer<Expression const> const& errorConstructorArgument: errorConstructorCall.sortedArguments())
1271+
{
1272+
errorConstructorArgument->accept(*this);
1273+
errorConstructorArgumentTypes.push_back(errorConstructorArgument->annotation().type);
1274+
}
1275+
unsigned const sizeOfConditionArgument = arguments.at(0)->annotation().type->sizeOnStack();
1276+
unsigned const sizeOfErrorArguments = CompilerUtils::sizeOnStack(errorConstructorArgumentTypes);
1277+
// stack: <condition> <arg0> <arg1> ... <argN>
1278+
try
1279+
{
1280+
// Move condition to the top of the stack
1281+
utils().moveToStackTop(sizeOfErrorArguments, sizeOfConditionArgument);
1282+
}
1283+
catch (StackTooDeepError const& _exception)
1284+
{
1285+
_exception << errinfo_sourceLocation(errorConstructorCall.location());
1286+
throw _exception;
1287+
}
1288+
// stack: <arg0> <arg1> ... <argN> <condition>
1289+
m_context << Instruction::ISZERO << Instruction::ISZERO;
1290+
AssemblyItem successBranchTag = m_context.appendConditionalJump();
1291+
1292+
auto const* errorDefinition = dynamic_cast<ErrorDefinition const*>(ASTNode::referencedDeclaration(errorConstructorCall.expression()));
1293+
solAssert(errorDefinition && errorDefinition->functionType(true));
1294+
utils().revertWithError(
1295+
errorDefinition->functionType(true)->externalSignature(),
1296+
errorDefinition->functionType(true)->parameterTypes(),
1297+
errorConstructorArgumentTypes
1298+
);
1299+
// Here, the argument is consumed, but in the other branch, it is still there.
1300+
m_context.adjustStackOffset(static_cast<int>(sizeOfErrorArguments));
1301+
m_context << successBranchTag;
1302+
// In case of the success branch i.e. require(true, ...), pop error constructor arguments
1303+
utils().popStackSlots(sizeOfErrorArguments);
1304+
break;
1305+
}
12671306

12681307
if (m_context.revertStrings() == RevertStrings::Strip)
12691308
{

test/libsolidity/semanticTests/errors/require_different_errors_same_parameters.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ contract C
1414
}
1515
}
1616

17-
// ====
18-
// compileViaYul: true
1917
// ----
2018
// f() -> FAILURE, hex"f55fefe3", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"0000000000000000000000000000000000000000000000000000000000000003", hex"0000000000000000000000000000000000000000000000000000000000000003", hex"74776f0000000000000000000000000000000000000000000000000000000000"
2119
// g() -> FAILURE, hex"44a06798", hex"0000000000000000000000000000000000000000000000000000000000000004", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"0000000000000000000000000000000000000000000000000000000000000006", hex"0000000000000000000000000000000000000000000000000000000000000004", hex"6669766500000000000000000000000000000000000000000000000000000000"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
contract C {
2+
uint256 counter = 0;
3+
4+
error CustomError(uint256);
5+
6+
function getCounter() public view returns (uint256) {
7+
return counter;
8+
}
9+
10+
function g(bool condition) internal returns (bool) {
11+
counter++;
12+
return condition;
13+
}
14+
15+
function f(bool condition) external {
16+
require(g(condition), CustomError(counter));
17+
}
18+
}
19+
20+
// ----
21+
// f(bool): false -> FAILURE, hex"110b3655", 1
22+
// getCounter() -> 0
23+
// f(bool): true ->
24+
// getCounter() -> 1

test/libsolidity/semanticTests/errors/require_evaluation_order.sol renamed to test/libsolidity/semanticTests/errors/require_error_evaluation_order_1.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ contract C
2121
}
2222
}
2323

24-
// ====
25-
// compileViaYul: true
2624
// ----
2725
// f() -> 7
2826
// g() -> 7
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
contract C {
2+
uint y;
3+
function g(bool x) internal returns (bool) {
4+
y = 42;
5+
return x;
6+
}
7+
error E(uint256);
8+
function h() internal returns (uint256) { return y; }
9+
function f(bool c) public {
10+
require(g(c), E(h()));
11+
}
12+
}
13+
14+
// ----
15+
// f(bool): false -> FAILURE, hex"002ff067", 42
16+
// f(bool): true ->
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract C {
2+
string failureMessage = "Failure Message";
3+
function g(bool x) internal returns (bool) {
4+
failureMessage = "Intercepted failure message";
5+
return x;
6+
}
7+
function h() internal returns (string memory) { return failureMessage; }
8+
function f(bool c) public {
9+
require(g(c), h());
10+
}
11+
}
12+
13+
// ----
14+
// f(bool): false -> FAILURE, hex"08c379a0", 0x20, 0x1b, "Intercepted failure message"
15+
// f(bool): true ->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
contract C {
2+
uint x = 0;
3+
uint y = 42;
4+
error E(uint);
5+
function f(bool c) public returns (uint256, uint256, uint256) {
6+
uint z = x;
7+
if (y == 42) {
8+
x = 21;
9+
} else {
10+
require(c, E(y));
11+
}
12+
y /= 2;
13+
return (x,y,z);
14+
}
15+
}
16+
// ----
17+
// f(bool): true -> 0x15, 0x15, 0
18+
// f(bool): false -> FAILURE, hex"002ff067", 21

0 commit comments

Comments
 (0)