feat(multi-atm): linear accrual - #54
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds linear accrual pricing functionality to the MultiATM contract, implementing price extrapolation based on linear regression of historical oracle data. The feature allows the contract to use trend-based pricing instead of simple min/max pricing from the last two oracle rounds.
Changes:
- Added
accrualRoundsparameter to thePairstruct andsetPairfunction to enable configurable linear regression over 2-30 oracle rounds (0 disables accrual) - Implemented
_computeLinearRegressionand_computeSlopefunctions to calculate price trends from historical oracle data and extrapolate prices forward - Added comprehensive test coverage for accrual pricing including validation tests, comparison tests with non-accrual mode, and rigorous decimal scaling tests with hardcoded expected values
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 14 comments.
| File | Description |
|---|---|
| contracts/token/MultiATM.sol | Adds linear regression price calculation with accrualRounds configuration, new constants for precision and max regression points, and modified _getPrices to support both accrual and non-accrual modes |
| test/main.test.js | Updates existing tests to pass new accrualRounds parameter (0 for backward compatibility) and adds extensive new test suites covering validation, linear regression calculations, swap operations, and decimal scaling scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // price = slope * (currentTime - baseTimestamp) / _PRECISION + intercept | ||
| uint256 absDeltaPrice = Math.mulDiv(SignedMath.abs(slope), block.timestamp - baseTimestamp, _PRECISION); | ||
| min = intercept + (slope < 0 ? -absDeltaPrice.toInt256() : absDeltaPrice.toInt256()); | ||
| max = min; |
There was a problem hiding this comment.
The code does not validate that the extrapolated price (min/max) is positive before returning. With a steep negative slope and large time extrapolation, the calculated price on line 366 could theoretically become negative (intercept + negative slope adjustment could be < 0). While oracle prices should be positive, the linear extrapolation could produce negative results. This would cause issues in the conversion calculations. Consider adding a check to ensure the extrapolated price remains positive.
| max = min; | |
| max = min; | |
| require(min > 0, "MultiATM: extrapolated oracle price must be positive"); |
|
|
||
| require(denominator > 0, InvalidOracleData()); | ||
|
|
||
| slope = _computeSlope(nInt * sumTP - sumT * sumP, denominator); |
There was a problem hiding this comment.
The multiplication nInt * sumTP and sumT * sumP on line 345 can overflow when dealing with large accumulated sums from the linear regression loop. This is particularly risky with 30 data points where sumTP and sumP can be very large. Consider using SafeMath operations or breaking down the calculation to avoid overflow.
| min = SignedMath.min(latest, previous); | ||
| max = SignedMath.max(latest, previous); | ||
|
|
||
| if (accrualRounds == 0) { |
There was a problem hiding this comment.
When accrualRounds is 0, the code attempts to access roundId - 1 without checking if roundId is 0. If this is the first oracle round (roundId = 0), this will underflow and attempt to access an invalid round. This should include a check to ensure at least 2 rounds exist before attempting to access the previous round.
| if (accrualRounds == 0) { | |
| if (accrualRounds == 0) { | |
| // Ensure there is at least one previous round before accessing roundId - 1 | |
| require(roundId >= 1, InvalidOracleData()); |
| for (uint8 i = 0; i < n; i++) { | ||
| (, int256 price, uint256 timestamp, , ) = oracle.getRoundData(startRoundId + i); | ||
| int256 t = int256(timestamp - baseTs); | ||
| sumT += t; | ||
| sumP += price; | ||
| sumTP += t * price; | ||
| sumT2 += t * t; |
There was a problem hiding this comment.
Integer overflow risk: The multiplication t * price on line 336 and t * t on line 337 could overflow for large timestamps or prices. While timestamps are relative to baseTs (reducing magnitude), with 30 rounds spanning multiple hours/days, the value of t can still be very large. The products should use checked arithmetic or SafeMath operations to prevent silent overflow.
| int256 denominator = nInt * sumT2 - sumT * sumT; | ||
|
|
||
| require(denominator > 0, InvalidOracleData()); |
There was a problem hiding this comment.
The check denominator > 0 on line 343 is insufficient. The denominator nInt * sumT2 - sumT * sumT equals zero when all timestamps are identical (all data points at the same time), which is theoretically possible if oracle data is published in a single block. This would cause a division by zero on line 345. Additionally, due to potential overflow on line 341 from the multiplications, the denominator could also become negative or wrap around. Consider adding overflow checks and providing a more informative error message for this edge case.
5d92eef to
42e282e
Compare
42e282e to
e30f7f8
Compare
No description provided.