delvtech / hyperdrive

An automated market maker for fixed and variable yield with on-demand terms.
Apache License 2.0
33 stars 4 forks source link

Intermittent failure in `test_sandwich_short_trade` #769

Closed jalextowle closed 8 months ago

jalextowle commented 9 months ago
[FAIL. Reason: assertion failed; counterexample: calldata=0x021db0e70000000000000000000000000000000000000000000000000ddd06d96a2a2146000000000004f6e8bc857adaf4e091a700000000000000002144e61fee851f290000000000000000000000000000000000000000000000000de26765bacd66050000000000000000000000000000000000000000000000000000000000001fee args=[998962223204933958 [9.989e17], 2042272226342949092412748848311668432195895990698578471431773993 [2.042e63], 1000475753853052421 [1e18], 8174]] test_sandwich_short_trade(uint256,uint256,uint256,uint256) (runs: 3870, μ: 20957565, ~: 20957565)
jalextowle commented 8 months ago

https://github.com/delvtech/hyperdrive/actions/runs/8180506029/job/22368685295?pr=812

jalextowle commented 8 months ago

After looking into these tests, I've identified a few common characteristics. The tests have the following configurations:

# Test 1
fixed rate = 0.999962223204933958
contribution = 449411223.589546564913729337
trade amount = 2.000475753853052421
sandwich amount = 1.000000000000008174

# Test 2
fixed rate = 0.999000000000059409
contribution = 451507676.494061196359471404
trade amount = 87494183.301843377180327349
sandwich amount = 1.000000000000022746

In each case, the fixed rate is extremely high, and the sandwich amount is small. The profit is large enough to warrant concern. Both tests fail in the same way. The sandwiching short receives a larger amount of proceeds than they paid. This is problematic since a checkpoint passes between opening and closing their short. The failures are as follows:

# Test 1
  Error: a < b not satisfied [uint]
    Value a: 499965631881109392
    Value b: 499934921081030279

# Test 2
  Error: a < b not satisfied [uint]
    Value a: 500904262519506962
    Value b: 500901696031623320

My first thought while investigating the failures is that the share payment in _calculateCloseShort is probably smaller than the share proceeds in _calculateOpenShort. Sure enough, this was true in both cases:

# Test 1
share proceeds = 0.500065078918977895
share payment = 0.500034368118898782

# Test 2
share proceeds = 0.499098303968399426
share payment = 0.499095737480515784

In each case, the discrepancy between the share payment and proceeds is almost identical to the difference between the amount the short paid and the amount the short received. In other words, this difference explains the issue that we are seeing with short sandwiches. After investigating HyperdriveMath and YieldSpaceMath, I couldn't find any issues with the implementation. The rounding appears to be correct to me in all cases, and the math checks out as well.

When I looked at the test again, I noticed that this test had the fees set to zero. By adding a modest curve fee of 0.0001e18, both of these issues were ruled out.