Skip to content

Commit 81fed19

Browse files
committed
test(ContractTest): add Stateful Test example
1 parent 0b1a6aa commit 81fed19

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8;
3+
4+
/// @custom:ape-fuzzer-max-examples 100
5+
/// @custom:ape-stateful-step-count 50
6+
/// @custom:ape-stateful-bundles a b c
7+
contract StatefulTest {
8+
uint256 public secret;
9+
10+
function setUp() external {
11+
secret = 703895692105206524502680346056234;
12+
}
13+
14+
/// @custom:ape-stateful-targets a
15+
function initialize_bundleA() external returns (uint256[10] memory) {
16+
// NOTE: Just using static array to return a literal
17+
// (as Solidity automatically casts it)
18+
return [
19+
uint256(1),
20+
uint256(2),
21+
uint256(3),
22+
uint256(5),
23+
uint256(7),
24+
uint256(11),
25+
uint256(13),
26+
uint256(17),
27+
uint256(19),
28+
uint256(23)
29+
];
30+
}
31+
32+
/// @custom:ape-stateful-precondition this.secret() + a + b < 2 ** 256
33+
/// @custom:ape-stateful-targets b
34+
function rule_add(uint256 a) external returns (uint256) {
35+
// NOTE: Due to precondition, will **never** fail
36+
secret += a;
37+
38+
return a % 100;
39+
}
40+
41+
/// @custom:ape-stateful-consumes b
42+
function rule_subtract(uint256[] calldata a, uint256 b) external {
43+
// NOTE: This will likely fail after a few calls
44+
45+
for (uint256 idx = 0; idx < a.length; idx++) {
46+
secret -= a[idx] % b;
47+
}
48+
}
49+
50+
function invariant_secret_not_found() external view {
51+
require(secret != 2378945823475283674509246524589);
52+
}
53+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
@custom:ape-fuzzer-max-examples 100
3+
@custom:ape-stateful-step-count 50
4+
@custom:ape-stateful-bundles a b c
5+
"""
6+
7+
secret: public(uint256)
8+
9+
10+
@external
11+
def setUp():
12+
self.secret = 703895692105206524502680346056234
13+
14+
15+
@external
16+
def initialize_bundleA() -> DynArray[uint256, 10]:
17+
"""
18+
@notice
19+
Add some initial values to a bundle. "Initializers" are called exactly once at the
20+
beginning of a test (before any `rule`s are called), but could be called in any order.
21+
They are mostly used to initialize "Bundles" with values for the rest of the test.
22+
23+
@dev
24+
Same as `@initializes` in Hypothesis, allowing to set up initial test state (incl Bundles).
25+
The return value is injected into the bundle specified by `@custom:test:stateful:targets`.
26+
A single return (1 value) or array return (multiple values) are supported for conveinence.
27+
28+
@custom:ape-stateful-targets a
29+
"""
30+
return [1, 2, 3, 5, 7, 11, 13, 17, 19, 23]
31+
32+
33+
@external
34+
def rule_add(a: uint256) -> uint256:
35+
"""
36+
@notice
37+
A rule is an action that MAY be called by the test harness as one step in the test.
38+
39+
Rules can also return Bundles values, which add more choices to the associated bundle.
40+
41+
@dev
42+
A rule is selected at random, and follows the same rules as normal tests with regard to arguments.
43+
44+
If you wish to avoid calling a rule except under a particular scenario, add a precondition.
45+
46+
@custom:ape-stateful-precondition self.secret() + a + b < 2**256
47+
@custom:ape-stateful-targets b
48+
"""
49+
# NOTE: Due to precondition, will **never** fail
50+
self.secret += a
51+
52+
return a % 100
53+
54+
55+
@external
56+
def rule_subtract(a: DynArray[uint256, 10], b: uint256):
57+
"""
58+
@notice
59+
If a failure occurs when executing a rule, that will automatically raise a test failure.
60+
61+
This may indicate a legitimate bug in what you are testing, or a design flaw in your test.
62+
63+
@dev
64+
Each argument that has a name matching a bundle "pulls" values from the associated bundle.
65+
If `@custom:test:stateful:consumes` is present, then that value will instead be "consumed"
66+
by the rule, and therefore removed from the associated bundle (e.g. no longer available)
67+
68+
To pull multiple values from a bundle, use an array (selection size is chosen at random).
69+
70+
@custom:ape-stateful-consumes b
71+
"""
72+
# NOTE: This will likely fail after a few calls
73+
74+
for val: uint256 in a:
75+
self.secret -= val % b
76+
77+
78+
79+
@view
80+
@external
81+
def invariant_secret_not_found():
82+
"""
83+
@notice
84+
An invariant is called after every rule invocation, to check consistency of internal state.
85+
If it fails, it will automatically raise a test failure, likely indicating a legitimate bug.
86+
87+
@dev
88+
An invariant **MUST** be `view`/`pure` mutability or it will be ignored.
89+
"""
90+
assert self.secret != 2378945823475283674509246524589

0 commit comments

Comments
 (0)