Reach out for an audit or to learn more about Macro
or Message on Telegram

Polynomial A-2

Security Audit

July 8th, 2024

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for Polynomial's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team on June 23rd 10th to June 24th 2024.

The purpose of this audit is to review the source code of certain Polynomial Solidity contracts, and provide feedback on the design, architecture, and quality of the source code with an emphasis on validating the correctness and security of the software in its entirety.

Disclaimer: While Macro’s review is comprehensive and has surfaced some changes that should be made to the source code, this audit should not solely be relied upon for security, as no single audit is guaranteed to catch all possible bugs.

Overall Assessment

The following is an aggregation of issues found by the Macro Audit team:

Severity Count Acknowledged Won't Do Addressed
Medium 1 - - 1
Low 3 - - 3
Code Quality 1 1 - -

Polynomial was quick to respond to these issues.

Specification

Our understanding of the specification was based on the following sources:

Trust Model, Assumptions, and Accepted Risks (TMAAR)

Fee tiers are created by owner of polynomial, and each account requires the owners signature to update their fee tier. The owner is trusted to fairly distribute fee discounts described in fee tiers to appropriate accounts, and update account’s fee tiers appropriately. The fee tiers of an account is also expected to be updated frequently, on a 24 hour basis, and so fee tier updates signatures are expected to have a 24 hour expiry to prevent reusing an account’s beneficial fee tier. Polynomial is also expected to update an accounts fee tier if they do not do so themselves prior to trading.

Source Code

The following source code was reviewed during the audit:

Specifically, we audited the following contracts within this repository.

Source Code SHA256
markets/perps-market/contracts/interfaces/IAsyncOrderModule.sol

b800cbd543216636cddb992737594bb63595c9d65f85f1e224a364e2ac4d9a45

markets/perps-market/contracts/interfaces/IFeeTierModule.sol

32d2b8fd6e255e360ba4197755ea05d2526b80268541dd2259a4f4558c235326

markets/perps-market/contracts/interfaces/IGlobalPerpsMarketModule.sol

d1ee0e99ad6bea0c508521e12c87b202a3ff099b253b9b72936cf13c715de573

markets/perps-market/contracts/interfaces/IPerpsAccountModule.sol

1aea5418bde0dd1cc08ece2a56383ed18b869f7bed447acd1141d346e7ecece9

markets/perps-market/contracts/modules/AsyncOrderModule.sol

80522909f01b8331a318488a1232b352f3042ac130a513f58aff2a07bbfa95b8

markets/perps-market/contracts/modules/FeeTierModule.sol

c76f87bd11b789e04bfc970dd79b808992151e4cabaafd183f785f5bd9059d8d

markets/perps-market/contracts/modules/PerpsAccountModule.sol

ba15951db3007e3049c4bde09992ca587cd61b8567340857091220120210619d

markets/perps-market/contracts/storage/AsyncOrder.sol

d6b18e3b6c76de58ddf1e24dff3e0ead23ab5053f80849e95d263aeda06b3cdc

markets/perps-market/contracts/storage/FeeTier.sol

791698aabd01764fb6b0c4d72687197cb63b4d40ef98fa0d3ec401ef7b58a7a2

markets/perps-market/contracts/storage/GlobalPerpsMarketConfiguration.sol

50eb09b89db3a4b4f71e8ab2dc283e4e0cfb5af6ca536a51d3902c64b253d844

markets/perps-market/contracts/storage/PerpsAccount.sol

27bf87e775af4d6ef427c0644d8078da2c751b0c50c58ca0180d84d279db6b3a

Note: This document contains an audit solely of the Solidity contracts listed above. Specifically, the audit pertains only to the contracts themselves, and does not pertain to any other programs or scripts, including deployment scripts.

Issue Descriptions and Recommendations

Click on an issue to jump to it, or scroll down to see them all.

Security Level Reference

We quantify issues in three parts:

  1. The high/medium/low/spec-breaking impact of the issue:
    • How bad things can get (for a vulnerability)
    • The significance of an improvement (for a code quality issue)
    • The amount of gas saved (for a gas optimization)
  2. The high/medium/low likelihood of the issue:
    • How likely is the issue to occur (for a vulnerability)
  3. The overall critical/high/medium/low severity of the issue.

This third part – the severity level – is a summary of how much consideration the client should give to fixing the issue. We assign severity according to the table of guidelines below:

Severity Description
(C-x)
Critical

We recommend the client must fix the issue, no matter what, because not fixing would mean significant funds/assets WILL be lost.

(H-x)
High

We recommend the client must address the issue, no matter what, because not fixing would be very bad, or some funds/assets will be lost, or the code’s behavior is against the provided spec.

(M-x)
Medium

We recommend the client to seriously consider fixing the issue, as the implications of not fixing the issue are severe enough to impact the project significantly, albiet not in an existential manner.

(L-x)
Low

The risk is small, unlikely, or may not relevant to the project in a meaningful way.

Whether or not the project wants to develop a fix is up to the goals and needs of the project.

(Q-x)
Code Quality

The issue identified does not pose any obvious risk, but fixing could improve overall code quality, on-chain composability, developer ergonomics, or even certain aspects of protocol design.

(I-x)
Informational

Warnings and things to keep in mind when operating the protocol. No immediate action required.

(G-x)
Gas Optimizations

The presented optimization suggestion would save an amount of gas significant enough, in our opinion, to be worth the development cost of implementing it.

Issue Details

M-1

Missing EIP1271 signature support

Topic
Signatures
Status
Impact
Medium
Likelihood
High

In PerpsAccountModule.sol, signatures are verified to be signed by the owner to update an accounts fee tier:

function _verify(
    address _signer,
    uint256 _feeTierId,
    uint128 _accountId,
    uint256 _expiry,
    bytes memory signature
) internal pure returns (bool) {
    bytes32 messageHash = _getMessageHash(_feeTierId, _accountId, _expiry);
    bytes32 ethSignedMessageHash = _getEthSignedMessageHash(messageHash);

    return _recoverSigner(ethSignedMessageHash, signature) == _signer;
}

function _recoverSigner(
    bytes32 _ethSignedMessageHash,
    bytes memory _signature
) internal pure returns (address) {
    (bytes32 r, bytes32 s, uint8 v) = _splitSignature(_signature);

    return ecrecover(_ethSignedMessageHash, v, r, s);
}

Reference: PerpsAccountModule.sol#L133-L153

However, this directly uses ecrecover() to pull the signer from the signature, which only works for external owned account signatures. In cases where the signer is a smart contract, like a multi-sig or AA wallet, this method for verifying signatures will not work. The intended owner will be a multi-sig smart contract, which currently will not worth with this setup. EIP1271 was designed to allow smart contracts to sign messages and allow for it’s verification on-chain, so supporting this eip is required.

Remediations to Consider

Add EIP1271 support to allow the owner to sign fee tier updates properly.

L-1

Signature malleability

Topic
Signatures
Status
Impact
Medium
Likelihood
Low

As shown in M-1, the signature verification is done by directly using ecrecover(), and it does not include additional checks on the r, s, and v signature values. As mentioned in openzeppelin’s implementation of signature validation, ecrecover() allows for signature malleability if the library used to sign the message does not explicitly prevent it. Although most libraries will not generate malleable signatures, explicitly preventing them from being used on-chain is best practice.

Remediations to Consider

Prevent signature malleability by using openzeppelin’s signature checker library, or prevent the s value from being greater than 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 to resolve the issue yourself.

L-2

Does not use EIP712

Topic
Signatures
Status
Impact
Low
Likelihood
Low

EIP712 is a standard for signatures for specifying how to sign data with structs, but more importantly describes the standard method for adding a domain to each signature. A domain is additional information that specifies the target contracts address, chain, and version that a signature can be used for, preventing a signature from being used on a differing chain, or unintended contract. Without a domain, signatures made by a signer could potentially be reused maliciously, and in this case prior signatures of the owner could be used to set an unintended fee tier if the prior signature also did not include a domain.

Remediations to Consider

Follow EIP-712 to generate and verify the signatures to be sure the signatures are valid.

L-3

Setting FeeTierId 0 unintentionally effects most accounts

Topic
Input validation
Status
Impact
Medium
Likelihood
Low

In FeeTierModule.sol, the owner can set the maker and taker fee discounts for a fee tier based on a provided id by calling setFeeTier():

function setFeeTier(
    uint256 id,
    uint256 makerDiscount,
    uint256 takerDiscount
) external override {
    OwnableStorage.onlyOwner();

    FeeTier.Data storage feeTier = FeeTier.load(id);
    FeeTier.setFeeTier(feeTier, makerDiscount, takerDiscount);

    emit FeeTierSet(id, makerDiscount, takerDiscount);
}

Reference: FeeTierModule.sol#L12-L23

function setFeeTier(Data storage self, uint256 makerDiscount, uint256 takerDiscount) internal {
    // check discount should not be more than 100%

    if (makerDiscount > 10000) {
        revert ParameterError.InvalidParameter(
            "makerDiscount",
            "Maker discount should not be more than 100%"
        );
    }
    if (takerDiscount > 10000) {
        revert ParameterError.InvalidParameter(
            "takerDiscount",
            "Taker discount should not be more than 100%"
        );
    }

    self.makerDiscount = makerDiscount;
    self.takerDiscount = takerDiscount;
}

Reference: FeeTier.sol#L21-L39

However, there are no constraints on the id set. If the id set is 0 then since 0 is the default value of feeTier for each account, setting this will apply the makerDiscount and takerDiscount to all users that have no set feeTier:

   // @dev fee tier for this account
   uint256 feeTierId;

Reference: PerpsAccount.sol#L53-L54

Remediations to Consider

Prevent setting a feeTier with an id of 0.

Q-1

Signature validation should not be handled in PerpsAccountModule

Topic
Refactoring
Status
Acknowledged
Quality Impact
Low

The signature verification functions are currently in the PerpsAccountModule, however these functions should be moved to their own signature verification library, since they are distinct enough and unrelated to the direct functionality of the PerpsAccountModule. Also consider using the openzeppelin libraries for signature verification rather than writing your own.

Disclaimer

Macro makes no warranties, either express, implied, statutory, or otherwise, with respect to the services or deliverables provided in this report, and Macro specifically disclaims all implied warranties of merchantability, fitness for a particular purpose, noninfringement and those arising from a course of dealing, usage or trade with respect thereto, and all such warranties are hereby excluded to the fullest extent permitted by law.

Macro will not be liable for any lost profits, business, contracts, revenue, goodwill, production, anticipated savings, loss of data, or costs of procurement of substitute goods or services or for any claim or demand by any other party. In no event will Macro be liable for consequential, incidental, special, indirect, or exemplary damages arising out of this agreement or any work statement, however caused and (to the fullest extent permitted by law) under any theory of liability (including negligence), even if Macro has been advised of the possibility of such damages.

The scope of this report and review is limited to a review of only the code presented by the Polynomial team and only the source code Macro notes as being within the scope of Macro’s review within this report. This report does not include an audit of the deployment scripts used to deploy the Solidity contracts in the repository corresponding to this audit. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. In this report you may through hypertext or other computer links, gain access to websites operated by persons other than Macro. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such websites’ owners. You agree that Macro is not responsible for the content or operation of such websites, and that Macro shall have no liability to your or any other person or entity for the use of third party websites. Macro assumes no responsibility for the use of third party software and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.