Skip to content

feat(multi-atm): linear accrual - #54

Merged
adam-hotait merged 2 commits into
mainfrom
feat/multi-atm-v2
Feb 26, 2026
Merged

feat(multi-atm): linear accrual#54
adam-hotait merged 2 commits into
mainfrom
feat/multi-atm-v2

Conversation

@adam-hotait

Copy link
Copy Markdown
Contributor

No description provided.

@adam-hotait adam-hotait self-assigned this Feb 11, 2026
Copilot AI review requested due to automatic review settings February 11, 2026 13:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 accrualRounds parameter to the Pair struct and setPair function to enable configurable linear regression over 2-30 oracle rounds (0 disables accrual)
  • Implemented _computeLinearRegression and _computeSlope functions 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;

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
max = min;
max = min;
require(min > 0, "MultiATM: extrapolated oracle price must be positive");

Copilot uses AI. Check for mistakes.
Comment thread contracts/token/MultiATM.sol Outdated

require(denominator > 0, InvalidOracleData());

slope = _computeSlope(nInt * sumTP - sumT * sumP, denominator);

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread contracts/token/MultiATM.sol Outdated
min = SignedMath.min(latest, previous);
max = SignedMath.max(latest, previous);

if (accrualRounds == 0) {

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (accrualRounds == 0) {
if (accrualRounds == 0) {
// Ensure there is at least one previous round before accessing roundId - 1
require(roundId >= 1, InvalidOracleData());

Copilot uses AI. Check for mistakes.
Comment on lines +331 to +337
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;

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +341 to +343
int256 denominator = nInt * sumT2 - sumT * sumT;

require(denominator > 0, InvalidOracleData());

Copilot AI Feb 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread contracts/token/MultiATM.sol Outdated
Comment thread contracts/token/MultiATM.sol Outdated
Comment thread test/main.test.js Outdated
@adam-hotait
adam-hotait marked this pull request as draft February 17, 2026 10:04
@adam-hotait
adam-hotait removed the request for review from Amxx February 17, 2026 10:04
@adam-hotait
adam-hotait marked this pull request as ready for review February 26, 2026 17:30
@adam-hotait
adam-hotait merged commit 0e27d4a into main Feb 26, 2026
1 check passed
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 this pull request may close these issues.

2 participants