Skip to content

Commit

Permalink
Merge pull request microsoft#221 from microsoft/shuvendu-modifies
Browse files Browse the repository at this point in the history
Shuvendu modifies
  • Loading branch information
shuvendu-lahiri authored Nov 24, 2019
2 parents 9ca6c7e + e3a9326 commit 6298e0e
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 6 deletions.
59 changes: 53 additions & 6 deletions Sources/SolToBoogie/ProcedureTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,14 +422,16 @@ public override bool Visit(FunctionDefinition node)
else
{
//extract the specifications from within the body
BoogieStmtList procBodyWoRequires, procBodyWoEnsures;
BoogieStmtList procBodyWoRequires, procBodyWoEnsures, procBodyWoModifies;
var preconditions = ExtractSpecifications("Requires_VeriSol", procBody, out procBodyWoRequires);
var postconditions = ExtractSpecifications("Ensures_VeriSol", procBodyWoRequires, out procBodyWoEnsures);
var modifies = ExtractSpecifications("Modifies_VeriSol", procBodyWoEnsures, out procBodyWoModifies);

procedure.AddPreConditions(preconditions);
procedure.AddPostConditions(postconditions);
procedure.AddPostConditions(modifies);
List<BoogieVariable> localVars = boogieToLocalVarsMap[currentBoogieProc];
BoogieImplementation impelementation = new BoogieImplementation(procName, inParams, outParams, localVars, procBodyWoEnsures);
BoogieImplementation impelementation = new BoogieImplementation(procName, inParams, outParams, localVars, procBodyWoModifies);
context.Program.AddDeclaration(impelementation);
}
}
Expand Down Expand Up @@ -1547,8 +1549,17 @@ private List<BoogieExpr> ExtractSpecifications(string specStringCall, BoogieStmt
}
if (callCmd.Callee.Equals(specStringCall))
{
VeriSolAssert(callCmd.Ins.Count == 4, $"Found {specStringCall}(..) with unexpected number of args");
specArguments.Add(callCmd.Ins[3]);
//first 3 args are {this, msg.sender, msg.value}
if (specStringCall.Equals("Modifies_VeriSol"))
{
VeriSolAssert(callCmd.Ins.Count == 5, $"For Modifies clause, found {specStringCall}(..) with unexpected number of args (expected 5)");
specArguments.Add(TranslateModifiesStmt(callCmd.Ins[3], callCmd.Ins[4]));
}
else
{
VeriSolAssert(callCmd.Ins.Count == 4, $"Found {specStringCall}(..) with unexpected number of args (expected 4)");
specArguments.Add(callCmd.Ins[3]);
}
} else
{
bodyWithoutSpecNodes.AddStatement(stmt);
Expand All @@ -1558,6 +1569,39 @@ private List<BoogieExpr> ExtractSpecifications(string specStringCall, BoogieStmt
return specArguments;
}

private BoogieExpr TranslateModifiesStmt(BoogieExpr boogieExpr1, BoogieExpr boogieExpr2)
{
//has to be M_ref_int[mapp[this]] instead of mapp[this]
var mapName = MapArrayHelper.GetMemoryMapName(BoogieType.Ref, BoogieType.Int);
var mappingExpr = new BoogieMapSelect(new BoogieIdentifierExpr(mapName), boogieExpr1);

//boogieExpr2 is a tuple, we need to flatten it into an array
var boogieTupleExpr = boogieExpr2 as BoogieTupleExpr;
VeriSolAssert(boogieTupleExpr != null, $"Expecting tuple expression, found {boogieExpr2.ToString()}");
VeriSolAssert(boogieTupleExpr.Arguments.Count > 0, $"Expecting non-tuple expression, found {boogieExpr2.ToString()}");
VeriSolAssert(boogieTupleExpr.Arguments.Count < 10, $"Expecting tuple expression of size < 10, found {boogieExpr2.ToString()}");

// forall x: Ref :: x == m1 || x == m2 ... || x == mi || map[x] == old(map[x])
var qVar1 = QVarGenerator.NewQVar(0, 0);
var bodyExpr = (BoogieExpr)new BoogieLiteralExpr(false);
for (int i = 0; i < boogieTupleExpr.Arguments.Count; i++)
{
bodyExpr =
new BoogieBinaryOperation(BoogieBinaryOperation.Opcode.OR, bodyExpr,
(new BoogieBinaryOperation(BoogieBinaryOperation.Opcode.EQ, qVar1, boogieTupleExpr.Arguments[i])));
}
var mapExpr = new BoogieMapSelect(mappingExpr, (BoogieExpr)qVar1);
var oldMapExpr = new BoogieFuncCallExpr("old", new List<BoogieExpr>() { mapExpr });
var eqExpr = new BoogieBinaryOperation(BoogieBinaryOperation.Opcode.EQ, mapExpr, oldMapExpr);
bodyExpr = new BoogieBinaryOperation(BoogieBinaryOperation.Opcode.OR, bodyExpr, eqExpr);
var qBodyExpr = new BoogieQuantifiedExpr(true,
new List<BoogieIdentifierExpr>() { qVar1 },
new List<BoogieType>() { BoogieType.Ref },
bodyExpr
);
return qBodyExpr;
}

private List<BoogieExpr> ExtractContractInvariants(BoogieStmtList body)
{
List<BoogieExpr> invariantExprs = new List<BoogieExpr>();
Expand Down Expand Up @@ -2125,7 +2169,7 @@ private BoogieExpr TranslateVeriSolCodeContractFuncCall(FunctionCall node)
boogieExprs[0] = new BoogieMapSelect(new BoogieIdentifierExpr(mapName), boogieExprs[0]);
}
return new BoogieFuncCallExpr(verisolFunc, boogieExprs);
}
}

private bool IsVeriSolCodeContractFunction(FunctionCall node)
{
Expand All @@ -2139,7 +2183,8 @@ private bool IsVeriSolCodeContractFunction(FunctionCall node)
if (member.MemberName.Equals("Invariant") ||
member.MemberName.Equals("ContractInvariant") ||
member.MemberName.Equals("Requires") ||
member.MemberName.Equals("Ensures"))
member.MemberName.Equals("Ensures") ||
member.MemberName.Equals("Modifies"))
return false;
else
return true;
Expand All @@ -2161,6 +2206,8 @@ private string GetVeriSolCodeContractFunction(FunctionCall node)
return "_SumMapping_VeriSol";
if (member.MemberName.Equals("Old"))
return "old"; //map it old(..) in Boogie
if (member.MemberName.Equals("Modifies"))
return "Modifies"; //map it modifies and forall(..) in Boogie
else
return null;
}
Expand Down
6 changes: 6 additions & 0 deletions Test/config/ERC20-modifies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recursionBound": 8,
"k": 1,
"main": "CorralEntry_ERC20",
"expectedResult": "Program has no bugs"
}
1 change: 1 addition & 0 deletions Test/records.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ DAO-buggy-call.sol
DAO-fixed-call.sol
#DAO-modular-buggy-call.sol, only when /stubModel:callback
ERC20-simplified.sol
ERC20-modifies.sol
block.sol
harness.sol
biginitinit.sol
Expand Down
256 changes: 256 additions & 0 deletions Test/regressions/ERC20-modifies.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
pragma solidity ^0.5.0;

import "./IERC20.sol";
import "./SafeMath.sol"; //import "../../math/SafeMath.sol";
import "./Libraries/VeriSolContracts.sol"; //change

/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20Mintable}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of returning `false` on failure. This behavior is nonetheless conventional
* and does not conflict with the expectations of ERC20 applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is IERC20 {
using SafeMath for uint256;

mapping (address => uint256) private _balances;

mapping (address => mapping (address => uint256)) private _allowances;

uint256 private _totalSupply;

/**
* A dummy constructor
*/
constructor (uint256 totalSupply) public {
require(msg.sender != address(0));
_totalSupply = totalSupply;
_balances[msg.sender] = totalSupply;
}

function contractInvariant() private view {
VeriSol.ContractInvariant(_totalSupply == VeriSol.SumMapping(_balances));
}

/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}

/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public returns (bool) {
_transfer(msg.sender, recipient, amount);
/* Other functional specs
assert (VeriSol.Old(_balances[msg.sender] + _balances[recipient]) == _balances[msg.sender] + _balances[recipient]);
assert (msg.sender == recipient || _balances[msg.sender] == VeriSol.Old(_balances[msg.sender] - amount));
assert (_balances[recipient] >= VeriSol.Old(_balances[recipient]));
assert (_balances[msg.sender] <= VeriSol.Old(_balances[msg.sender]));
*/
//VeriSol.Modifies(_balances, [recipient]); // this should fail
VeriSol.Modifies(_balances, [msg.sender, recipient]);
return true;
}

/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

/**
* @dev See {IERC20-approve}.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
/*
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
*/
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20};
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `value`.
* - the caller must have allowance for `sender`'s tokens of at least
* `amount`.
*/
/*
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
return true;
}
*/
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
/*
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
return true;
}
*/
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
/*
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
return true;
}
*/
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");

_balances[sender] = _balances[sender] - amount; // bug not using safemath
//_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
emit Transfer(sender, recipient, amount);
}

/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements
*
* - `to` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");

_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, amount);
}

/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 value) internal {
require(account != address(0), "ERC20: burn from the zero address");

_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}

/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 value) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");

_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}

/**
* @dev Destoys `amount` tokens from `account`.`amount` is then deducted
* from the caller's allowance.
*
* See {_burn} and {_approve}.
*/
function _burnFrom(address account, uint256 amount) internal {
_burn(account, amount);
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
}
}
15 changes: 15 additions & 0 deletions Test/regressions/Libraries/VeriSolContracts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,19 @@ library VeriSol {
function Old(int x) external pure returns (uint);
function Old(address x) external pure returns (address);

/**
* Function to specify the upper bound on the modifies set
*
*/
function Modifies(mapping (address => uint256) storage a, address[1] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[2] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[3] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[4] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[5] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[6] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[7] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[8] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[9] memory x) public pure;
function Modifies(mapping (address => uint256) storage a, address[10] memory x) public pure;

}

0 comments on commit 6298e0e

Please sign in to comment.