Skip to content

Fuzzing#

Fuzzing is a technique for testing software that involves providing invalid, unexpected, or random data as inputs to a computer program.

Introduction#

The Wake testing framework provides a FuzzTest class that can be used to write fuzz tests. A FuzzTest can be run using the run method with two required arguments:

class CounterTest(FuzzTest):
    ...

CounterTest().run(sequences_count=10, flows_count=100)

The first argument specifies the number of test sequences to be executed. A sequence is an independent test case - all connected chains are reset after each sequence. Each sequence consists of a given number of flows. A flow is an atomic test step that is executed in a test sequence.

The FuzzTest class provides two properties, sequence_num and flow_num, that can be used to obtain the current sequence and flow numbers, both starting from 0.

Flows#

A flow is a single test step that is executed in a test sequence. Flows are defined using the @flow decorator:

@flow(precondition=lambda self: self.count > 0)
def flow_decrement(self) -> None:
    self.counter.decrement(from_=random_account())
    self.count -= 1

Flow functions must be defined inside a test class that inherits from FuzzTest.

The @flow decorator accepts the following keyword arguments:

Argument Description
weight weight defining probability of the flow being executed in a test sequence; defaults to 100
max_times maximum number of times the flow can be executed in a test sequence; defaults to None
precondition function that accepts a single argument self and returns a boolean value; the flow is executed only if the precondition is True

How flow weights work

If a flow has a weight of `100` and another flow has a weight of `50`, the first flow will be executed twice as often as the second flow.

```python
@flow(weight=100)
def flow_1(self) -> None:
    ...

@flow(weight=50)
def flow_2(self) -> None:
    ...
```

That means that the probability of `flow_1` being executed is `100 / (100 + 50) = 2/3` and the probability of `flow_2` being executed is `50 / (100 + 50) = 1/3`.

Invariants#

An invariant is a test that is executed after each flow in a test sequence. Invariants are defined using the @invariant decorator:

@invariant(period=10)
def invariant_count(self) -> None:
    assert self.counter.count() == self.count

An optional period argument can be passed to the @invariant decorator. If specified, the invariant is executed only after every period flows.

Execution hooks#

Execution hooks are functions that are executed during the FuzzTest lifecycle. This is the list of all available execution hooks:

  • pre_sequence(self) - executed before each test sequence,
  • pre_flow(self, flow: Callable) - executed before each flow, accepts the flow function to be executed as an argument,
  • post_flow(self, flow: Callable) - executed after each flow, accepts the flow function that was executed as an argument,
  • pre_invariants(self) - executed before each set of invariants,
  • pre_invariant(self, invariant: Callable) - executed before each invariant, accepts the invariant function to be executed as an argument,
  • post_invariant(self, invariant: Callable) - executed after each invariant, accepts the invariant function that was executed as an argument,
  • post_invariants(self) - executed after each set of invariants,
  • post_sequence(self) - executed after each test sequence.

The whole FuzzTest lifecycle is visualized in the following diagram:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d6XLiSpb+30/hqP7TXHUwMDFk06ZzXzp6Ylx1MDAwMm9cdTAwMThcdTAwMWIvXHUwMDE4g5fpXHUwMDBlXHUwMDA3i1xmMoswizF03Fx1MDAxN5l/82rzJJOJy6AlJVx1MDAwNFx1MDAxNpi6XHJxo25cdTAwMTUgIaXOd75zTp7lX3/Y2/sxXHUwMDE4d61cdTAwMWZ/2/thvVfLLbvWK49+/EW//2b1+rbTUVx1MDAxZqHpv/vOsFedfrMxXHUwMDE4dPt/++tf50ekqk774yirZbWtzqCvvvff6t97e/+a/qk+sWv62MvLaveB9uFzpV2lrMJcdTAwMDanJYynh06/9HkxPas6KHfqLWv+0bt6n0My+/dYX1x1MDAxOcWzf4/s2qCh3sOQp4DrXHUwMDA1Z99oWHa9MVBfXHUwMDExPCU9r9lXPn7zb3tg9k5/0HOa1qHTcnr6wv74cdL5ZVXK1Wa951xmO7XZd1x1MDAwNr1yp98t99Q6zL/3bLdahcG49bGC5Wpj2HPd3Mev3P28XHUwMDA16Ht/dlxc31HrPT9K/Wy90bH6fc8xTrdcXLVcdTAwMDd6eSCY34e+xm62Nn0w/3SfoVP7eYbOsNWa/7Bl6edFXHUwMDEwI0RcdTAwMDA0X+a5WFDJ/e9eOp2piECBuEBIgPnK2v0jJVx1MDAxYoPpWZ/Lrb41X0F9XHLHfrlxy45HNFx1MDAwNtb7fFldkiXL+NGGb0O70HjIVyqTo/t+sfJj9r3f/mI+7cfBuZtcdTAwMDJcdTAwMTldg5dreHfb4ZmsPG1cdTAwMDPh/ZXP3y/3es4o7nmf72tH11x1MDAwNbvJ6pXhWbPfusR5dlx1MDAxOO+8P/82f1bDbq38sX6QcUolXHUwMDEzUkgwfzItu9P0P8aWU23Ol/xcdTAwMGauXHUwMDBi9uHSvHpcdTAwMDFcXHpcdTAwMTb/XHUwMDAzkkSmqFx1MDAxN5SCXHUwMDA0QIlcYlx1MDAwYsJcdTAwMTCxXHUwMDFk7kJwXHUwMDA3IVx1MDAwM1hcdTAwMTJKoVx1MDAwMXiEg1DgXHUwMDExJiGmkK5cdTAwMDI8z4V8XdrmwqOFRq3iYaNsd/b6nXK333BcdTAwMDb9vWrPmv7E/Fk5nUHBnuhcdTAwMWJBwPPuSbltt8ae5dZnTbfsul6RXHUwMDFmVXVcdTAwMGJW74d7WVx1MDAwNrZip9lcdTAwMTfadq3mZpNKuW+pW/hQVrN3q+qn1DVavWxcdTAwMWOycnp23e6UW7dcdTAwMGLuL1x1MDAwMnaQONIhXHUwMDA3N5aTh+lcdTAwMWW0zp7az+fx6ZBcdFx1MDAwZvKIXHUwMDBiUTPkuShzhjwsdshcdTAwMGJBXHUwMDFlRYJTwTk1XHUwMDAwXHUwMDBmQlx1MDAxYYo8XHUwMDA0XGLgiFx1MDAwMEpXQN6KlFx1MDAwNzPv9cPSPYf547vKmL3dIWf/NC41ndhcdTAwMTCSXHUwMDE3u5Z9auRyo0z/sX6brWwjNZnvMlx1MDAwZTVx5oVcdTAwMDeGLqr6XHUwMDA0XGKUXCJcYlx1MDAxMMR3XHUwMDAwXHRcdTAwMDGIkFxuXHUwMDFlQLGTXHUwMDExIC5t41x1MDAwM1xiZYJcdTAwMTBIXFxG4/YwU7dnPfWt16GlLvVPf977v//536VIXHR7TphcYimh+cPzkdJcdTAwMDLK8JOS4dZcIrBWejwpZ0a1TqVcdTAwMGZaZNC1XHUwMDBm6UDex+cjl9mh12WfyFx1MDAwMNxcdTAwMTA2WIJ454GFwVxyXHUwMDExRFx0Q8zER5ShMLQxKlx1MDAxMcNcZm/QXHUwMDAx65Toa+dcdTAwMTVnXHUwMDE1fTaUXFw/1nPpy5u4bNRpjlx1MDAxZXOjSefoQjxcdTAwMWSdXVR6XHUwMDE1dNDdRjYy32VcdTAwMWM2XHUwMDEyyOco7ZNg9Fx1MDAwMlxuXHUwMDE5hMeOjcJcdTAwMWQlJFx1MDAxMJCAcVx1MDAxM1x1MDAxZFx1MDAxMVx1MDAxZYpcdTAwMGYogUJcdTAwMTaCYFx1MDAxNXNt3XT0qa+fOsP23n/uge0lo1x1MDAwNXzhJ6PAjUVcdTAwMDDtXHUwMDA0ZPvdYqNSn9S6PVxmnMrlU6VcdTAwMTBcdTAwMDRazS63nU7NhzWEPEjDhjAhMPhFUOxcdTAwMWMj/fZcXEF/rjP6+c5vXHUwMDAxXHUwMDAwMmX0MU6IXHSAXHUwMDE4hUcqINKHuYG7doaqXFy22IW4vTg+uH22XHUwMDBisFx1MDAwMnm2W4jLUNix7t9cdTAwMWOlbtLX9/x5NFx1MDAxONSfLmvxXHUwMDE4KvK8Nny9rlb5ef6QPZT3R0dYXGZcdTAwMGZy28h85tWLxXxe3nPL4oz3uMFcdTAwMGKjZIfGMC+MciRcdTAwMDWURi8sKkpcdTAwMDFcdTAwMDCTjLlcdTAwMTTiltLe3/f+0fl8o/9UVZcy+G5cdTAwMWEkoYHCXHUwMDA1XFxcdTAwMTVJg3/f899mlPVcdTAwMTlpJvtcdTAwMTSFXHUwMDA3hILzXHUwMDE0Rur5Q0gpZj6C3Fx1MDAxNykuIFwis1x1MDAxZLFgnFx1MDAwNKWQVKRJXHUwMDE551i58Vxiclx1MDAwM2BBSlx1MDAxObCQK7tKMMHQXHUwMDBlv8uyKaKAc86MXHUwMDFiboRcdTAwMDScwFx1MDAxOa5cdTAwMDVcdTAwMTecXHUwMDAxsU3Bla5j+9l6/re9uSRM/zH7+z//Yvz2frj46VdA8ObnXHUwMDBikGSr3Fx1MDAxZlx1MDAxYzrttj1QN3qtLzKgY1x1MDAwN+Xe4MDu1OxO3fv8fm5vxzF+p1xuqTrUXHUwMDBisFx1MDAwZlJcdTAwMDAqXHUwMDFmXHUwMDA1ICHVRVLE5namlqByV693XG5Bwels35pcdTAwMDdkxOrU5lx1MDAxN1x1MDAxNbzgtFx1MDAwNn3DKtf8n6rj3J+5zVxiv5aJXGLwuKN8XHUwMDExZp0gXHUwMDFjq1x1MDAwN1x1MDAwNeNI4odOezmtwJKddd6sPuc1ckDObGRvXHUwMDBmvoOa6yO27FxydWGevGabR6hccjlcZni+wJ+RXCKMiVRyM//ykqZFQla2KI/eXHUwMDE47FxcWHftyuuTLN/fXV7fbmDDPPK8v8Buh9mAdsHOXHUwMDEwc5/BTlG3IHpcdTAwMGIqNuzMj2nLYSdcXMv8XHUwMDAxO7o+2LnCc6ZQ3KclXHUwMDBlKMCIuvD4Rditi4+XNpqneGw5o2BcdTAwMTjKXHUwMDFjXHUwMDFi+pqh7TOpXHUwMDE3sILfpPZcXGckvFx1MDAwMlx1MDAwMaM5wphcZt29Uv6pgFxmg1iO01x1MDAwN8D20Vx1MDAwNFxcOlmULuJcdTAwMDfLseVJpZhcdTAwMTPbXHUwMDBlMO6S4+meqcsvT1x1MDAxYWCIXHUwMDE5XHUwMDAwXHUwMDA2XHTwI0zxn9DpZC6nJFx1MDAxOaM7IcLrXHUwMDE3TzO4cnRA73L7XHUwMDA1eHz6yo57OC4xta9cboPJ1Vx1MDAxOFx1MDAxZVx1MDAwZrPpt1eUzedeXHUwMDBipVx1MDAwNFxir1TvdigvTci4p1x1MDAxODVdXHUwMDFmXHUwMDBlO7m3X4vwqCtNxVx1MDAwN0fClCMrXHUwMDEwhrHRaH5IW45GP91R5P/tXHUwMDA06Vx1MDAwZVx1MDAxYqxMV1x1MDAwNO4zP4ZcdTAwMDOClLW7XGZcdTAwMTZ/Qbr7+z86+u+ByJM5XHUwMDFjlCjzLeCNUObzXXIk5Fx1MDAxNvh34Vx0huqZKFx1MDAxN1x1MDAxYon4wDtcdTAwMThlby5AXHUwMDA3XHUwMDFkXHUwMDFlNsdcdTAwMDcvd+SWXGbVc99y4Kkl8O/WXHUwMDEy5M5cdTAwMWVKnFx1MDAwYpGBXHUwMDBigz6eMkNcYqaM4e928sBRsZi9XHUwMDEwvfdiuXHcfbt7IJfnL3G5qeyMnIPK8KrSS2fPT/jTY7mUSYLzhjYqturoofKUyVx1MDAxZHWqd48vtcfCr8V5XHUwMDEw4vCseiSEQJzjOHvWUY9p+8HnzykniK1cdTAwMTN8IDrp4lx1MDAxM3zKweZcdTAwMDK6Y1u/J+7rOv1BaFx1MDAxNtxcdTAwMDY8vlx1MDAwNURcdTAwMTFIbDNcXO7qnMdIIOI+szWBsjMxQ/FRd1C5yljOsXPjXFxcdTAwMWZcdTAwMGWOXHUwMDFk++34NPu8/ajzpMd+lFfJtWHuS2VaP1x1MDAwMcm5clx1MDAxMVx1MDAwMEpcdTAwMGWPK5Jhb3KLx87jSVx1MDAxM1x1MDAxNE9bTDYrp/n05LvJcJTPl09cdTAwMWFHz7dcdTAwMDfZq/akWs0/pItXv1x1MDAxNlx1MDAxOVJcdTAwMTjKhVjo7EZcbuPboeaHtPWgZNhcdTAwMGZKub6dXHUwMDA2dz2lqUzrc6dcdTAwMDFgTDAnrvjNr0uEXjbTS+Sv4+lZ/YHTc1x1MDAxNyqZq4eSZcNoXHUwMDBlWVR7NLtmI/DiaJ6oZFx1MDAwMm2JplxikoJBqlS3O276Ya1cdTAwMDFDoVx1MDAwNUpBXHUwMDAyqaRcdTAwMTIpXHUwMDFilktkyL/zOF27lFx1MDAwMfdjXGJNXHUwMDE5UFx1MDAxZVx1MDAwMVZLaqxXXHUwMDAyLlvNp0BcdTAwMDWT6iFisk2JQElnXGaEyNv0Uy1q81NcdTAwMDRcdTAwMTgvsSSBaKt2b7b773rnM21cdTAwMDCkXHUwMDAwIeoxcaVqsUJcdTAwMTlnXHUwMDAxXHUwMDE58KRcdTAwMDSEXkKkKll0XHRcdTAwMThSQjmBXHUwMDE46axcdTAwMGKIg3KYVPJBXHUwMDFjJz5KK0lcdFNK5JFUeCBcZlx1MDAwMa9SokSkMFx1MDAxMFx1MDAxNFCqXFw44jI4Z1xuStKUpGqxifpcdTAwMDMwyFxyXHUwMDE5Tkrv6cgzI0JQqEszdupqOXVcdTAwMDW5kEqWkDFhXHUwMDFmYlx1MDAxMa6wlC2kN+DiWHy/pMJCXGaldO5cdTAwMTeBglxi9X/uVVhcdTAwMTCmiMRYZ+9hyVxi4mLhXHRD5Xl6woAoJ6lcdTAwMGX9OiBcdTAwMDFF6c6mklxu4Vx1MDAxMFx1MDAwM6pcXFx1MDAwMITw3Fx1MDAwZZ0rs5RcdTAwMDCMXHUwMDExwHROo3rKNJhOteGkKXVcdGGijSBmglx1MDAwM8HjezNnhFx1MDAxNq5cdTAwMGZcdTAwMGXIw/CAZo8qz9dHR6WH7VFcdTAwMTkhm8tU+JxcdTAwMTnlxK0zrlx1MDAwN1xycT1DUJ1BwqaIS8qdWTGOUCy2XHUwMDA2z1x1MDAwZs2nyUtZytd8/6RcXGqzLfTLlSyHJkpoe1x1MDAwYmNcdTAwMWOrrirqrrddlJk3/09p7TVcbvKiXHUwMDFh9U9BXHUwMDE2XGIyXHUwMDAxMPl9bs7qUma9w/kn/cfm49NcdTAwMGJUrqnw2ne1q7OHIKGpXHUwMDEwXHUwMDEwqUeBIUTxU/86qDh65s3O+dXoiVxm0eTo4u02s/WIXHUwMDBi7MgyideIOFx1MDAxYURcdTAwMWNcdGRDMFxilXVcdTAwMDFlYoGwXHUwMDE1maNmtUfg7KpWLmdunEPRXHUwMDE4XHUwMDE1x7nD2JVtt71b+5lcdTAwMWVcdTAwMTZL1mk+c1RqZ67LjV8rUszDU4UgXHUwMDE1XHUwMDAyXHUwMDEwgXl8fJhXc8vxIbDX3+VgnYzkqqyOICSkY4PKadtooJgsIWxfI6Spev/zRklogeo2JVx1MDAwN6krXFydePSedyiygFx1MDAxMFA5LTK+21x1MDAwMlx1MDAwN8W7dPMofdWynlx1MDAwZY/tYXnYLF1vO7I48bstXkcmcbfF2FwiJZh4Llx1MDAxOeNcdTAwMWPyxIy9XHUwMDE1ued8fEybtqhcdTAwMWOc0MbonmdPyPtV7FQgm1x1MDAxY9yfOaVcdTAwMTdr/FguvGbKnbP93kGi3LOMOliJe5TzXHUwMDFlmi5HMFx1MDAxNoyxJdLlzKu57VxiYd5yUu7ZtfymdFx1MDAxZMmBXHUwMDE0Slx1MDAwNDbpXGZtjnum6S/f6FxyLdDkxmydOO7Qz6z4wTMqXj1W+42y3bhcdTAwMDBcdTAwMGbnXbpfXHUwMDBmblx1MDAwMlx1MDAwNHtcckBcdTAwMDCBR1x1MDAxNKlrg/ZTgKjJeNk1I1xy6zXAmZSEXHUwMDAyXHUwMDEyiF/qs6PQmjRcdHXjYLRaQ8QktXrQbtPv/vAgbbW2oy3reVx1MDAxMFx1MDAwMaGB0zViMrTjqOcmXHUwMDAyttz0qiMpKqK2XHQqay3sSVFOXHSjy2S4vT10jnGLZ61BXHUwMDA1XHUwMDE1rs6uT+rD4uX2yHlY+NmfViog8v16kixlSHBwRVx1MDAxNWb93VxilcSd3LZVtU1cdTAwMTe5l6t692pQyp3l705Kk0unwbfRXGKTMtRcdTAwMDbTpUKSXHUwMDEzXHUwMDE5X7rNN73t0i283U5cdTAwMDVZ59ZcbnR1po7KXHUwMDE0g0j5Spjx30NcdTAwMDAgmCl2M+zs2Z23cs8uq6v7r40miC1QwX728F9qJMqiQ1x1MDAwMlxcXHUwMDA2ko3mwTb1KUax+v7/TMtcdTAwMTSn/fODQvXuXHUwMDFjv6fzhdb47OXM2nqsebClj5FkrVx1MDAxZVx1MDAwZo5VXHUwMDFkpLxNXGKQK7Ple1x1MDAwMlx1MDAwMpX8ZY+lr4qw0Vx1MDAxZmT549NcdTAwMTBcdTAwMTROYrdZs586k07m4jl3VK6g4aibr7dO00lcdTAwMDVcdTAwMDQkpFx1MDAwMK5/e5Tz0GJcdTAwMDJcYjGCXHUwMDAyi1htkKOWc9tcdTAwMTFC/bs1kqw1ZoZMfFx1MDAxNNwhhYhChGRyW/3RfCQhYUgsIXDL8JF5h3Su5L+hhmeBNjftkVx1MDAwNq73XHUwMDBizFx1MDAwNHFE57tppVxigPHNwMbTXHUwMDA1LZ9cdTAwMWa1XHUwMDA3zczr3fPTy9FRw9p6J1x1MDAwN0JBfchT+mituVx08bpcdTAwMTOp9Vx1MDAxN4C7XHUwMDFiaHxcdTAwMGY7XZXHMH/brTRQ57B8J+xCxz5cdTAwMWbEZaf+6T56z4yd8lx1MDAwM1x1MDAxZj3XOs7oqDNJtlnn+sPVIJh6OU9EU1x1MDAwNty01UlskJiXc+tBXCK9k1Ogpzl34lx1MDAxMGGm6UdcdTAwMDGIUFxmIaBMbLK8dHNcdTAwMDFrd9PL//iPjbLSXHUwMDAyRVx1MDAxZdWeU13pXHUwMDE3+FxiR3hKgCguYlwiVsvGn5nxgMrz++zxSZpcZp6Kt2+vxfb9eNuhRoG/gI3BtWItXHUwMDFlXHUwMDFkXHUwMDExqbOHXHUwMDEz3Fx1MDAxY1qRjVx1MDAwZeDLVemyUeO3XHUwMDE3XHUwMDE3h/vNm0zOOnbistGTzDt1WL3JXFxfYyuTO76/Q01cdTAwMWWPjVwiz3v0clu7ntxcZlx1MDAxZSRcdTAwMTnlSLr6UD1cdTAwMWW3fzGWwyS0klx1MDAwMFwiQokyXHUwMDA2cXzomVx1MDAxZtO2Q1x1MDAwZvvj3VxmrtVcdTAwMTI0pVtcdTAwMWJ8MImlxJQvXHUwMDE18v51eO6zXHUwMDFmz4Y5blx1MDAwMTmEdVxyXG7jt1x1MDAwZrFcdTAwMWZcdTAwMGbO2OT5XHUwMDFk3Z/mj5xitS+v2dt7cFx1MDAxYjZkXHUwMDFjXHUwMDEwxd6tWFxigoMhITJkNrt2XHUwMDFh/533YoPbPIbiu8/WJFx1MDAxOEHdmstYVVx1MDAxNdHWUPGgcppjOcVcdDHe2fhxcGbfj8ZcdTAwMDWb4PPTXHUwMDFiXFzsX/TiMlx1MDAxMypksq/3LTv34lx1MDAxNJ6bx1Y236BbxkxRd1x1MDAxOUBOMIFBXHUwMDE5JX73XHUwMDFkXHUwMDA2YUN2U4KWyWBghHLd38Q8LSHUUKBcdTAwMThcdTAwMTNE4mVcdTAwMGVvipM+aebI6SyXwrDRuUBcdTAwMGKIw89G05uJQFN02GVBp1x1MDAwMpyCXFxgXGawrp6U3k1Sd/bkvC0pXHUwMDBiOcI152BcdTAwMDc1I0GFl/1CXCKIQOaBJViGpqYgoFveUY7iRKQ2hcFk+1x1MDAxNGDiXHUwMDExNyq8db+ELTxDqMROP6WuS1xikGBifVx1MDAwZaJjLHvullx1MDAwMpAhzpiyV6VywSGBwerdeW+GjU4/iNM4aVFbXHUwMDE0j3pBNJh8OFx1MDAwN5iprminTtyLXHUwMDFjrk4ox1xcMFx1MDAwMMw5iVH2LmerzWj+JZSJ59v7ZCNjUFx1MDAxNrZcdTAwMTfxjEFBXGZcbiRcdTAwMTlFUi3avFB1XHUwMDA2ffh9wH/v9tN2v3rzbj/cVFx1MDAwZlx0bt2Nz0lcdTAwMTD45omD1GdYXHUwMDAwYJrFbigqhC5q26HfvdLh6MdcdTAwMDBxiEUguD790fBqXGaIKVGuXHUwMDAwXFzJoF91Km73spWvXHUwMDE07+BIqpXs9Vx1MDAxYaObXHUwMDA2iOvuXHUwMDE27LOW7D7et8jpoNe5zz11u93XXHUwMDA0XHUwMDAyvM2n91x1MDAxZWneT5x7Wm6mh6XC/XPmJt55XHUwMDAzemStbrR59Vx1MDAwMpA0zFx1MDAxY5T+bVx1MDAwN73hYlx1MDAxYf9OXGaE7CpcdTAwMTD4d4akceAuhpxcdTAwMTBMYSDF6Yfujlx1MDAxNF6Gi1x051x1MDAwNK5cdTAwMTZqWrcznW61XFzJkXv/6FjvVnWofsSV0rl6lcCXZlx1MDAwZc7vy+diL2Arv4vtu8X5XHJGoC96NGn0tEFf00pcdTAwMThkQ9M+3W787rJUXGI4Vlxc6N719PT/XHUwMDBiN4SF+o8yuk2xrWT9auj+OnZ1y/q6Kbx8d6voXHShe273XHUwMDE4IChcdTAwMTV+MGGIXHUwMDAxLJRfXHUwMDFmMJKRSFx1MDAwMcZcdTAwMTjkelx1MDAxMocyaYLSkaxlfMjesm/17lx1MDAxZHwjtXb2nL+e54/u4tCwcMnlh1x1MDAxZZjHf2axbEOdzK5cdTAwMWEvnIApQpyo/0zWb3D/x91cdTAwMDaDccDINlx1MDAwMf6Tf1x1MDAwN73hr1ePN73oXGLQRI8yjGZPZalcdTAwMDJCXGJVZlx1MDAxNkHCN8yeoFx1MDAwMIZAXG5JgFx1MDAwMdA9Xbn+z1R8ttsgWpZdXHUwMDE5ZUBcdTAwMTJ3Xz53hknovlx1MDAxMZdcdTAwMWND9Dum1lxicdMvxOdn+DrZhlHq5WW1+0D78LnSrlJWYYPTXHUwMDEyxiGUXG4kRUJcdTAwMTEqhpRL9afrcuPFncIuXCJ6TOGeN/hFmOSUYUZcdTAwMTBcdTAwMTZYsDjBr4SJ/OFcdTAwMDXuv1x1MDAxY9D+2aSHj1x1MDAwZl+bk+Px+34snURdXHUwMDE1K1x1MDAxZqlEc1x1MDAwN3DuS4uUwJzovuCAXHUwMDE4m8kzlJJKpymAUFx0XHUwMDE5dIVwdirJ/VxiQlVcdTAwMTLlut9cYsUm9jd0XHUwMDAxnzVcItGNXHUwMDAxJVlX99zv10nIXHUwMDFi+mZcdTAwMGJcdTAwMGZcYlx1MDAxNdaP4/1ymqRGXHUwMDBid1x1MDAxZtZcdTAwMTnejs5cdTAwMTaNwj5nPMVcdTAwMDHkUFx1MDAwYlx1MDAxZnXHXaeFxlxmpjBnTJmlXHUwMDE0K1xyXHUwMDE33PKCXHUwMDE0q+PVsYTq3D9cbkz9NyBcYjnLTjG4n09EJFx1MDAwMGFMaXCO0Vx1MDAxNP2hMXGsIEBcdTAwMTWIf696YVx1MDAxZmKeglxiQUpcdTAwMDBkSN2rr6228IpcdTAwMWRAi89cdTAwMTgqztOPXHUwMDAzkrxcdTAwMTnVXHUwMDExZqFEJ4Z6LVx1MDAxNEUtXGZcdTAwMTP13KighMO5XzS3UFxmO/ObbaSNIFx1MDAwZd3exUAwyfhcdTAwMTKtUFx1MDAwZuHFpFxizmnrXHUwMDFldvJcdTAwMTdPt3LYvC5tj4ZcYilfXHUwMDAyXHUwMDAyXHUwMDA0klx1MDAwNF1cdTAwMTHWxGd1YUOSrqGmgkGGXHUwMDEx+/Zcbr+zSa6Mc1xi2dBpPtw99Wuv1zD2POZ1bbmNLl56g0y60Dy06oUqPXWuqlx1MDAxN0nNY1ZIRcxcdTAwMTUlWFNNXHUwMDA1cimEQHhJUFx1MDAwMZWpJOPMk4l6TFtcdTAwMGY9iXzAW+NMZmxKXHUwMDE4MFW1c0EkXHUwMDE1S1xySF+9omJ5cZs/1C9Xtf9p9rfNV7cvIIvI6nb/da9OgFx1MDAxMIf3XFyDQqqHIyiIXHUwMDBmw/rgoVsrlZ/T7JE2L/MjIN9791tcdTAwMGZDXHUwMDE46Mlcblx1MDAwMV1cdTAwMWZcdTAwMTCN7cBccpMkdJ9pwcl3M+B7efJO+bh1e3V8bOVygkv4crNcdTAwMDGmijyvcz5cdTAwMTiObp/eXHUwMDBmT1x1MDAwZi6sa6vb4fmnSlJcZqiMXFzoSsFaV1UhQuFcZkiQ1FeB4vc+Mj+mrYde0PhcdTAwMDR0fcanO85cdTAwMWVZPK/cX2X5b6bR2PLy9jVcbpzTyGbbjS8gXHUwMDA3P+O5L3N1gkOQhVx1MDAxM5yeaSiwXHUwMDAwcVx1MDAwMpk/x1xc9qtcdTAwMTl1w6dAlN9cdTAwMDaFs8pr53Aotlx1MDAxZWWAXHUwMDA1XHUwMDFhKHlwl7ipaSopMzBcdTAwMWNSIINJmporUtxonKPFKmik5V3n1m6fTkpcdTAwMWQr9sSLL+Q/fiPFyU00L1PgXHUwMDBib17GKdGD0GF8ijM/pq1cdTAwMDdcdTAwMWb3Z49KT1elxMHn4rMoP1x1MDAwZulcdTAwMDFPjG3IzVta4L7o5ulcdTAwMTbhW+HnLWBcZmNv83U4eu6iXHUwMDAyf6STM1x1MDAwMsQy3WPeXvO0na4jkLux+EvhXHUwMDFjw9uLxrYjkVx1MDAxMz9cdTAwMGJCSOXacIhILDdcdTAwMGbpUmKM3OVcdTAwMWPf5Oe10vnbYmHYKYLjk0n/xXm/7Vc20OUl8rydtJOv347gdVxy3o1cdTAwMGVykk9qXHUwMDEzKyFcdTAwMTJkknCB1u/ncVx1MDAxY5FIh1x1MDAxOeBgme4x5se09dBcdTAwMGJEWOB872wzLW5cckM9dPJcdTAwMDMhdFP9O4nSwXRcdHFLklx1MDAwMr+jgedcdTAwMDKWiKa+yFx1MDAwZZ5xlE5kXHUwMDEyXHUwMDAy0XN/XHUwMDExXHUwMDA0XHUwMDE0sWlcdTAwMWGXTzY5S+mESS5cdTAwMTmSXHUwMDFjXHUwMDE56vqVYZtChEjEiJ5Jplx1MDAwZVxiXG5cdTAwMWNFyHOWXVx1MDAxYfKyWVxiatH0XHUwMDA0dlNdLqWh5VxiXHUwMDE4ScjoOoqDJIXEVVx1MDAxMfh9aVxihKSQXHUwMDEyP4CQ4FxcQOhNXHUwMDFh0N6+svcoXHUwMDEyXHUwMDAwSkAxX3zCUHGeflx1MDAxY5Dk+Vx0XHUwMDAzLJtYSma09tjz5Fx1MDAxYXBcdTAwMDJcdMdYj+hcdTAwMTaQQVe1xqxcdFx1MDAwMFx1MDAwMVxcKFxyjJkgXGJcYlx1MDAxOOx+lGxcdTAwMDJcdTAwMDRQ66lcdTAwMTCMpX5cYlx1MDAwNGDAg1x1MDAxOZokpWd1fL6EYIFrSjhrK3o/OkphSkZSQImS0Fx1MDAxNUJCYN++JWAyxbm+XVwiuJJcdTAwMTZDby5I1d1cdTAwMTJcdTAwMDZcdTAwMTHjSJE/NzipTH1FibaQTJlFhLKdwlxcNsVcXJdyYFx1MDAxMuzJqz9cdTAwMGLPdFF8p+RPoWKVxK2I6Up6b9ddWvJd+pKilDSKnn4xpUmlVIYgRIQrR3hhh1x1MDAxNMpcIs5G+bKnXHUwMDBiXHUwMDA3xvTq/JDYhOaFzVx1MDAxYpS+T9euXHUwMDFlryuZcd2B3XZ3YtS8MEWwpFx1MDAxY1x1MDAwYqo8WCVApjR0XHUwMDE0slwiS2re6P3y+c+ZXHSCXG6gXHUwMDA3W+tcdTAwMTXkXHUwMDAwkbVcdTAwMTe5RVx1MDAwN4Uj+75cdTAwMDDmLXNTa6d0qy7iXHUwMDEzXHUwMDEy68KroG5cdTAwMDVUXHUwMDExvpRcXIGOqpsztUGkQklcdTAwMTnEUiiTSomv3OnWZY1RpVxcicBcdTAwMDSakuVZeJeYqckkXHRMvIDnXHUwMDBiIdVkrVHiyXbX+lQhnSk5Jlx1MDAxMOqU1sUn8KpQ7DlcdTAwMDNZeHi4+E9P51x1MDAxN/xNaNDooO+eK1x1MDAwMdZoOjKsXHUwMDBijFx1MDAwNOaQSUJWNFWja/r3fNVEYmq96V1vZVx0MJNcdTAwMDFccrl6JFxuXHUwMDAxSn1CpfPXrUGjM0eiO2dhnuKY64GRyldcdTAwMDH+9vzQ1D8gRTlSmldcdTAwMDFcdTAwMTkqo1x1MDAxY1x1MDAwMlOewK6MYOnOWnp7l6JgXHUwMDAwVCvGYFx1MDAwN835pGOl2mjybfqmvVxyxPc78OHSpl/7KNEs/5Vcciq3flBOnWD6XHUwMDBm7dwxXHUwMDFj1Fx1MDAwZiuWPEbnhHiUXCJQdqYuViPahGOu8E7ENSSsk6K35Fx1MDAxN+gkmdL1v1x1MDAxODBlLit73+dAk2AvXHUwMDAzXHUwMDFkc+FUfZ1TobwzXCJNc9vcaVx1MDAxYzut5H5cZuFaSSojQVBmXG4rSteP+r1kKZT2UFxuLU467JJqXHRw92jjb7PkwiVu+rFcdTAwMTa2TWim2Epharlo45phouxcdTAwMTemXHUwMDFlT1ArXHUwMDA05SCWZlpowrkrwrlUvjthQGfsKuuJXHUwMDA3LmLtqim6mXnkXlxi8u1+oKB5xFxmXHUwMDFl5a5t8ZJ6XHUwMDA3M6yseWR0IEW42lx1MDAxMXxabc2S386YVlVugdphXCLk+19XMcuXOEZ3XHUwMDFi9ylcdTAwMWaGhfLuXHUwMDA1R5josu+g8jFcZllIXHUwMDE29ye2cpFf7Fr2qZHLjTL9x/ptNp5JXCKYXHUwMDE398TV/zairVx1MDAxYdx5QUt3XHUwMDE4VVx1MDAxZbXyRYXJ4CDhUXmOXGKF2jP/3Vx1MDAwMt/j9sCNeD2xe6xMW6xgXGYkpkhcdTAwMGaiXHUwMDE2XHUwMDA2dK/o9EDiSIdcdTAwMWPcWE5cdTAwMWWme9A6e2o/n4d7XoBcdTAwMTGkbDLlXHUwMDBmXHUwMDBiXHUwMDFhx/FKWMO0r1xug8nVXHUwMDE4XHUwMDFlXHUwMDBms+m3V5TN515cdTAwMGKlmFx1MDAxYcbb5oWwYJtcdTAwMTdDw1S0a5i6bFx1MDAxZlx1MDAxN0xcdTAwMDTCWJjaNVx1MDAwNJs5zlwi05hcdTAwMDNcdTAwMGVo8v7MtuhcdTAwMTfPt5GrXHUwMDE3w/rUyz6agEsni9JF/GA5tjypXHUwMDE0c2Feg1wibsBcdTAwMDQhOs6tXGaJ+a7tXHUwMDFj2zglXHTlXHUwMDAwKq+MMO3pRCucdXZ8KdW7XHUwMDFkyktcdTAwMTMy7tWOrtP14bCTi1x1MDAxN4/1q1x1MDAwMWaYQWRcbrfuXHUwMDFjjKW7tkCqi1x1MDAwNqnJw+Ch+VJCXHUwMDEwKlx1MDAxOMBratvy/XrAY2agb/YvYmtcYk3+WOexybmmXGJqXGKSwlQo/U/Vo8eIg7U7XHUwMDFj+LZ3az/Tw2LJOs1njkrtzHW5sZpcdTAwMWXgwd6TOz2QRKCBYIKQeZ5cdFx1MDAwYjVcdTAwMDcgwVx1MDAxNENPR66dXHUwMDFlWJ9cdTAwMWXooOLomTc751ejJzJEk6OLt9tMmFx1MDAxZVx1MDAwMIIhpVx1MDAwMSSjVLuFsVpOJoz7l8b4snl1U3hrVcDg8OHw3MrCVlx1MDAxMPfBts1Meid3iqBcdTAwMTNATT3cdlx1MDAxOSqhXZuxXHUwMDA0kiBpzItcdTAwMGW8Od+/kHpiXHUwMDExpdvE87NBt/pd13P5Nbo2f1xcdVx1MDAwNGhscnB/5pRerPFjufCaKXfO9ntcdTAwMDcxydJbL8OxoVxiIcVcdTAwMDXgXHUwMDA0IMFcdTAwMTjDXFxQQ5RcdTAwMWXB3Vx1MDAwNuHydjRhSFx1MDAxMlx1MDAxMJz6rY+KXHUwMDE4S6KVNIVryPXaXHUwMDE2XHUwMDA23Vx1MDAwZlx1MDAxNzn9QjDZXHLC5VlcdTAwMTVcdTAwMGWKd+nmUfqqZT1cdTAwMWRcdTAwMWXbw/KwWbpcdTAwMGUmXqFcdTAwMTTwvtyE6tldhOqBQqLLLDBHWKyxYeGHxiBt+8Qqllxu+2J0VrtcdTAwMWY3m+yuNohHs96UXHUwMDAygYJcdTAwMTlcdTAwMDU7nl2KZ1x1MDAxMeBKXHUwMDBiSGNYzT3s1a9cdTAwMDZcdTAwMTCTXHUwMDE4M7iuMaM7ot1cdTAwMGLCZtItXGZB7cWy7kD9+qxylW/dyphESzy44dLQi9xcdTAwMDCb3ZjNJVlVQC5cdTAwMDTA5p7C4Vx1MDAxNdJcYlx1MDAxMIrWNmVzXHUwMDFiKNXDoIlO2fye3uCWrFrj0sX9oSztn1WuXyb3znO82lx1MDAwN+XlerAogWE6SVx1MDAxMIu7kbdLY1Fwhlx1MDAxMFx0OovTRVx1MDAwZsVcIlx1MDAwNEjq4uht8iDXt2OU7MTb78Gi/dSZdDJcdTAwMTfPuaNyXHUwMDA1XHJH3Xy9dZpeXHKLYi6hUZu3u7lAK6SjMoApNHmbXHUwMDEwh3ftwXquXHUwMDFixGsoc4dbUVjk3b7dyFx1MDAxOKCeOO2fXHUwMDFmXHUwMDE0qnfn+D2dL7TGZy9n7nYznqRP5fxiJpV/wCFcIq7chr0tXHUwMDE4P21dOPvNqzNayjSv65XO2GHDM8O8MMOQPe6tlpEgXGL73ZC9pdxIXHUwMDA2JNOzXHUwMDEwTPhcdTAwMGWGmGZzvyhUWiFeXHUwMDBm9E07kb/LXHUwMDExe527trBE44JWut2MLc6dw/bBWSzI+KdZ7SDzVchcYj1hhbhcdTAwMTcy3iBaQNTDkFKuKfKyUo/e3zdoXCL7s0Xbl9iDXHUwMDFhXGKRaVx1MDAxNKWyjiRV0lx1MDAwMIWySVx1MDAwMFx1MDAxM6bhIbtcco4lTU5cdTAwMDQ4kIBcdTAwMDBjolx1MDAxMFxmou5cdTAwMTNfXGYxXWeTPLqW7sS3XHUwMDFli3M/Qt70XHUwMDBif/PuxjItkVx1MDAwMFx1MDAxNEKZqFx1MDAxNEMoiNKZsSqjXHUwMDEytkJPSXZ8lreHpUH9/Vx1MDAwMlx1MDAxN9uVq2pzXHUwMDFji1JdzmZgMMqOU1fjVI1ehXpTzFx1MDAwN4VmXHJcdTAwMTBCgW58saYtzVx1MDAxZKP6MVx1MDAxM918J1x1MDAwMjOS+muKRZBQdzuAS2GG4mmrJ2nsX1x1MDAxMFx1MDAxZSdV3p6kXHUwMDEy4FV8t5DuvV9cdTAwMWUoXHUwMDE24Ks1XHUwMDAw8tffXFz8w89cdTAwMTX6Ue52XHUwMDBiXHUwMDAztT4zNv7xZlujg6C0//F5+tLHT1x1MDAwMa0l3ZraY7/94bf/XHUwMDA3y588PSJ9 Chain snapshots createdpre_sequence() ⚡sequence_num = 0sequence_num < sequences_countflow_num = 0flow_num <flows_countpost_sequence() ⚡Chain snapshots restoredpre_flow(flow) ⚡flow()post_flow(flow) ⚡falseRun invariants?pre_invariants() ⚡sequence_num++flow_num++DoneAll invariants executed?truepre_invariant(invariant) ⚡invariant()post_invariant(invariant) ⚡post_invariants() ⚡falsefalsetruetruetruefalse

Example#

Putting all of the above together, here is an example of a FuzzTest that tests the Counter contract:

from wake.testing import *
from wake.testing.fuzzing import *
from pytypes.contracts.Counter import Counter


class CounterTest(FuzzTest):
    counter: Counter
    count: int

    def pre_sequence(self) -> None:
        self.counter = Counter.deploy()
        self.count = 0

    @flow()
    def flow_increment(self) -> None:
        self.counter.increment()
        self.count += 1

    @flow()
    def flow_decrement(self) -> None:
        with may_revert(PanicCodeEnum.UNDERFLOW_OVERFLOW) as e:
            self.counter.decrement()

        if e.value is None:
            self.count -= 1
        else:
            assert self.count == 0

    @invariant(period=10)
    def invariant_count(self) -> None:
        assert self.counter.count() == self.count


@default_chain.connect()
def test_counter():
    CounterTest().run(sequences_count=30, flows_count=100)

The test performs 30 test sequences, each consisting of 100 flows. It tests with two flows of the same probability: flow_increment and flow_decrement. The invariant invariant_count is executed after every 10 flows.

Generating random data#

There are two ways to generate random data in Wake fuzz tests.

Warning

Do not use the random module from the Python standard library to generate random data in fuzz tests. Instead, use random defined in the wake.testing module.

Flow arguments#

Every flow function can accept additional arguments to the implicit self. These arguments are generated based on the type hints:

@flow()
def flow_set_count(self, count: uint) -> None:
    self.counter.set_count(count, from_=self.counter.owner())
    self.count = count

Flow argument types can be any of the following:

  • integer types ranging from uint8 to uint256 and from int8 to int256, including uint and int,
  • byte types ranging from bytes1 to bytes32, including bytes and bytearray,
  • List, including List1 to List32 helper annotations (e.g. List16[uint8]),
  • bool,
  • str,
  • Address, does never generate the zero address,
  • any Enum, including enums generated in pytypes,
  • any dataclass, including dataclasses generated in pytypes.

All flow arguments are generated non-biased, i.e. the probability of generating a value of a given type is the same for all values of that type. For types that have length, the length is generated in the range 0 to 64.

For generating fine-tuned random data, it is recommended to use the random functions from the wake.testing.fuzzing module.

Random functions#

Additionally to the methods provided by the standard random module, Wake testing framework provides a set of random functions that can be used to generate random data.

Warning

Never import the standard random module in Wake tests.

from wake.testing import *

import random  # never do this

Wake already provides a custom isolated random instance that can be imported from wake.testing.

random_account()#

random_account() returns a random account from a given chain. It accepts the following keyword arguments:

Argument Description Default value
lower_bound lower bound index of chain.accounts to choose from 0
upper_bound upper bound index of chain.accounts to choose from None (i.e. len(chain.accounts))
predicate predicate that the account must satisfy None (i.e. no predicate)
chain chain to choose the account from default_chain

random_address()#

random_address() returns a random address. It accepts the following keyword arguments:

Argument Description Default value
zero_address_prob probability of generating the zero address 0

random_int(min, max)#

random_int(min, max) returns a random integer in the range min to max. It accepts the following keyword arguments:

Argument Description Default value
min_prob probability of generating min None (i.e. 1 / (max - min + 1))
max_prob probability of generating max None (i.e. 1 / (max - min + 1))
zero_prob probability of generating 0, if min < 0 < max None (i.e. 1 / (max - min + 1))
edge_values_prob value to use for min_prob, max_prob and
zero_prob if not set
None

random_bool()#

random_bool() returns a random boolean value. It accepts the following keyword arguments:

Argument Description Default value
true_prob probability of generating True 0.5

random_string(min, max)#

random_string(min, max) returns a random string of length in the range min to max. It accepts the following keyword arguments:

Argument Description Default value
alphabet alphabet to choose characters from string.printable
predicate predicate that the string must satisfy None (i.e. no predicate)

random_bytes(min, max)#

random_bytes(min, max) returns a random byte array of length in the range min to max. If max is not specified, it generates exactly min bytes. It accepts the following keyword arguments:

Argument Description Default value
predicate predicate that the byte array must satisfy None (i.e. no predicate)

Launching tests in parallel#

Wake testing framework allows running the same test in parallel with different random seeds. Multiprocess tests are launched by setting the -P flag specifying the number of processes to be used:

wake test -P 3 tests/test_counter_fuzz.py

If a test process encounters an error, the user is prompted whether to debug the test or continue testing. While debugging, other processes are still running in the background.

By default, nothing but status of each test is printed to the console. Using the --attach-first flag, the output of the first process is printed to the console. Standard output and standard error of all processes are redirected to the .wake/logs/testing directory.

Reproducing a failed test

For every process, Wake generates a random seed that is used to initialize the random number generator. The seed is printed to the console and can be used to reproduce the test failure:

wake test tests/test_counter_fuzz.py -P 5 -S 62061e838798ad0f

A random seed can be specified using the -S flag. Multiple -S flags are allowed.

Non-deterministic tests with set

Python built-in set is an unordered container. Given the unordered behavior, the following code will lead to different fuzz test results with the same random seed:

items = {1, 2, 3}
item = random.choice(list(items))

It is highly recommended to use OrderedSet in fuzz tests instead of the built-in set.