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

Refactor to return errors instead of panic #99

Closed
dpaiton opened this issue May 15, 2024 · 0 comments · Fixed by #113
Closed

Refactor to return errors instead of panic #99

dpaiton opened this issue May 15, 2024 · 0 comments · Fixed by #113
Assignees

Comments

@dpaiton
Copy link
Contributor

dpaiton commented May 15, 2024

TL;DR

Our use of both panic and result results in a worse developer experience, and should be changed to using Result everywhere.

The problem

Per the Rust docs:

You could call panic! for any error situation, whether there’s a possible way to recover or not, but then you’re making the decision that a situation is unrecoverable on behalf of the calling code.

While it was reasonable to set up panics in the MVP stage of this repo, it has matured enough now that we should take care to consider developer experience with error handling. The current hybrid workflow uses panic and Result, which is difficult to manage.

Examples

We have stuff like this:

            let max_long = match panic::catch_unwind(|| {
                state.calculate_max_long(U256::MAX, checkpoint_exposure, None)
            }) {
                Ok(max_long) => match max_long {
                    Ok(max_long) => max_long,
                    Err(_) => continue,
                },
                Err(_) => continue,
            };

Where we have to catch both a panic and a result to handle a type <Result, Result<FixedPoint, Report>, Report>.

Another situation is here:

            // Need to catch panics because of FixedPoint.
            let actual = panic::catch_unwind(|| {
                state.calculate_max_long(
                    U256::MAX,
                    checkpoint_exposure,
                    Some(max_iterations.into()),
                )
            });
            match chain
                .mock_hyperdrive_math()
                .calculate_max_long(
                    MaxTradeParams {
                        share_reserves: state.info.share_reserves,
                        bond_reserves: state.info.bond_reserves,
                        longs_outstanding: state.info.longs_outstanding,
                        long_exposure: state.info.long_exposure,
                        share_adjustment: state.info.share_adjustment,
                        time_stretch: state.config.time_stretch,
                        vault_share_price: state.info.vault_share_price,
                        initial_vault_share_price: state.config.initial_vault_share_price,
                        minimum_share_reserves: state.config.minimum_share_reserves,
                        curve_fee: state.config.fees.curve,
                        flat_fee: state.config.fees.flat,
                        governance_lp_fee: state.config.fees.governance_lp,
                    },
                    checkpoint_exposure,
                    max_iterations.into(),
                )
                .call()
                .await
            {
                Ok((expected_base_amount, ..)) => {
                    assert_eq!(
                        actual.unwrap().unwrap(),
                        FixedPoint::from(expected_base_amount)
                    );
                }
                Err(_) => assert!(actual.is_err() || actual.unwrap().is_err()),
            }

Where we have to check if either the FixedPoint or the hyperdrive-math function errored to compare against a single (flat) error from Solidity.

Solution

We should use return Err(eyre!("message")); for all errors from the lowest level (FixedPointMath) on up. Again, quoting the docs:

However, when failure is expected, it’s more appropriate to return a Result than to make a panic! call.

Since e.g. underflow & overflow errors are expected to happen often (e.g. when you are producing a negative FixedPoint) we should return a Result and let the user decide how to handle it.

@dpaiton dpaiton changed the title Refactor fixedpoint to return errors instead of panic Refactor to return errors instead of panic May 21, 2024
@dpaiton dpaiton self-assigned this May 21, 2024
@dpaiton dpaiton linked a pull request May 24, 2024 that will close this issue
dpaiton added a commit that referenced this issue May 27, 2024
# Resolved Issues
#99
#64
#65
#66

# Description
Our use of both panic and result results in a worse developer
experience, and should be changed to using Result everywhere possible.
This PR removes almost all `panic` uses in the core libs; where
functions once would panic but return a type `T` they now return an
error with the type`Result<T>`. I also removed `unwrap()` statements,
which convert `Result` to `panic`, in most places around the repo.

Exceptions were made for core (anything with sugar like `+`, `-`, `*`)
FixedPoint arithmetic (thus keeping any U256 panics), certain
test-related cases, and macros.

I also improved some error messaging (in bindings mostly, but also
elsewhere), added comments, & added a lint ignore.

I ran the fuzz testing with 10k `FUZZ_RUNS` and 50k `FAST_FUZZ_RUNS`
twice without error locally.
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 a pull request may close this issue.

1 participant