From 690e3b4cb8c60e49f4d9780e67cd2469de640281 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Thu, 20 Jul 2023 22:00:47 -0700 Subject: [PATCH] test: add halmos-cheatcodes example test (#136) --- tests/expected/all.json | 10 +++++++ tests/test/Token.t.sol | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/test/Token.t.sol diff --git a/tests/expected/all.json b/tests/expected/all.json index df893c7e..f9d95c64 100644 --- a/tests/expected/all.json +++ b/tests/expected/all.json @@ -974,6 +974,16 @@ "num_bounded_loops": null } ], + "test/Token.t.sol:TokenTest": [ + { + "name": "checkBalanceInvariant()", + "exitcode": 1, + "num_models": 2, + "num_paths": null, + "time": null, + "num_bounded_loops": null + } + ], "test/UnsupportedOpcode.t.sol:X": [ { "name": "checkFoo()", diff --git a/tests/test/Token.t.sol b/tests/test/Token.t.sol new file mode 100644 index 00000000..f5621824 --- /dev/null +++ b/tests/test/Token.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import {SymTest} from "halmos-cheatcodes/SymTest.sol"; +import {Test} from "forge-std/Test.sol"; + +/// @custom:halmos --solver-timeout-assertion 60000 +contract TokenTest is SymTest, Test { + Token token; + + function setUp() public { + token = new Token(); + + // set the balances of three arbitrary accounts to arbitrary symbolic values + for (uint i = 0; i < 3; i++) { + address receiver = svm.createAddress('receiver'); + uint256 amount = svm.createUint256('amount'); + token.transfer(receiver, amount); + } + } + + function checkBalanceInvariant() public { + // consider two arbitrary distinct accounts + address caller = svm.createAddress('caller'); + address others = svm.createAddress('others'); + vm.assume(others != caller); + + // record their current balances + uint256 oldBalanceCaller = token.balanceOf(caller); + uint256 oldBalanceOthers = token.balanceOf(others); + + // consider an arbitrary function call to the token from the caller + vm.prank(caller); + bytes memory data = svm.createBytes(100, 'data'); + (bool success,) = address(token).call(data); + vm.assume(success); + + // ensure that the caller cannot spend others' tokens + assert(token.balanceOf(caller) <= oldBalanceCaller); + assert(token.balanceOf(others) >= oldBalanceOthers); + } +} + +contract Token { + mapping(address => uint) public balanceOf; + + constructor() { + balanceOf[msg.sender] = 1e27; + } + + function transfer(address to, uint amount) public { + _transfer(msg.sender, to, amount); + } + + function _transfer(address from, address to, uint amount) public { + balanceOf[from] -= amount; + balanceOf[to] += amount; + } +}