Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not flood dictionary with data dependent on fuzz inputs #7552

Merged
merged 3 commits into from
Apr 4, 2024

Conversation

klkvr
Copy link
Member

@klkvr klkvr commented Apr 3, 2024

Motivation

Currently data collected during individual fuzz/invariant run (e.g. logs, storage writes) persists in fuzz dictionary resulting in large dictionary sizes due to it being flooded by data dependent on fuzz inputs. In #7551 case it resulted in dictionary accumulating log topics which directly contained fuzz inputs thus decreasing a chance of picking actually useful value from push byte as a fuzz input.

Solution

For fuzz tests we now don't call collect_state_from_call at all making calldata choice completely stateless.

For invariant tests I've refactored EvmFuzzState to keep track of all newly added keys and an option to remove them via revert fn. Thus, on invariant runs we accumulate and persist dictionary between separate calls, but it is getting cleared after each invariant sequence run.

Setters now also don't let dictionary size to grow beyond configured limit which didn't really work before

Results

This does not directly resolv #7551 because in this exact case fuzz-test has a structure with many branches and a chance to reach a branch which should've caused failure is relatively low because most of the runs are getting caught earlier.

For the sake of experiment let's remove some of the branches to increase chance of hitting this exact failure:

function test_initialize(PoolKey memory key0, uint160 sqrtPriceX96) public {
    vm.assume(key0.currency0 < key0.currency1);
    vm.assume(!key0.fee.isDynamicFee());
    key0.tickSpacing = int24(bound(key0.tickSpacing, manager.MIN_TICK_SPACING(), manager.MAX_TICK_SPACING()));

    // Assumptions tested in Pool.t.sol
    sqrtPriceX96 = uint160(bound(sqrtPriceX96, TickMath.MIN_SQRT_RATIO, TickMath.MAX_SQRT_RATIO - 1));

    // tested in Hooks.t.sol
    key0.hooks = IHooks(Constants.ADDRESS_ZERO);

    if (
        (key0.fee & SwapFeeLibrary.DYNAMIC_FEE_FLAG == 0) && (key0.fee & SwapFeeLibrary.STATIC_FEE_MASK >= 1000000)
    ) {
        vm.expectRevert(abi.encodeWithSelector(SwapFeeLibrary.FeeTooLarge.selector));
        manager.initialize(key0, sqrtPriceX96, ZERO_BYTES);
    } else {
        vm.expectEmit(true, true, true, true);
        emit Initialize(key0.toId(), key0.currency0, key0.currency1, key0.fee, key0.tickSpacing, key0.hooks);
        manager.initialize(key0, sqrtPriceX96, ZERO_BYTES);

        (Pool.Slot0 memory slot0,,,) = manager.pools(key0.toId());
        assertEq(slot0.sqrtPriceX96, sqrtPriceX96);
        assertEq(slot0.protocolFee, 0);
    }
}

On latest nightly, forge is not able to catch a revert after 100.000 runs.
On this PR's version, revert is catched on 9261 run.

This benchmark is dependent on exact test and seed and this is kind of an edge case as this test suite results in 7000 push bytes being pre-collected after setUp, but I believe that cleaning up fuzz dictionary is reasonable anyway.

@klkvr klkvr changed the title fix dictionary fix: do not flood dictionary with data dependent on fuzz inputs Apr 3, 2024
@grandizzy
Copy link
Collaborator

grandizzy commented Apr 3, 2024

this rang a bell re #7462 which I cannot reproduce with this PR anymore (reproducible with master and UniStaker repo by reverting commit uniswapfoundation/UniStaker@1573451 that workarounds this issue)

@grandizzy
Copy link
Collaborator

grandizzy commented Apr 3, 2024

I used PoC #3607 (comment) to collect and visualize some stats on test_initialize (revert caught locally after 25098 runs), it can be seen that with PR there are less unique values (as dictionary not flooded) and number of times a fuzzed value is picked better distributed than without PR, attaching here, hope it makes sense

Without PR
masterstats

With PR
prstats

@Evalir
Copy link
Member

Evalir commented Apr 3, 2024

hey @grandizzy thanks! an aside: how are you getting these metrics? I think you dropped the info in another issue or PR, but we should document further

@grandizzy
Copy link
Collaborator

grandizzy commented Apr 3, 2024

hey @grandizzy thanks! an aside: how are you getting these metrics? I think you dropped the info in another issue or PR, but we should document further

hey @Evalir , tagged you in the metrics issue #3607 (comment)

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this makes sense to me, but would appreciate another look @DaniPopes

@@ -19,16 +19,116 @@ use std::{fmt, sync::Arc};
/// A set of arbitrary 32 byte data from the VM used to generate values for the strategy.
///
/// Wrapped in a shareable container.
pub type EvmFuzzState = Arc<RwLock<FuzzDictionary>>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, one less type alias 🙏

@klkvr klkvr requested a review from mattsse April 3, 2024 22:22
Copy link
Member

@DaniPopes DaniPopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@Evalir Evalir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gg

@klkvr klkvr merged commit 1631c5c into foundry-rs:master Apr 4, 2024
19 checks passed
klkvr added a commit that referenced this pull request Apr 6, 2024
mattsse added a commit that referenced this pull request Apr 9, 2024
* chore: make cast use an alloy provider

* move initial methods to alloy

* feat(`foundry-common`): NameOrAddress ENS util (#7122)

* feat(foundry-common): NameOrAddress ENS util

* chore: rename err

* chore: remove from impl for str

* chore: unrelated fix from alloy upgrade

* nit

* feat(`cast`): Move non `tx` methods to alloy (#7129)

* chore: add alloy contract

* feat(cast): migrate most methods to alloy

* chore: leave todo for converting a tx envelope into an rpc tx

* fix: use proper type for storage

* readd decodetx for now

* chore: extend txbuilder to build an alloy tx request

* feat: migrate most methods bar send/decode raw tx

* fix: include tx data

* simplify txbuilder

* chore: simplify back access_list

* chore: remove unnecesary conversion

* fmt

* doctests

* fmt

* do not use trait

* Update crates/cast/bin/main.rs

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>

* cleanup builder

* clippy

* fix doc comments

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>

* DocumentMut

* wip

* wip

* wip: bump alloy

* wip

* wip

* wip

* [wip] migrate to alloy providers and signers (#7425)

wip

* fix wallets after alloy bump

* clean up deps

* use serde on consensus types

* update TypedTransaction for anvil

* make anvil compile

* wip: make script compile

* fix script

* make forge compile

* fix: anvil tests

* bump alloy

* fix tests

* fix tx builder

* fix cargo.toml

* fix cargo.toml

* fix script gas price logic

* remove ethers from anvil

* clippy

* rm all_derives

* deps

* fmt

* fix tests

* configure clippy

* clippy

* add feature

* fix cargo deny

* fix persist

* fix doctests

* fmt

* fix clap

* review fixes

* fmt

* bump alloy

* Update cargo.toml

* fmt

* fixes

* ethers clean-up

* fix(fmt): fix indent closing parenthesis enclosed in { } (#7557)

* fix(fmt): fix indent closing parenthesis enclosed in { }

* Fix testdata bad formatting

* feat(test): only compile files needed for tests (#7334)

* feat(forge test): only compile files needed for tests

* remove comment

* clippy

* update fixtures

* getCode + getDeployedCode updates

* fixes

* fix path matching

* clippy

* add config flag

* fix

* docs

* fmt

* patch compilers

* fix Cargo.toml

* update patch

* update patch

* doc

* rm space

* cargo cheats

* new output selection fn

* log compiler errors on failure

* fixes

* fix: do not flood dictionary with data dependent on fuzz inputs (#7552)

* fix dictionary

* clippy + fmt

* fix

* Feat: Index cheatcode for Strings (#7539)

* feat: index cheatcode

* some nits to make it work

* nit: use as_str()

* final changes

* chore: reviewed changes

* chore: reduce logs in tests (#7566)

* fix(script): decode custom error in script fail message (#7563)

* clippy

* bump alloy

* AnyNetwork

* bump alloy

* add comment

* clippy

* bump alloy

* fixes

* refactor cast logs to use alloy (#7594)

* refactor cast logs to use alloy

* fmt

* make clippy happy

* cleanup

* doc nits

---------

Co-authored-by: evalir <hi@enriqueortiz.dev>

---------

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com>
Co-authored-by: Krishang <93703995+kamuik16@users.noreply.github.com>
Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
Co-authored-by: bernard-wagner <bernard-wagner@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants