-
Notifications
You must be signed in to change notification settings - Fork 0
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
Improved Testing of the Rust SDK #20
Comments
calculate_max_long
and calculate_max_short
I may have fallen down a rabbit hole trying to understand some errors 🕳️🔍 TL;DR
The Rabbit HoleI pulled the latest on
Specifically, this line was panicking about 14% of the time from an hyperdrive-rs/crates/hyperdrive-math/src/long/max.rs Lines 214 to 215 in 3360daa
So, I refactored the function to return a fn absolute_max_long(&self) -> Result<(FixedPoint, FixedPoint)> {
// ...
let effective_share_reserves = self.effective_share_reserves();
if effective_share_reserves > target_share_reserves {
return Err(eyre!(
"Effective share reserves exceed target share reserves."
));
}
// ...
Ok((absolute_max_base_amount, absolute_max_bond_amount))
} And in the test: -let actual = panic::catch_unwind(|| state.absolute_max_long());
+let actual = state.absolute_max_long(); This fixed the
When dividing a hyperdrive-rs/crates/fixed-point/src/lib.rs Lines 174 to 176 in 3360daa
If the number is big enough, scaling the number can overflow the let number = fixed!(52413673737912410055885968403491055370236425929284030968784.659601181251437610);
let scaled = number.0 * fixed!(1e18).0 // panics because 52413673737912410055885968403491055370236425929284030968784659601181251437610000000000000000000n > U256::MAX This means that a hyperdrive-rs/contracts/src/libraries/FixedPointMath.sol Lines 25 to 37 in 3360daa
Like #[proc_macro]
pub fn result(input: TokenStream) -> TokenStream {
let expr = parse_macro_input!(input as Expr);
quote!(
std::panic::catch_unwind(|| #expr).or_else(|panic_info| {
let panic_message = if let Some(s) = panic_info.downcast_ref::<String>() {
s.as_str()
} else if let Some(&s) = panic_info.downcast_ref::<&str>() {
s
} else {
"Operation failed with unknown panic"
};
Err(eyre::eyre!("Operation failed: {}", panic_message))
}))
.into()
} And, in the pub(super) fn solvency_after_long(
&self,
base_amount: FixedPoint,
bond_amount: FixedPoint,
checkpoint_exposure: I256,
) -> Option<FixedPoint> {
result!({
let governance_fee = self.open_long_governance_fee(base_amount);
let share_reserves = self.share_reserves() + base_amount / self.vault_share_price()
- governance_fee / self.vault_share_price();
let exposure = self.long_exposure() + bond_amount;
let checkpoint_exposure = FixedPoint::from(-checkpoint_exposure.min(int256!(0)));
if share_reserves + checkpoint_exposure / self.vault_share_price()
>= exposure / self.vault_share_price() + self.minimum_share_reserves()
{
Some(
share_reserves + checkpoint_exposure / self.vault_share_price()
- exposure / self.vault_share_price()
- self.minimum_share_reserves(),
)
} else {
None
}
})
.unwrap_or(None)
} This fixed the specific issue but the test still fails at the next unwrapped operation that overflows. To fully address the problem, all the functions that perform arithmetic operations on Alternatively the pub fn div_down(self, other: FixedPoint) -> FixedPoint {
let scale = uint256!(1e18);
let mut adjustment: u64 = 1;
// Dynamically adjust the scale to prevent overflow.
while let (_, true) = self.0.overflowing_mul(scale / adjustment) {
adjustment *= 10;
}
let truncated_value = self.mul_div_down(FixedPoint(scale / adjustment), other);
FixedPoint(truncated_value.0 * adjustment)
} Example: let number = fixed!(2410055885968403491055370236425929284030968784.659601181251437610);
let divisor = fixed!(1.226983151123229214);
// expected value (target precision): 1964212698244586656204108415688430900380716129.257078308393140637
// actual value (adjusted precision): 1964212698244586656204108415688430900380716129.257078308393100000 This avoids having a These are both pretty big changes, so before going further, I'd like to get a sanity check on the direction I'm heading in and look deeper into the cause of the original error in Phew! 🐇🕳️🔍 |
FYI I ended up touching on some of these changes in this pr: |
Todos:
|
PRs that work towards this goal:
|
I bumped up the assignees on this. I'm happy to manage it, but it's a broad enough task that everyone should try to contribute tests as they can & be aware of how their current efforts can influence the progress of this (e.g. make sure you're hella testing new features!) |
Duplicate issues: I'm reproducing here and closing it: There are limited tests for rust Open long/short doesn't fuzz against solidity. |
I think this is the move. We should refactor fixedpoint to return results instead of panic #99 This would clean up a lot of testing code. |
# Resolved Issues Partially resolves these two: #88 #45 # Description The fee rounding behavior was adjusted recently to more closely match solidity, but the order of operations was not exactly the same. This caused an occasional slight variation in output (usually off by 1e1), that compounded in situations where we were iteratively assessing fees such as `max_long`. In that case, the error would grow to as much as 1e5. During my effort to find this bug I - modified some math to use the e.g. `.mul_down` syntax instead of `*` where it was sufficiently complicated to require this to easily pattern match against Solidity. - modified `calculate_max_long` to return errors instead of panic. This makes checks in the tests ugly, but that is a temporary problem until we purge the panics all-together (#20) - purged the use of `traced` and `trace_test` which is helpful for debugging but was not actively being used and can slow down tests. # Review Checklists Please check each item **before approving** the pull request. While going through the checklist, it is recommended to leave comments on items that are referenced in the checklist to make sure that they are reviewed. - [ ] **Testing** - [ ] Are there new or updated unit or integration tests? - [ ] Do the tests cover the happy paths? - [ ] Do the tests cover the unhappy paths? - [ ] Are there an adequate number of fuzz tests to ensure that we are covering the full input space? - [ ] If matching Solidity behavior, are there differential fuzz tests that ensure that Rust matches Solidity?
U256 issue outlined here: #127 |
Updating test chain to support fuzzing over multiple states: #128 |
panic / result issue resolved as much as we want by #113 |
closing in favor of new smaller issues |
There were some issues reported a while ago by @slundqui. We'll need to review these calculations to see if we can find the source of the issue. We should also take the time to improve the test coverage. The current testing methodology is pretty good, but we don't test with different configurations, which really limits the robustness of the testing.
We should differentially test every rust function against Solidity.
The text was updated successfully, but these errors were encountered: