mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
[SMTChecker] Add a new trusted mode which assumes that code that is
available at compile time is trusted.
This commit is contained in:
@@ -518,6 +518,188 @@ which has the following form:
|
||||
"source2.sol": ["contract2", "contract3"]
|
||||
}
|
||||
|
||||
Trusted External Calls
|
||||
======================
|
||||
|
||||
By default, the SMTChecker does not assume that compile-time available code
|
||||
is the same as the runtime code for external calls. Take the following contracts
|
||||
as an example:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract Ext {
|
||||
uint public x;
|
||||
function setX(uint _x) public { x = _x; }
|
||||
}
|
||||
contract MyContract {
|
||||
function callExt(Ext _e) public {
|
||||
_e.setX(42);
|
||||
assert(_e.x() == 42);
|
||||
}
|
||||
}
|
||||
|
||||
When ``MyContract.callExt`` is called, an address is given as the argument.
|
||||
At deployment time, we cannot know for sure that address ``_e`` actually
|
||||
contains a deployment of contract ``Ext``.
|
||||
Therefore, the SMTChecker will warn that the assertion above can be violated,
|
||||
which is true, if ``_e`` contains another contract than ``Ext``.
|
||||
|
||||
However, it can be useful to treat these external calls as trusted, for example,
|
||||
to test that different implementations of an interface conform to the same property.
|
||||
This means assuming that address ``_e`` indeed was deployed as contract ``Ext``.
|
||||
This mode can be enabled via the CLI option ``--model-checker-ext-calls=trusted``
|
||||
or the JSON field ``settings.modelChecker.extCalls: "trusted"``.
|
||||
|
||||
Please be aware that enabling this mode can make the SMTChecker analysis much more
|
||||
computationally costly.
|
||||
|
||||
An important part of this mode is that it is applied to contract types and high
|
||||
level external calls to contracts, and not low level calls such as ``call`` and
|
||||
``delegatecall``. The storage of an address is stored per contract type, and
|
||||
the SMTChecker assumes that an externally called contract has the type of the
|
||||
caller expression. Therefore, casting an ``address`` or a contract to
|
||||
different contract types will yield different storage values and can give
|
||||
unsound results if the assumptions are inconsistent, such as the example below:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract D {
|
||||
constructor(uint _x) { x = _x; }
|
||||
uint public x;
|
||||
function setX(uint _x) public { x = _x; }
|
||||
}
|
||||
|
||||
contract E {
|
||||
constructor() { x = 2; }
|
||||
uint public x;
|
||||
function setX(uint _x) public { x = _x; }
|
||||
}
|
||||
|
||||
contract C {
|
||||
function f() public {
|
||||
address d = address(new D(42));
|
||||
|
||||
// `d` was deployed as `D`, so its `x` should be 42 now.
|
||||
assert(D(d).x() == 42); // should hold
|
||||
assert(D(d).x() == 43); // should fail
|
||||
|
||||
// E and D have the same interface, so the following
|
||||
// call would also work at runtime.
|
||||
// However, the change to `E(d)` is not reflected in `D(d)`.
|
||||
E(d).setX(1024);
|
||||
|
||||
// Reading from `D(d)` now will show old values.
|
||||
// The assertion below should fail at runtime,
|
||||
// but succeeds in this mode's analysis (unsound).
|
||||
assert(D(d).x() == 42);
|
||||
// The assertion below should succeed at runtime,
|
||||
// but fails in this mode's analysis (false positive).
|
||||
assert(D(d).x() == 1024);
|
||||
}
|
||||
}
|
||||
|
||||
Due to the above, make sure that the trusted external calls to a certain
|
||||
variable of ``address`` or ``contract`` type always have the same caller
|
||||
expression type.
|
||||
|
||||
It is also helpful to cast the called contract's variable as the type of the
|
||||
most derived type in case of inheritance.
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
interface Token {
|
||||
function balanceOf(address _a) external view returns (uint);
|
||||
function transfer(address _to, uint _amt) external;
|
||||
}
|
||||
|
||||
contract TokenCorrect is Token {
|
||||
mapping (address => uint) balance;
|
||||
constructor(address _a, uint _b) {
|
||||
balance[_a] = _b;
|
||||
}
|
||||
function balanceOf(address _a) public view override returns (uint) {
|
||||
return balance[_a];
|
||||
}
|
||||
function transfer(address _to, uint _amt) public override {
|
||||
require(balance[msg.sender] >= _amt);
|
||||
balance[msg.sender] -= _amt;
|
||||
balance[_to] += _amt;
|
||||
}
|
||||
}
|
||||
|
||||
contract Test {
|
||||
function property_transfer(address _token, address _to, uint _amt) public {
|
||||
require(_to != address(this));
|
||||
|
||||
TokenCorrect t = TokenCorrect(_token);
|
||||
|
||||
uint xPre = t.balanceOf(address(this));
|
||||
require(xPre >= _amt);
|
||||
uint yPre = t.balanceOf(_to);
|
||||
|
||||
t.transfer(_to, _amt);
|
||||
uint xPost = t.balanceOf(address(this));
|
||||
uint yPost = t.balanceOf(_to);
|
||||
|
||||
assert(xPost == xPre - _amt);
|
||||
assert(yPost == yPre + _amt);
|
||||
}
|
||||
}
|
||||
|
||||
Note that in function ``property_transfer``, the external calls are
|
||||
performed on variable ``t``
|
||||
|
||||
Another caveat of this mode are calls to state variables of contract type
|
||||
outside the analyzed contract. In the code below, even though ``B`` deploys
|
||||
``A``, it is also possible for the address stored in ``B.a`` to be called by
|
||||
anyone outside of ``B`` in between transactions to ``B`` itself. To reflect the
|
||||
possible changes to ``B.a``, the encoding allows an unbounded number of calls
|
||||
to be made to ``B.a`` externally. The encoding will keep track of ``B.a``'s
|
||||
storage, therefore assertion (2) should hold. However, currently the encoding
|
||||
allows such calls to be made from ``B`` conceptually, therefore assertion (3)
|
||||
fails. Making the encoding stronger logically is an extension of the trusted
|
||||
mode and is under development. Note that the encoding does not keep track of
|
||||
storage for ``address`` variables, therefore if ``B.a`` had type ``address``
|
||||
the encoding would assume that its storage does not change in between
|
||||
transactions to ``B``.
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract A {
|
||||
uint public x;
|
||||
address immutable public owner;
|
||||
constructor() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
function setX(uint _x) public {
|
||||
require(msg.sender == owner);
|
||||
x = _x;
|
||||
}
|
||||
}
|
||||
|
||||
contract B {
|
||||
A a;
|
||||
constructor() {
|
||||
a = new A();
|
||||
assert(a.x() == 0); // (1) should hold
|
||||
}
|
||||
function g() public view {
|
||||
assert(a.owner() == address(this)); // (2) should hold
|
||||
assert(a.x() == 0); // (3) should hold, but fails due to a false positive
|
||||
}
|
||||
}
|
||||
|
||||
Reported Inferred Inductive Invariants
|
||||
======================================
|
||||
|
||||
|
||||
@@ -433,6 +433,10 @@ Input Description
|
||||
"divModNoSlacks": false,
|
||||
// Choose which model checker engine to use: all (default), bmc, chc, none.
|
||||
"engine": "chc",
|
||||
// Choose whether external calls should be considered trusted in case the
|
||||
// code of the called function is available at compile-time.
|
||||
// For details see the SMTChecker section.
|
||||
"extCalls": "trusted",
|
||||
// Choose which types of invariants should be reported to the user: contract, reentrancy.
|
||||
"invariants": ["contract", "reentrancy"],
|
||||
// Choose whether to output all unproved targets. The default is `false`.
|
||||
|
||||
Reference in New Issue
Block a user