If we executed the test function incr_position_fee_growth_tick as the team said in Contest description, it will revert with called Result::unwrap() on an Err value: [70, 101, 101, 32, 103, 114, 111, 119, 116, 104, 32, 115, 117, 98, 32, 111, 118, 101, 114, 102, 108, 111, 119, 32, 116, 105, 99, 107] when we decode this error it will give us the message Fee growth sub overflow tick.This means the calculation of the fee growth isnt working correctly, aftrer some digging i found that Uniswap V3 code is with solidity < 0.8.0 so that overflows can happen, this design of uni isnt wrong and the fees calculated are correct because of this overflow. If we look at the function responsible for calculating feees in the context of superposition get_fee_growth_inside in tick.rs all substraction operations in this function are used with checked_sub.
The function checked_sub in rust performs the subtraction and also checks if an overflow or underflow occurs. If the subtraction would result in an overflow or underflow, checked_sub returns None instead of wrapping around like in the uniswap v3 code version. We can use instead wrapping_sub to mimic exactly how uniswap handles the fee calculation and see if it will solve the issue.
Proof of Concept
To test our POC below please copy the function get_fee_growth_inside I put in Recommended Mitigation Steps and paste it instead of the incorrect one in tick.rs.
You can add the test function below in pkg/seawater/tests/lib.rs
cargo test --features testing --package seawater -- test_incr_position_fee_growth_tick_working --nocapture
As you can see the test passes and we will see the fees in the logs.
starting fee: 3000, token: 0x22b9fa698b68bBA071B513959794E9a47d19214c, fee global 0: 224733083815886344915083562155014453453618, fee global 1: 78926184188422759162812938368672
NOTE: The test function incr_position_fee_growth_tick is now reverting with "Sqrt price is 0" and not "Fee growth sub overflow tick" i am not sure why it is reverting with price is 0 but the test function i gave is working correctly.
Lines of code
https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/tick.rs#L125-L246
Vulnerability details
Description
If we executed the test function
incr_position_fee_growth_tick
as the team said in Contest description, it will revert withcalled Result::unwrap() on an Err value: [70, 101, 101, 32, 103, 114, 111, 119, 116, 104, 32, 115, 117, 98, 32, 111, 118, 101, 114, 102, 108, 111, 119, 32, 116, 105, 99, 107]
when we decode this error it will give us the messageFee growth sub overflow tick
.This means the calculation of the fee growth isnt working correctly, aftrer some digging i found that Uniswap V3 code is with solidity < 0.8.0 so that overflows can happen, this design of uni isnt wrong and the fees calculated are correct because of this overflow. If we look at the function responsible for calculating feees in the context of superpositionget_fee_growth_inside
in tick.rs all substraction operations in this function are used withchecked_sub
. The function checked_sub in rust performs the subtraction and also checks if an overflow or underflow occurs. If the subtraction would result in an overflow or underflow, checked_sub returns None instead of wrapping around like in the uniswap v3 code version. We can use instead wrapping_sub to mimic exactly how uniswap handles the fee calculation and see if it will solve the issue.Proof of Concept
To test our POC below please copy the function get_fee_growth_inside I put in Recommended Mitigation Steps and paste it instead of the incorrect one in tick.rs.
You can add the test function below in pkg/seawater/tests/lib.rs
Run the test with
As you can see the test passes and we will see the fees in the logs.
NOTE: The test function incr_position_fee_growth_tick is now reverting with "Sqrt price is 0" and not "Fee growth sub overflow tick" i am not sure why it is reverting with price is 0 but the test function i gave is working correctly.
Impact
Incorrect calculations of postion fees.
Tools Used
Manual Analysis and also this report from late 2023 about the same issue happening in solidity > 0.8.0
Recommended Mitigation Steps
One way to solve this is to use the function wrapping_sub instead of checked_sub.
Assessed type
Uniswap