Skip to content

Commit c69c08a

Browse files
author
Leonardo
authored
Merge pull request #11738 from ethereum/smt_div_mod_slacks
[SMTChecker] Add option divModWithSlacks
2 parents a532df2 + 08c065e commit c69c08a

File tree

37 files changed

+799
-10
lines changed

37 files changed

+799
-10
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Compiler Features:
1010
* Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default).
1111
* Commandline Interface: option ``--pretty-json`` works also with ``--standard--json``.
1212
* SMTChecker: Unproved targets are hidden by default, and the SMTChecker only states how many unproved targets there are. They can be listed using the command line option ``--model-checker-show-unproved`` or the JSON option ``settings.modelChecker.showUnproved``.
13+
* SMTChecker: new setting to enable/disable encoding of division and modulo with slack variables. The command line option is ``--model-checker-div-mod-slacks`` and the JSON option is ``settings.modelChecker.divModWithSlacks``.
1314

1415

1516
Bugfixes:

docs/smtchecker.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,17 @@ which has the following form:
509509
"source2.sol": ["contract2", "contract3"]
510510
}
511511
512-
.. _smtchecker_engines:
512+
Division and Modulo With Slack Variables
513+
========================================
514+
515+
Spacer, the default Horn solver used by the SMTChecker, often dislikes division
516+
and modulo operations inside Horn rules. Because of that, by default the
517+
Solidity division and modulo operations are encoded using the constraint
518+
``a = b * d + m`` where ``d = a / b`` and ``m = a % b``.
519+
However, other solvers, such as Eldarica, prefer the syntactically precise operations.
520+
The command line flag ``--model-checker-div-mod-no-slacks`` and the JSON option
521+
``settings.modelChecker.divModNoSlacks`` can be used to toggle the encoding
522+
depending on the used solver preferences.
513523

514524
Natspec Function Abstraction
515525
============================
@@ -523,6 +533,8 @@ body of the function is not used, and when called, the function will:
523533
- Return a nondeterministic value, and either keep the state variables unchanged if the abstracted function is view/pure, or also set the state variables to nondeterministic values otherwise. This can be used via the annotation ``/// @custom:smtchecker abstract-function-nondet``.
524534
- Act as an uninterpreted function. This means that the semantics of the function (given by the body) are ignored, and the only property this function has is that given the same input it guarantees the same output. This is currently under development and will be available via the annotation ``/// @custom:smtchecker abstract-function-uf``.
525535

536+
.. _smtchecker_engines:
537+
526538
Model Checking Engines
527539
======================
528540

docs/using-the-compiler.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ Input Description
400400
"source1.sol": ["contract1"],
401401
"source2.sol": ["contract2", "contract3"]
402402
},
403+
// Choose whether division and modulo operations should be replaced by
404+
// multiplication with slack variables. Default is `true`.
405+
// Using `false` here is recommended if you are using the CHC engine
406+
// and not using Spacer as the Horn solver (using Eldarica, for example).
407+
// See the Formal Verification section for a more detailed explanation of this option.
408+
"divModWithSlacks": true,
403409
// Choose which model checker engine to use: all (default), bmc, chc, none.
404410
"engine": "chc",
405411
// Choose whether to output all unproved targets. The default is `false`.

libsolidity/formal/ModelCheckerSettings.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ struct ModelCheckerTargets
112112
struct ModelCheckerSettings
113113
{
114114
ModelCheckerContracts contracts = ModelCheckerContracts::Default();
115+
/// Currently division and modulo are replaced by multiplication with slack vars, such that
116+
/// a / b <=> a = b * k + m
117+
/// where k and m are slack variables.
118+
/// This is the default because Spacer prefers that over precise / and mod.
119+
/// This option allows disabling this mechanism since other solvers
120+
/// might prefer the precise encoding.
121+
bool divModNoSlacks = false;
115122
ModelCheckerEngine engine = ModelCheckerEngine::None();
116123
bool showUnproved = false;
117124
smtutil::SMTSolverChoice solvers = smtutil::SMTSolverChoice::All();
@@ -123,6 +130,7 @@ struct ModelCheckerSettings
123130
{
124131
return
125132
contracts == _other.contracts &&
133+
divModNoSlacks == _other.divModNoSlacks &&
126134
engine == _other.engine &&
127135
showUnproved == _other.showUnproved &&
128136
solvers == _other.solvers &&

libsolidity/formal/SMTEncoder.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,9 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::divModWithSlacks(
19161916
IntegerType const& _type
19171917
)
19181918
{
1919+
if (m_settings.divModNoSlacks)
1920+
return {_left / _right, _left % _right};
1921+
19191922
IntegerType const* intType = &_type;
19201923
string suffix = "div_mod_" + to_string(m_context.newUniqueId());
19211924
smt::SymbolicIntVariable dSymb(intType, intType, "d_" + suffix, m_context);

libsolidity/interface/StandardCompiler.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
442442

443443
std::optional<Json::Value> checkModelCheckerSettingsKeys(Json::Value const& _input)
444444
{
445-
static set<string> keys{"contracts", "engine", "showUnproved", "solvers", "targets", "timeout"};
445+
static set<string> keys{"contracts", "divModNoSlacks", "engine", "showUnproved", "solvers", "targets", "timeout"};
446446
return checkKeys(_input, keys, "modelChecker");
447447
}
448448

@@ -941,6 +941,14 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
941941
ret.modelCheckerSettings.contracts = {move(sourceContracts)};
942942
}
943943

944+
if (modelCheckerSettings.isMember("divModNoSlacks"))
945+
{
946+
auto const& divModNoSlacks = modelCheckerSettings["divModNoSlacks"];
947+
if (!divModNoSlacks.isBool())
948+
return formatFatalError("JSONError", "settings.modelChecker.divModNoSlacks must be a Boolean.");
949+
ret.modelCheckerSettings.divModNoSlacks = divModNoSlacks.asBool();
950+
}
951+
944952
if (modelCheckerSettings.isMember("engine"))
945953
{
946954
if (!modelCheckerSettings["engine"].isString())

solc/CommandLineParser.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static string const g_strMetadata = "metadata";
8686
static string const g_strMetadataHash = "metadata-hash";
8787
static string const g_strMetadataLiteral = "metadata-literal";
8888
static string const g_strModelCheckerContracts = "model-checker-contracts";
89+
static string const g_strModelCheckerDivModNoSlacks = "model-checker-div-mod-no-slacks";
8990
static string const g_strModelCheckerEngine = "model-checker-engine";
9091
static string const g_strModelCheckerShowUnproved = "model-checker-show-unproved";
9192
static string const g_strModelCheckerSolvers = "model-checker-solvers";
@@ -720,6 +721,11 @@ General Information)").c_str(),
720721
"Multiple pairs <source>:<contract> can be selected at the same time, separated by a comma "
721722
"and no spaces."
722723
)
724+
(
725+
g_strModelCheckerDivModNoSlacks.c_str(),
726+
"Encode division and modulo operations with their precise operators"
727+
" instead of multiplication with slack variables."
728+
)
723729
(
724730
g_strModelCheckerEngine.c_str(),
725731
po::value<string>()->value_name("all,bmc,chc,none")->default_value("none"),
@@ -1092,6 +1098,9 @@ General Information)").c_str(),
10921098
m_options.modelChecker.settings.contracts = move(*contracts);
10931099
}
10941100

1101+
if (m_args.count(g_strModelCheckerDivModNoSlacks))
1102+
m_options.modelChecker.settings.divModNoSlacks = true;
1103+
10951104
if (m_args.count(g_strModelCheckerEngine))
10961105
{
10971106
string engineStr = m_args[g_strModelCheckerEngine].as<string>();
@@ -1140,6 +1149,7 @@ General Information)").c_str(),
11401149
m_options.metadata.literalSources = (m_args.count(g_strMetadataLiteral) > 0);
11411150
m_options.modelChecker.initialize =
11421151
m_args.count(g_strModelCheckerContracts) ||
1152+
m_args.count(g_strModelCheckerDivModNoSlacks) ||
11431153
m_args.count(g_strModelCheckerEngine) ||
11441154
m_args.count(g_strModelCheckerShowUnproved) ||
11451155
m_args.count(g_strModelCheckerSolvers) ||
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--model-checker-engine all
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.0;
3+
contract C {
4+
function f(uint a, uint b) public pure returns (uint, uint) {
5+
require(b != 0);
6+
return (a / b, a % b);
7+
}
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--model-checker-engine bmc

0 commit comments

Comments
 (0)