Skip to content

Commit 208cf6a

Browse files
author
Leonardo
authored
Merge pull request #11828 from ethereum/smt_chc_balance
[SMTChecker] Add proper constraints on the contract's balance
2 parents c69f919 + a9af631 commit 208cf6a

File tree

103 files changed

+777
-256
lines changed

Some content is hidden

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

103 files changed

+777
-256
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Language Features:
66
Compiler Features:
77
* Immutable variables can be read at construction time once they are initialized.
88
* SMTChecker: Support low level ``call`` as external calls to unknown code.
9+
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
910

1011

1112
Bugfixes:

docs/smtchecker.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,25 @@ are located in storage, even though they also have type ``uint[]``. However,
830830
if ``d`` was assigned, we would need to clear knowledge about ``array`` and
831831
vice-versa.
832832

833+
Contract Balance
834+
================
835+
836+
A contract may be deployed with funds sent to it, if ``msg.value`` > 0 in the
837+
deployment transaction.
838+
However, the contract's address may already have funds before deployment,
839+
which are kept by the contract.
840+
Therefore, the SMTChecker assumes that ``address(this).balance >= msg.value``
841+
in the constructor in order to be consistent with the EVM rules.
842+
The contract's balance may also increase without triggering any calls to the
843+
contract, if
844+
845+
- ``selfdestruct`` is executed by another contract with the analyzed contract
846+
as the target of the remaining funds,
847+
- the contract is the coinbase (i.e., ``block.coinbase``) of some block.
848+
849+
To model this properly, the SMTChecker assumes that at every new transaction
850+
the contract's balance may grow by at least ``msg.value``.
851+
833852
**********************
834853
Real World Assumptions
835854
**********************
@@ -841,3 +860,7 @@ push: If the ``push`` operation is applied to an array of length 2^256 - 1, its
841860
length silently overflows.
842861
However, this is unlikely to happen in practice, since the operations required
843862
to grow the array to that point would take billions of years to execute.
863+
Another similar assumption taken by the SMTChecker is that an address' balance
864+
can never overflow.
865+
866+
A similar idea was presented in `EIP-1985 <https://eips.ethereum.org/EIPS/eip-1985>`_.

libsmtutil/Sorts.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,15 @@ struct TupleSort: public Sort
159159
name(std::move(_name)),
160160
members(std::move(_members)),
161161
components(std::move(_components))
162-
{}
162+
{
163+
for (size_t i = 0; i < members.size(); ++i)
164+
memberToIndex[members.at(i)] = i;
165+
}
166+
167+
SortPointer memberSort(std::string const& _member)
168+
{
169+
return components.at(memberToIndex.at(_member));
170+
}
163171

164172
bool operator==(Sort const& _other) const override
165173
{
@@ -186,8 +194,10 @@ struct TupleSort: public Sort
186194
std::string const name;
187195
std::vector<std::string> const members;
188196
std::vector<SortPointer> const components;
197+
std::map<std::string, size_t> memberToIndex;
189198
};
190199

200+
191201
/** Frequently used sorts.*/
192202
struct SortProvider
193203
{

libsolidity/formal/CHC.cpp

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,13 @@ void CHC::endVisit(ContractDefinition const& _contract)
169169
smtutil::Expression zeroes(true);
170170
for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract))
171171
zeroes = zeroes && currentValue(*var) == smt::zeroValue(var->type());
172-
addRule(smtutil::Expression::implies(initialConstraints(_contract) && zeroes, predicate(entry)), entry.functor().name);
172+
// The contract's address might already have funds before deployment,
173+
// so the balance must be at least `msg.value`, but not equals.
174+
auto initialBalanceConstraint = state().balance(state().thisAddress()) >= state().txMember("msg.value");
175+
addRule(smtutil::Expression::implies(
176+
initialConstraints(_contract) && zeroes && initialBalanceConstraint,
177+
predicate(entry)
178+
), entry.functor().name);
173179
setCurrentBlock(entry);
174180

175181
solAssert(!m_errorDest, "");
@@ -315,11 +321,16 @@ void CHC::endVisit(FunctionDefinition const& _function)
315321
shouldAnalyze(*m_currentContract)
316322
)
317323
{
318-
auto sum = summary(_function);
324+
defineExternalFunctionInterface(_function, *m_currentContract);
325+
setCurrentBlock(*m_interfaces.at(m_currentContract));
326+
327+
// Create the rule
328+
// interface \land externalFunctionEntry => interface'
319329
auto ifacePre = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context);
320-
auto txConstraints = state().txTypeConstraints() && state().txFunctionConstraints(_function);
321-
m_queryPlaceholders[&_function].push_back({txConstraints && sum, errorFlag().currentValue(), ifacePre});
322-
connectBlocks(ifacePre, interface(), txConstraints && sum && errorFlag().currentValue() == 0);
330+
auto sum = externalSummary(_function);
331+
332+
m_queryPlaceholders[&_function].push_back({sum, errorFlag().currentValue(), ifacePre});
333+
connectBlocks(ifacePre, interface(), sum && errorFlag().currentValue() == 0);
323334
}
324335

325336
m_currentFunction = nullptr;
@@ -949,6 +960,7 @@ void CHC::resetSourceAnalysis()
949960
m_queryPlaceholders.clear();
950961
m_callGraph.clear();
951962
m_summaries.clear();
963+
m_externalSummaries.clear();
952964
m_interfaces.clear();
953965
m_nondetInterfaces.clear();
954966
m_constructorSummaries.clear();
@@ -1128,6 +1140,8 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
11281140

11291141
if (!function->isConstructor() && function->isPublic() && resolved.count(function))
11301142
{
1143+
m_externalSummaries[contract].emplace(function, createSummaryBlock(*function, *contract));
1144+
11311145
auto state1 = stateVariablesAtIndex(1, *contract);
11321146
auto state2 = stateVariablesAtIndex(2, *contract);
11331147

@@ -1144,12 +1158,41 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
11441158
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) +
11451159
applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); });
11461160

1147-
connectBlocks(nondetPre, nondetPost, errorPre == 0 && (*m_summaries.at(contract).at(function))(args));
1161+
connectBlocks(nondetPre, nondetPost, errorPre == 0 && (*m_externalSummaries.at(contract).at(function))(args));
11481162
}
11491163
}
11501164
}
11511165
}
11521166

1167+
void CHC::defineExternalFunctionInterface(FunctionDefinition const& _function, ContractDefinition const& _contract)
1168+
{
1169+
// Create a rule that represents an external call to this function.
1170+
// This contains more things than the function body itself,
1171+
// such as balance updates because of ``msg.value``.
1172+
auto functionEntryBlock = createBlock(&_function, PredicateType::FunctionBlock);
1173+
auto functionPred = predicate(*functionEntryBlock);
1174+
addRule(functionPred, functionPred.name);
1175+
setCurrentBlock(*functionEntryBlock);
1176+
1177+
m_context.addAssertion(initialConstraints(_contract, &_function));
1178+
m_context.addAssertion(state().txTypeConstraints() && state().txFunctionConstraints(_function));
1179+
1180+
// The contract may have received funds through a selfdestruct or
1181+
// block.coinbase, which do not trigger calls into the contract.
1182+
// So the only constraint we can add here is that the balance of
1183+
// the contract grows by at least `msg.value`.
1184+
SymbolicIntVariable k{TypeProvider::uint256(), TypeProvider::uint256(), "funds_" + to_string(m_context.newUniqueId()), m_context};
1185+
m_context.addAssertion(k.currentValue() >= state().txMember("msg.value"));
1186+
// Assume that address(this).balance cannot overflow.
1187+
m_context.addAssertion(smt::symbolicUnknownConstraints(state().balance(state().thisAddress()) + k.currentValue(), TypeProvider::uint256()));
1188+
state().addBalance(state().thisAddress(), k.currentValue());
1189+
1190+
errorFlag().increaseIndex();
1191+
m_context.addAssertion(summaryCall(_function));
1192+
1193+
connectBlocks(functionPred, externalSummary(_function));
1194+
}
1195+
11531196
void CHC::defineContractInitializer(ContractDefinition const& _contract, ContractDefinition const& _contextContract)
11541197
{
11551198
m_contractInitializers[&_contextContract][&_contract] = createConstructorBlock(_contract, "contract_initializer");
@@ -1220,12 +1263,34 @@ smtutil::Expression CHC::summary(FunctionDefinition const& _function, ContractDe
12201263
return smt::function(*m_summaries.at(&_contract).at(&_function), &_contract, m_context);
12211264
}
12221265

1266+
smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function, ContractDefinition const& _contract)
1267+
{
1268+
return smt::functionCall(*m_summaries.at(&_contract).at(&_function), &_contract, m_context);
1269+
}
1270+
1271+
smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function, ContractDefinition const& _contract)
1272+
{
1273+
return smt::function(*m_externalSummaries.at(&_contract).at(&_function), &_contract, m_context);
1274+
}
1275+
12231276
smtutil::Expression CHC::summary(FunctionDefinition const& _function)
12241277
{
12251278
solAssert(m_currentContract, "");
12261279
return summary(_function, *m_currentContract);
12271280
}
12281281

1282+
smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function)
1283+
{
1284+
solAssert(m_currentContract, "");
1285+
return summaryCall(_function, *m_currentContract);
1286+
}
1287+
1288+
smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function)
1289+
{
1290+
solAssert(m_currentContract, "");
1291+
return externalSummary(_function, *m_currentContract);
1292+
}
1293+
12291294
Predicate const* CHC::createBlock(ASTNode const* _node, PredicateType _predType, string const& _prefix)
12301295
{
12311296
auto block = createSymbolicBlock(

libsolidity/formal/CHC.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ class CHC: public SMTEncoder
164164
/// in a given _source.
165165
void defineInterfacesAndSummaries(SourceUnit const& _source);
166166

167+
/// Creates the rule
168+
/// summary_function \land transaction_entry_constraints => external_summary_function
169+
/// This is needed to add these transaction entry constraints which include
170+
/// potential balance increase by external means, for example.
171+
void defineExternalFunctionInterface(FunctionDefinition const& _function, ContractDefinition const& _contract);
172+
167173
/// Creates a CHC system that, for a given contract,
168174
/// - initializes its state variables (as 0 or given value, if any).
169175
/// - "calls" the explicit constructor function of the contract, if any.
@@ -225,6 +231,13 @@ class CHC: public SMTEncoder
225231
/// @returns a predicate that defines a function summary.
226232
smtutil::Expression summary(FunctionDefinition const& _function);
227233
smtutil::Expression summary(FunctionDefinition const& _function, ContractDefinition const& _contract);
234+
/// @returns a predicate that applies a function summary
235+
/// over the constrained variables.
236+
smtutil::Expression summaryCall(FunctionDefinition const& _function);
237+
smtutil::Expression summaryCall(FunctionDefinition const& _function, ContractDefinition const& _contract);
238+
/// @returns a predicate that defines an external function summary.
239+
smtutil::Expression externalSummary(FunctionDefinition const& _function);
240+
smtutil::Expression externalSummary(FunctionDefinition const& _function, ContractDefinition const& _contract);
228241
//@}
229242

230243
/// Solver related.
@@ -317,6 +330,9 @@ class CHC: public SMTEncoder
317330

318331
/// Function predicates.
319332
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, Predicate const*>> m_summaries;
333+
334+
/// External function predicates.
335+
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, Predicate const*>> m_externalSummaries;
320336
//@}
321337

322338
/// Variables.

libsolidity/formal/SMTEncoder.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,9 +2283,7 @@ void SMTEncoder::resetStorageVariables()
22832283

22842284
void SMTEncoder::resetBalances()
22852285
{
2286-
// TODO this should be changed to `balances` only
2287-
// when `state` gets more members.
2288-
state().newState();
2286+
state().newBalances();
22892287
}
22902288

22912289
void SMTEncoder::resetReferences(VariableDeclaration const& _varDecl)

libsolidity/formal/SymbolicState.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ smtutil::Expression SymbolicState::blockhash(smtutil::Expression _blockNumber) c
104104
return smtutil::Expression::select(m_tx.member("blockhash"), move(_blockNumber));
105105
}
106106

107+
void SymbolicState::newBalances()
108+
{
109+
auto tupleSort = dynamic_pointer_cast<TupleSort>(stateSort());
110+
auto balanceSort = tupleSort->components.at(tupleSort->memberToIndex.at("balances"));
111+
SymbolicVariable newBalances(balanceSort, "fresh_balances_" + to_string(m_context.newUniqueId()), m_context);
112+
m_state.assignMember("balances", newBalances.currentValue());
113+
}
114+
107115
void SymbolicState::transfer(smtutil::Expression _from, smtutil::Expression _to, smtutil::Expression _value)
108116
{
109117
unsigned indexBefore = m_state.index();
@@ -121,6 +129,16 @@ void SymbolicState::transfer(smtutil::Expression _from, smtutil::Expression _to,
121129
m_context.addAssertion(m_state.value() == newState);
122130
}
123131

132+
void SymbolicState::addBalance(smtutil::Expression _address, smtutil::Expression _value)
133+
{
134+
auto newBalances = smtutil::Expression::store(
135+
balances(),
136+
_address,
137+
balance(_address) + move(_value)
138+
);
139+
m_state.assignMember("balances", newBalances);
140+
}
141+
124142
smtutil::Expression SymbolicState::txMember(string const& _member) const
125143
{
126144
return m_tx.member(_member);
@@ -181,16 +199,6 @@ void SymbolicState::prepareForSourceUnit(SourceUnit const& _source)
181199

182200
/// Private helpers.
183201

184-
void SymbolicState::addBalance(smtutil::Expression _address, smtutil::Expression _value)
185-
{
186-
auto newBalances = smtutil::Expression::store(
187-
balances(),
188-
_address,
189-
balance(_address) + move(_value)
190-
);
191-
m_state.assignMember("balances", newBalances);
192-
}
193-
194202
void SymbolicState::buildABIFunctions(set<FunctionCall const*> const& _abiFunctions)
195203
{
196204
map<string, SortPointer> functions;

libsolidity/formal/SymbolicState.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class SymbolicState
108108
smtutil::Expression state(unsigned _idx) const { return m_state.value(_idx); }
109109
smtutil::SortPointer const& stateSort() const { return m_state.sort(); }
110110
void newState() { m_state.newVar(); }
111+
112+
void newBalances();
111113
/// @returns the symbolic balances.
112114
smtutil::Expression balances() const;
113115
/// @returns the symbolic balance of address `this`.
@@ -117,6 +119,9 @@ class SymbolicState
117119

118120
/// Transfer _value from _from to _to.
119121
void transfer(smtutil::Expression _from, smtutil::Expression _to, smtutil::Expression _value);
122+
123+
/// Adds _value to _account's balance.
124+
void addBalance(smtutil::Expression _account, smtutil::Expression _value);
120125
//@}
121126

122127
/// Transaction data.
@@ -163,9 +168,6 @@ class SymbolicState
163168
//@}
164169

165170
private:
166-
/// Adds _value to _account's balance.
167-
void addBalance(smtutil::Expression _account, smtutil::Expression _value);
168-
169171
/// Builds m_abi based on the abi.* calls _abiFunctions.
170172
void buildABIFunctions(std::set<FunctionCall const*> const& _abiFunctions);
171173

0 commit comments

Comments
 (0)