Skip to content

Commit 8447b32

Browse files
authored
Merge pull request #11861 from ethereum/smt_value
[SMTChecker] Support `value` in CHC for external function calls
2 parents d384664 + 106c591 commit 8447b32

12 files changed

+168
-5
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
* Immutable variables can be read at construction time once they are initialized.
1111
* SMTChecker: Support low level ``call`` as external calls to unknown code.
1212
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
13+
* SMTChecker: Support the ``value`` option for external function calls.
1314
* Commandline Interface: Disallowed the ``--experimental-via-ir`` option to be used with Standard Json, Assembler and Linker modes.
1415

1516

libsolidity/formal/CHC.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
#include <range/v3/algorithm/for_each.hpp>
3636

37+
#include <range/v3/view/enumerate.hpp>
3738
#include <range/v3/view/reverse.hpp>
3839

3940
#ifdef HAVE_Z3_DLOPEN
@@ -743,13 +744,15 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
743744
/// so we just add the nondet_interface predicate.
744745

745746
solAssert(m_currentContract, "");
746-
if (isTrustedExternalCall(&_funCall.expression()))
747+
auto [callExpr, callOptions] = functionCallExpression(_funCall);
748+
749+
if (isTrustedExternalCall(callExpr))
747750
{
748751
externalFunctionCallToTrustedCode(_funCall);
749752
return;
750753
}
751754

752-
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
755+
FunctionType const& funType = dynamic_cast<FunctionType const&>(*callExpr->annotation().type);
753756
auto kind = funType.kind();
754757
solAssert(
755758
kind == FunctionType::Kind::External ||
@@ -773,6 +776,19 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
773776
if (!m_currentFunction || m_currentFunction->isConstructor())
774777
return;
775778

779+
if (callOptions)
780+
{
781+
optional<unsigned> valueIndex;
782+
for (auto&& [i, name]: callOptions->names() | ranges::views::enumerate)
783+
if (name && *name == "value")
784+
{
785+
valueIndex = i;
786+
break;
787+
}
788+
solAssert(valueIndex, "");
789+
state().addBalance(state().thisAddress(), 0 - expr(*callOptions->options().at(*valueIndex)));
790+
}
791+
776792
auto preCallState = vector<smtutil::Expression>{state().state()} + currentStateVariables();
777793

778794
if (!usesStaticCall)
@@ -829,6 +845,8 @@ void CHC::externalFunctionCallToTrustedCode(FunctionCall const& _funCall)
829845
state().newTx();
830846
// set the transaction sender as this contract
831847
m_context.addAssertion(state().txMember("msg.sender") == state().thisAddress());
848+
// set the transaction value as 0
849+
m_context.addAssertion(state().txMember("msg.value") == 0);
832850
// set the origin to be the current transaction origin
833851
m_context.addAssertion(state().txMember("tx.origin") == txOrigin);
834852

@@ -1451,10 +1469,12 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
14511469
return smtutil::Expression(true);
14521470

14531471
auto contractAddressValue = [this](FunctionCall const& _f) {
1454-
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_f.expression().annotation().type);
1472+
auto [callExpr, callOptions] = functionCallExpression(_f);
1473+
1474+
FunctionType const& funType = dynamic_cast<FunctionType const&>(*callExpr->annotation().type);
14551475
if (funType.kind() == FunctionType::Kind::Internal)
14561476
return state().thisAddress();
1457-
if (MemberAccess const* callBase = dynamic_cast<MemberAccess const*>(&_f.expression()))
1477+
if (MemberAccess const* callBase = dynamic_cast<MemberAccess const*>(callExpr))
14581478
return expr(callBase->expression());
14591479
solAssert(false, "Unreachable!");
14601480
};

libsolidity/formal/SMTEncoder.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2590,6 +2590,16 @@ Expression const* SMTEncoder::innermostTuple(Expression const& _expr)
25902590
return expr;
25912591
}
25922592

2593+
pair<Expression const*, FunctionCallOptions const*> SMTEncoder::functionCallExpression(FunctionCall const& _funCall)
2594+
{
2595+
Expression const* callExpr = &_funCall.expression();
2596+
auto const* callOptions = dynamic_cast<FunctionCallOptions const*>(callExpr);
2597+
if (callOptions)
2598+
callExpr = &callOptions->expression();
2599+
2600+
return {callExpr, callOptions};
2601+
}
2602+
25932603
Expression const* SMTEncoder::cleanExpression(Expression const& _expr)
25942604
{
25952605
auto const* expr = &_expr;
@@ -2713,7 +2723,8 @@ FunctionDefinition const* SMTEncoder::functionCallToDefinition(
27132723
if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall)
27142724
return {};
27152725

2716-
Expression const* calledExpr = &_funCall.expression();
2726+
auto [calledExpr, callOptions] = functionCallExpression(_funCall);
2727+
27172728
if (TupleExpression const* fun = dynamic_cast<TupleExpression const*>(calledExpr))
27182729
{
27192730
solAssert(fun->components().size() == 1, "");

libsolidity/formal/SMTEncoder.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ class SMTEncoder: public ASTConstVisitor
7171
/// otherwise _expr.
7272
static Expression const* innermostTuple(Expression const& _expr);
7373

74+
/// @returns {_funCall.expression(), nullptr} if function call option values are not given, and
75+
/// {_funCall.expression().expression(), _funCall.expression()} if they are.
76+
static std::pair<Expression const*, FunctionCallOptions const*> functionCallExpression(FunctionCall const& _funCall);
77+
7478
/// @returns the expression after stripping redundant syntactic sugar.
7579
/// Currently supports stripping:
7680
/// 1. 1-tuple; i.e. ((x)) -> x
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
contract C {
2+
function g(address payable i) public {
3+
require(address(this).balance == 100);
4+
i.call{value: 10}("");
5+
// Disabled due to Spacer nondeterminism
6+
//assert(address(this).balance == 90); // should hold
7+
// Disabled due to Spacer nondeterminism
8+
//assert(address(this).balance == 100); // should fail
9+
}
10+
}
11+
// ====
12+
// SMTEngine: all
13+
// ----
14+
// Warning 9302: (96-117): Return value of low-level calls not used.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract C {
2+
function g(address payable i) public {
3+
require(address(this).balance == 100);
4+
i.call{value: 0}("");
5+
assert(address(this).balance == 100); // should hold
6+
assert(address(this).balance == 20); // should fail
7+
}
8+
}
9+
// ====
10+
// SMTEngine: all
11+
// ----
12+
// Warning 9302: (96-116): Return value of low-level calls not used.
13+
// Warning 6328: (120-156): CHC: Assertion violation might happen here.
14+
// Warning 6328: (175-210): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0\n\nTransaction trace:\nC.constructor()\nC.g(0)\n i.call{value: 0}("") -- untrusted external call
15+
// Warning 4661: (120-156): BMC: Assertion violation happens here.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract C {
2+
function g(address payable i) public {
3+
require(address(this).balance > 100);
4+
i.call{value: 20}("");
5+
assert(address(this).balance > 0); // should hold
6+
// Disabled due to Spacer nondeterminism
7+
//assert(address(this).balance == 0); // should fail
8+
}
9+
}
10+
// ====
11+
// SMTEngine: all
12+
// ----
13+
// Warning 9302: (95-116): Return value of low-level calls not used.
14+
// Warning 6328: (120-153): CHC: Assertion violation might happen here.
15+
// Warning 4661: (120-153): BMC: Assertion violation happens here.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
contract C {
2+
function g() public {
3+
require(address(this).balance == 100);
4+
this.h{value: 10}();
5+
assert(address(this).balance == 100); // should hold
6+
assert(address(this).balance == 90); // should fail
7+
}
8+
9+
function h() external payable {}
10+
}
11+
// ====
12+
// SMTEngine: all
13+
// ----
14+
// Warning 6328: (157-192): CHC: Assertion violation happens here.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
contract C {
2+
function g(uint i) public {
3+
require(address(this).balance == 100);
4+
this.h{value: i}();
5+
assert(address(this).balance == 100); // should hold
6+
assert(address(this).balance == 90); // should fail
7+
}
8+
9+
function h() external payable {}
10+
}
11+
// ====
12+
// SMTEngine: all
13+
// SMTIgnoreCex: yes
14+
// ----
15+
// Warning 6328: (162-197): CHC: Assertion violation happens here.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
interface I {
2+
function f() external payable;
3+
}
4+
5+
contract C {
6+
function g(I i) public {
7+
require(address(this).balance == 100);
8+
i.f{value: 10}();
9+
assert(address(this).balance == 90); // should hold
10+
// Disabled due to Spacer nondeterminism
11+
//assert(address(this).balance == 100); // should fail
12+
}
13+
}
14+
// ====
15+
// SMTEngine: all
16+
// ----
17+
// Warning 6328: (151-186): CHC: Assertion violation might happen here.
18+
// Warning 4661: (151-186): BMC: Assertion violation happens here.

0 commit comments

Comments
 (0)