Skip to content

Commit c4340be

Browse files
Merge pull request #387 from 0xMacro/audits/Polynomial-7
Polynomial-7
2 parents 10f9295 + c91d7ab commit c4340be

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<page
2+
clientName="Polynomial"
3+
reportDate="Oct 3, 2025"
4+
auditTitle="Polynomial A-7"
5+
auditVersion="1.0.0"
6+
repoUrl="https://github.com/Polynomial-Protocol/fx-contracts"
7+
layout="/library/audits/_layout.html"
8+
repoCommitHash="4d7e3512dfcbe3b2d009252f846fd64dc5d3b722"
9+
finalRepoCommitHash="015e17891683f99970db6e4568c39e732c1ebefb"
10+
>
11+
12+
<content-for name="schedule">
13+
The security audit was performed by the Macro security team on September 24-26th 2025.
14+
</content-for>
15+
16+
<content-for name="spec">
17+
<ul>
18+
<li>Discussions with the {{page.clientName}} team.</li>
19+
<li>Available documentation in the repository.</li>
20+
</ul>
21+
22+
</content-for>
23+
24+
<content-for name="source-code">
25+
<p>
26+
Specifically, we audited the following contracts within this repository.
27+
</p>
28+
29+
<template type="file-hashes">
30+
627b85eb428a0a12ecbe4cd76df4f075eb73fc0c1d34dba21cadeb25e301109f markets/perps-market/contracts/interfaces/IMarketCloseModule.sol
31+
3e52757af1bfbe6ed8b22def142f1d9a682216d7b3a36ac9ffb840bb7b1447f6 markets/perps-market/contracts/modules/LimitOrderModule.sol
32+
179092881e60907fbdb90ed42d1b1a2df16ed115ecb17fd9f9fffbbd069213df markets/perps-market/contracts/modules/LiquidationModule.sol
33+
b1151e40518b3772915c7289581484b013d43456b8a17bff507593ccedb0bfa8 markets/perps-market/contracts/modules/MarketCloseModule.sol
34+
b04ee7f619c7f072e2c88cf2af23dae296f2a9572ffb5e10a92569f9140133ae markets/perps-market/contracts/modules/MarketConfigurationModule.sol
35+
5d17eff7c642d062bf117bb78cdf6f73093c642c2e2740259aa87d91b9aa906f markets/perps-market/contracts/modules/OffchainLimitOrderModule.sol
36+
5864c6ce51f838a09fa16b917cee963673adc899934cbcd8c7b949edb9111405 markets/perps-market/contracts/modules/PythLazer/OffchainLimitOrderPythLazerModule.sol
37+
d596fe427d98f67a80eb74df862f3518353d69c89b611bc82052ce5e8554139b markets/perps-market/contracts/storage/AsyncOrder.sol
38+
9f83bee8743cd449fb8f71c0d934f3839a12c7a4e0be966c9cc6073e953794c0 markets/perps-market/contracts/storage/MarketClose.sol
39+
01b3fd9163b25e8c20aae672fe3b2866c172988baaa4a9c79c33a0e22f80f7b8 markets/perps-market/contracts/storage/PerpsAccount.sol
40+
09f2de3b69d341f7924bc2008c67c9b2d7b86591b8018004683608d4aaaabc07 markets/perps-market/contracts/storage/PerpsPrice.sol
41+
97d81e4aef63df64b6d36399aca3ea037195c620ff7d74f46027107003eaaf81 markets/perps-market/contracts/storage/Position.sol
42+
</template>
43+
</content-for>
44+
</page>
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<item>
2+
<field name="topic">Protocol design</field>
3+
<field name="impact">critical</field>
4+
<field name="chance">high</field>
5+
<field name="status">fixed</field>
6+
<field name="commit">76f33b892aa0c94201ebda9d9c53d46eb034e9de</field>
7+
<field name="content">
8+
## [C-1] Stale price of closed markets results in breaking core protocol functionality
9+
10+
This update is intended to allow for markets that use assets outside of crypto, that are not traded and thus priced 24/7, and instead have market operating hours, like US stocks. This results in the oracle no longer updating the price once the markets have closed, resulting in stale data errors when querying the prices normally. In order to allow for important functionality like liquidations of accounts that contain positions in these markets while they are closed, the function [getCurrentPricesWithClosedMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L85-L131) is used, where the owner can close markets via [closeMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/modules/MarketCloseModule.sol#L48-L63) which stores the current price as the price at market close and closes the market. This allows for position value to still be calculated when markets close and allows liquidations to occur. However other important functionality like [reportedDebt()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/modules/PerpsMarketFactoryModule.sol#L114-L138), used by the core protocol to determine markets it supports debt, would revert if any market that is closed, locking key functionality for the protocol.
11+
12+
**Remediations to Consider**
13+
14+
Allow for closed market handling in [getCurrentPrices()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L45-L83) rather then use custom handling for liquidations via [getCurrentPricesWithClosedMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L85-L131).
15+
</field>
16+
</item>
17+
18+
<item>
19+
<field name="topic">Error Recovery</field>
20+
<field name="impact">critical</field>
21+
<field name="chance">low</field>
22+
<field name="status">fixed</field>
23+
<field name="commit">015e17891683f99970db6e4568c39e732c1ebefb</field>
24+
<field name="content">
25+
## [H-1] Not closing markets in time can lock key protocol functionality
26+
27+
For the reasons described in C-1, once markets are closed the oracle will no longer update the price and would resulting stale pricing which effects core functionality. [closeMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/modules/MarketCloseModule.sol#L48-L63) is intended to be called at market close to store the last price and mark as close to allow for custom handling via [getCurrentPricesWithClosedMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L85-L131). However, if for whatever reason the owner does not manage to call to close a market within 60 seconds of the market close price being updated, then they will be prevent from doing so, as it will revert due to the stale closing price. This would then result in the same issues described in C-1, even if resolved as suggested, until the market is able to be opened.
28+
29+
**Remediations to Consider**
30+
31+
Allow for a market to be closed even if the data is stale to prevent locking the protocol if a market is missed.
32+
</field>
33+
</item>
34+
35+
<item>
36+
<field name="topic">pricing</field>
37+
<field name="impact">high</field>
38+
<field name="chance">high</field>
39+
<field name="status">fixed</field>
40+
<field name="commit">76f33b892aa0c94201ebda9d9c53d46eb034e9de</field>
41+
<field name="content">
42+
## [H-2] Wrong pricing tolerance
43+
44+
[getCurrentPricesWithClosedMarkets()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L85-L131) is intended to be the same as [getCurrentPrices()](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L45-L83), with the exception of handling closed markets and use of strict tolerance as it is supposed to be used for liquidations which only use strict price tolerance:
45+
46+
```solidity
47+
bytes32[] memory runtimeKeys = new bytes32[](0);
48+
// tolerance is STRICT (no runtime keys supplied here; mirrors previous behavior)
49+
NodeOutput.Data[] memory outputs = oracleManager.processManyWithRuntime(
50+
feedIds,
51+
runtimeKeys,
52+
runtimeKeys
53+
);
54+
```
55+
56+
Reference: [PerpsPrice.sol#L118-124](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L118-L124)
57+
58+
However, it is using the logic for default tolerance, as the logic for anything other than `Tolerance.DEFAULT` uses non-zero keys and values:
59+
60+
```solidity
61+
if (priceTolerance != Tolerance.DEFAULT) {
62+
bytes32[] memory sharedRuntimeKeys = new bytes32[](1);
63+
sharedRuntimeKeys[0] = bytes32("stalenessTolerance");
64+
65+
bytes32[][] memory runtimeKeys = new bytes32[][](marketIds.length);
66+
bytes32[][] memory runtimeValues = new bytes32[][](marketIds.length);
67+
68+
for (uint256 i = 0; i < marketIds.length; i++) {
69+
bytes32[] memory newRuntimeValues = new bytes32[](1);
70+
newRuntimeValues[0] = toleranceBytes(load(marketIds[i].to128()), priceTolerance);
71+
runtimeKeys[i] = sharedRuntimeKeys;
72+
runtimeValues[i] = newRuntimeValues;
73+
}
74+
75+
outputs = oracleManager.processManyWithManyRuntime(feedIds, runtimeKeys, runtimeValues);
76+
} else {
77+
bytes32[] memory runtimeKeys = new bytes32[](0);
78+
// do the process call
79+
outputs = oracleManager.processManyWithRuntime(feedIds, runtimeKeys, runtimeKeys);
80+
}
81+
```
82+
83+
Reference: [PerpsPrice#L57-76](https://github.com/Polynomial-Protocol/fx-contracts/blob/4d7e3512dfcbe3b2d009252f846fd64dc5d3b722/markets/perps-market/contracts/storage/PerpsPrice.sol#L57-L76)
84+
85+
This can result in inaccurate pricing for liquidations, and wrong determinations if an account should be liquidated or not.
86+
87+
**Remediations to Consider**
88+
89+
Use the strict tolerance logic of `getCurrentPrice()`, or alternatively adjust getCurrentPrice() to handle closed markets and remove `getCurrentPricesWithClosedMarkets()`.
90+
</field>
91+
</item>
92+
93+
<item>
94+
<field name="topic">Inconsistency</field>
95+
<field name="impact">medium</field>
96+
<field name="chance">low</field>
97+
<field name="status">ack</field>
98+
<field name="content">
99+
## [M-1] Potentially inconsistent rollover fees
100+
101+
Currently rollover fees are only applied when an account is updated, using the value of the markets rollover fees at the time of update. This is fine if the rollover fee does not change since the last position update, however if it does change potentially to incentivize LPs during high volatility, then the user would be charged the new fee, be it higher or lower than the last, when they next update their position. This leads to potential over or under charging of fees, and inconsistent rewards for LP providers. It could also result in cases where if the rollover fee is increased temporarily, for high volatility, then a user may delay updating their position until the rollover fee is reduced in order to reduce the fees charged to them.
102+
103+
**Remediations to Consider**
104+
105+
Consider keeping tabs of a cumulative fee rather than just a last updated timestamp, if the account and market each has a cumulative fee of time * current rolloverFee, then after a fee change the new fee would only be applied for time after the update, and not used for the entire time since an account was last updated, resulting in accurate fees. Additionally, you could consider adding a public method that allows charging fees owed by an account, since fees to the protocol are only applied on update which could never occur. Allowing for inactive accounts with large fees to be collected.
106+
107+
</field>
108+
<field name="response">
109+
rolloverFee is operated as static for now and we do not expect changes soon.
110+
Interim: We will not modify rolloverFee until the cumulative accounting fix is live. If an urgent change is required, we will announce the effective block in advance.
111+
Planned fix: Add a cumulative rolloverFeeIndex with per-account lastIndex, and a keeper method to charge inactive accounts. Targeted for the next upgrade window.
112+
</field>
113+
</item>
114+
115+
<item>
116+
<field name="topic">Informational</field>
117+
<field name="content">
118+
## [I-1] Positions in closed markets cannot be modified until the market is reopened
119+
120+
The intended behaviour of handling closed markets is that positions cannot be modified while they are closed, acting similarly to traditional stock markets the underlying assets are traded on. This is to prevent trading on information that would effect price but is not tracked by the oracle, leading to an unfair edge. Although this behaviour is not explicitly handled, when querying the price of the asset will revert due to stale data, preventing updating the position as intended.
121+
122+
Users should be aware that these positions are limited to the hours of the traditional markets they are traded on, unlike other markets on polynomial.
123+
</field>
124+
<field name="response">
125+
This is intended. During off-market hours the oracle is stale. The app will display data based on the last official close price and clearly indicate “Market closed.” Users cannot modify positions during this period.
126+
Planned: Add explicit market-hours checks with clear errors and a read API for next open and close times.
127+
</field>
128+
</item>
129+

0 commit comments

Comments
 (0)