Platform: Code4rena
Start Date: 13/03/2023
Pot Size: $72,500 USDC
Total HM: 33
Participants: 35
Period: 7 days
Judge: Dravee
Total Solo HM: 16
Id: 222
League: ETH
Rank: 33/35
Findings: 1
Award: $103.46
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0xSmartContract, DadeKuma, GalloDaSballo, PaludoX0, RaymondFam, Rolezn, Sathish9098, adriro, auditor0517, bin2chen, btk, joestakey, juancito
103.4639 USDC - $103.46
FILE : 2023-03-polynomial/src/Exchange.sol
175: int256 usdSkew = wadMul(skew, int256(indexPrice)); // Normalized skew (Skew in to funding rate) 177: int256 normalizedSkew = wadDiv(usdSkew, int256(skewNormalizationFactor)); 178: int256 maxFunding = int256(maxFundingRate);
193: int256 currentTimeStamp = int256(block.timestamp); 194: int256 fundingLastUpdatedTimestamp = int256(fundingLastUpdated);
413: int256 currentTimeStamp = int256(block.timestamp); 414: int256 fundingLastUpdatedTimestamp = int256(fundingLastUpdated);
FILE : 2023-03-polynomial/src/LiquidityPool.sol
295: uint256 availableFunds = uint256(int256(totalFunds) - usedFunds);
Recommended Mitigation Steps: Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256.
It’s recommended to execute external calls after state changes, to prevent reetrancy bugs.
FILE : 2023-03-polynomial/src/ShortCollateral.sol
FILE : 2023-03-polynomial/src/KangarooVault.sol
Te safeMint function helps to prevent common errors and vulnerabilities in ERC20 token contracts, such as integer overflows, invalid recipient addresses, and unexpected behavior due to insufficient checks. It can also help to protect against potential attacks or exploits that may try to exploit these types of vulnerabilities.
FILE : 2023-03-polynomial/src/Exchange.sol
241: powerPerp.mint(msg.sender, params.amount);
FILE : 2023-03-polynomial/src/LiquidityPool.sol
189: liquidityToken.mint(user, tokensToMint);
235: liquidityToken.mint(current.user, tokensToMint);
FILE : 2023-03-polynomial/src/KangarooVault.sol
191: VAULT_TOKEN.mint(user, tokensToMint);
258: VAULT_TOKEN.mint(current.user, tokensToMint);
The integer ranges not checked before assigning the values to state variables. This will cause the serious damage if we assigns unexpected values to state variables and functions
FILE : 2023-03-polynomial/src/Exchange.sol
_priceNormalizationFactor > 0 value is not checked. As per current implamentations its possible to apply 0 to PRICING_CONSTANT. This will cause serious problems for exchange.sol contract
constructor(uint256 _priceNormalizationFactor, ISystemManager _systemManager) Auth(msg.sender, Authority(address(0x0))) { PRICING_CONSTANT = _priceNormalizationFactor; systemManager = _systemManager; }
function setSkewNormalizationFactor(uint256 _skewNormalizationFactor) external requiresAuth { emit UpdateSkewNormalizationFactor(skewNormalizationFactor, _skewNormalizationFactor); skewNormalizationFactor = _skewNormalizationFactor; }
function setMaxFundingRate(uint256 _maxFundingRate) external requiresAuth { emit UpdateMaxFundingRate(maxFundingRate, _maxFundingRate); maxFundingRate = _maxFundingRate; }
FILE : 2023-03-polynomial/src/ShortCollateral.sol
function collectCollateral(address collateral, uint256 positionId, uint256 amount) external onlyExchange nonReentrant { ERC20(collateral).safeTransferFrom(address(exchange), address(this), amount);
[ShortCollateral.sol#L85-L90] (https://github.com/code-423n4/2023-03-polynomial/blob/aeecafc8aaceab1ebeb94117459946032ccdff1e/src/ShortCollateral.sol#L85-L90)
FIE : 2023-03-polynomial/src/LiquidityPool.sol
the amount not checked with zero value
Recommended Mitigation:
require(INPUTVARIABLE > 0, "Zero value");
FILE : 2023-03-polynomial/src/Exchange.sol
191: fundingRate = fundingRate / 1 days;
411: fundingRate = fundingRate / 1 days;
The following methods have a lack of checks if the received argument is an address, it’s good practice in order to reduce human error to check that the address specified in the function is different than address(0)
FILE : FILE : 2023-03-polynomial/src/LiquidityPool.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { feeReceipient = _feeReceipient; }
The critical procedures should be two step process. See similar findings in previous Code4rena contests for reference: (https://code4rena.com/reports/2022-06-illuminate/#2-critical-changes-should-use-two-step-procedure)
FILE : FILE : 2023-03-polynomial/src/LiquidityPool.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { feeReceipient = _feeReceipient; }
Solmate's SafeTransferLib, which is often used to interact with non-compliant/unsafe ERC20 tokens, does not check whether the ERC20 contract exists. The following code will not revert in case the token doesn't exist (yet)
FILE : 2023-03-polynomial/src/KangarooVault.sol
207: SUSD.safeTransferFrom(msg.sender, address(this), amount);
As per current implementations user can call initiateDeposit() function without having enough balance in user account
FILE : 2023-03-polynomial/src/KangarooVault.sol
Recombined Mitigation:
require (address(user).balance > amount, "Not enough balance ");
The requiresAuth role has a single point of failure and requiresAuth can use critical a few functions.
Even if protocol admins/developers are not malicious there is still a chance for Owner keys to be stolen. In such a case, the attacker can cause serious damage to the project due to important functions. In such a case, users who have invested in project will suffer high financial losses
requiresAuth functions :
FILE : 2023-03-polynomial/src/Exchange.sol
216: function setSkewNormalizationFactor(uint256 _skewNormalizationFactor) external requiresAuth {
223: function setMaxFundingRate(uint256 _maxFundingRate) external requiresAuth {
FILE : 2023-03-polynomial/src/KangarooVault.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { require(_feeReceipient != address(0x0)); emit UpdateFeeReceipient(feeReceipient, _feeReceipient); feeReceipient = _feeReceipient; }
function setFees(uint256 _performanceFee, uint256 _withdrawalFee) external requiresAuth { require(_performanceFee <= 1e17 && _withdrawalFee <= 1e16);
emit UpdateFees(performanceFee, withdrawalFee, _performanceFee, _withdrawalFee); performanceFee = _performanceFee; withdrawalFee = _withdrawalFee; }
All parameters values are not a constant value and can be changed with setter functions, before a function using the setter methods state variables value in the project, setter functions can be triggered by requiresAuth and operations can be blocked
FILE : 2023-03-polynomial/src/Exchange.sol
216: function setSkewNormalizationFactor(uint256 _skewNormalizationFactor) external requiresAuth {
223: function setMaxFundingRate(uint256 _maxFundingRate) external requiresAuth {
FILE : 2023-03-polynomial/src/KangarooVault.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { require(_feeReceipient != address(0x0)); emit UpdateFeeReceipient(feeReceipient, _feeReceipient); feeReceipient = _feeReceipient; }
function setFees(uint256 _performanceFee, uint256 _withdrawalFee) external requiresAuth { require(_performanceFee <= 1e17 && _withdrawalFee <= 1e16);
emit UpdateFees(performanceFee, withdrawalFee, _performanceFee, _withdrawalFee); performanceFee = _performanceFee; withdrawalFee = _withdrawalFee; }
.transfer will relay 2300 gas and .call will relay all the gas. If the receive/fallback function from the recipient proxy contract has complex logic, using .transfer will fail, causing integration issues.
FILE : 2023-03-polynomial/src/KangarooVault.sol
549: ERC20(token).transfer(receiver, amt);
Recommendation Replace .transfer with .call. Note that the result of .call need to be checked.
In the case of the init() function, an attacker may observe a transaction waiting to call the init() function and submit their own transaction with higher gas fees that calls the same function with different or malicious parameters. If the attacker's transaction is processed first, it can potentially manipulate the initialization process of the smart contract, resulting in unexpected or malicious behavior
FILE : 2023-03-polynomial/src/SystemManager.sol
function init( address _pool, address _powerPerp, address _exchange, address _liquidityToken, address _shortToken, address _synthetixAdapter, address _shortCollateral ) public { require(!isInitialized); refreshSynthetixAddresses(); pool = ILiquidityPool(_pool); powerPerp = IPowerPerp(_powerPerp); exchange = IExchange(_exchange); liquidityToken = ILiquidityToken(_liquidityToken); shortToken = IShortToken(_shortToken); synthetixAdapter = ISynthetixAdapter(_synthetixAdapter); shortCollateral = IShortCollateral(_shortCollateral); isInitialized = true; }
Recommended Mitigation:
Use a timelock
init() function can be called anybody when the contract is not initialized.
More importantly, if someone else runs this function, they will have full authority because of the owner state variable.
Here is a definition of init() function
FILE : 2023-03-polynomial/src/SystemManager.sol
function init( address _pool, address _powerPerp, address _exchange, address _liquidityToken, address _shortToken, address _synthetixAdapter, address _shortCollateral ) public { require(!isInitialized); refreshSynthetixAddresses(); pool = ILiquidityPool(_pool); powerPerp = IPowerPerp(_powerPerp); exchange = IExchange(_exchange); liquidityToken = ILiquidityToken(_liquidityToken); shortToken = IShortToken(_shortToken); synthetixAdapter = ISynthetixAdapter(_synthetixAdapter); shortCollateral = IShortCollateral(_shortCollateral); isInitialized = true; }
Recommended Mitigation Steps
Add a control that makes init() only call the Deployer Contract or EOA
if (msg.sender != DEPLOYER_ADDRESS) { revert NotDeployer(); }
Adding events for critical parameter changes will facilitate offchain monitoring and indexing
FILE : FILE : 2023-03-polynomial/src/LiquidityPool.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { feeReceipient = _feeReceipient; }
Recommended Mitigation:
Add event for critical address changes with old and new address
Use a solidity version of at least 0.8.12 to get string.concat() to be used instead of abi.encodePacked(<str>,<str>)
FILE : 2023-03-polynomial/src/Exchange.sol
2: pragma solidity ^0.8.9;
FILE : 2023-03-polynomial/src/LiquidityPool.sol
2: pragma solidity ^0.8.9;
FILE : 2023-03-polynomial/src/KangarooVault.sol
2: pragma solidity ^0.8.9;
Exchange.sol#L167 Exchange.sol#L155 xchange.sol#L186 Exchange.sol#L100-L105 Exchange.sol#L205-L206 LiquidityPool.sol#L430-L435
Exchange.sol#L87-L92 Exchange.sol#L98-L105 Exchange.sol#L154-L155 Exchange.sol#L166-L167 Exchange.sol#L184-L186 Exchange.sol#L205-L206
The Solidity style guide recommends declaring events should declare bellow the state variables
But Exchange.sol file evens are declared bottom of the file
Please declare the events as per solidity style guide
FILE : 2023-03-polynomial/src/Exchange.sol
CONTEXT ALL CONTRACTS
Description I recommend using header for Solidity code layout and readability
FILE : 2023-03-polynomial/src/Exchange.sol
function setSkewNormalizationFactor(uint256 _skewNormalizationFactor) external requiresAuth { emit UpdateSkewNormalizationFactor(skewNormalizationFactor, _skewNormalizationFactor); skewNormalizationFactor = _skewNormalizationFactor; }
function setMaxFundingRate(uint256 _maxFundingRate) external requiresAuth { emit UpdateMaxFundingRate(maxFundingRate, _maxFundingRate); maxFundingRate = _maxFundingRate; }
FILE : FILE : 2023-03-polynomial/src/LiquidityPool.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { feeReceipient = _feeReceipient; }
CONTEXT ALL CONTRACTS
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. (https://docs.soliditylang.org/en/v0.8.15/natspec-format.html)
Recommendation NatSpec comments should be increased in Contracts
All the contracts in scope are floating the pragma version.
Recommendation Locking the pragma helps to ensure that contracts do not accidentally get deployed using an outdated compiler version.
Note that pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or a package.
FILE : 2023-03-polynomial/src/LiquidityPool.sol
27: contract LiquidityPool is ILiquidityPool, Auth, ReentrancyGuard, PauseModifier {
FILE : 2023-03-polynomial/src/LiquidityPool.sol
56: bytes32 public immutable baseAsset;
FILE : 2023-03-polynomial/src/KangarooVault.sol
60: bytes32 public immutable name;
FILE : 2023-03-polynomial/src/PowerPerp.sol
FILE : 2023-03-polynomial/src/ShortToken.sol
ShortToken.sol#L9
ShortToken.sol#L11
FILE : 2023-03-polynomial/src/LiquidityPool.sol
The Solidity assert() function is meant to assert invariants. Properly functioning code should never reach a failing assert statement.
FILE : 2023-03-polynomial/src/LiquidityPool.sol
220: assert(queuedDepositHead + count - 1 < nextQueuedDepositId);
FILE : 2023-03-polynomial/src/LiquidityPool.sol
227: return;
FILE : 2023-03-polynomial/src/LiquidityPool.sol
86: /// @notice Minimum deposit delay 87: uint256 public minWithdrawDelay;
Recommendations:
86: /// @notice Minimum withdraw delay 87: uint256 public minWithdrawDelay;
FILE : 2023-03-polynomial/src/Exchange.sol
function setSkewNormalizationFactor(uint256 _skewNormalizationFactor) external requiresAuth { emit UpdateSkewNormalizationFactor(skewNormalizationFactor, _skewNormalizationFactor); skewNormalizationFactor = _skewNormalizationFactor; }
function setMaxFundingRate(uint256 _maxFundingRate) external requiresAuth { emit UpdateMaxFundingRate(maxFundingRate, _maxFundingRate); maxFundingRate = _maxFundingRate; }
FILE : 2023-03-polynomial/src/KangarooVault.sol
function setFeeReceipient(address _feeReceipient) external requiresAuth { require(_feeReceipient != address(0x0)); emit UpdateFeeReceipient(feeReceipient, _feeReceipient); feeReceipient = _feeReceipient; }
function setFees(uint256 _performanceFee, uint256 _withdrawalFee) external requiresAuth { require(_performanceFee <= 1e17 && _withdrawalFee <= 1e16);
emit UpdateFees(performanceFee, withdrawalFee, _performanceFee, _withdrawalFee); performanceFee = _performanceFee; withdrawalFee = _withdrawalFee; }
This is a best-practice to protect against reentrancy in other modifiers
FILE : 2023-03-polynomial/src/LiquidityPool.sol
function closeLong(uint256 amount, address user, bytes32 referralCode) external override onlyExchange nonReentrant returns (uint256 totalCost)
function openShort(uint256 amount, address user, bytes32 referralCode) external override onlyExchange nonReentrant returns (uint256 totalCost)
LiquidityPool.sol#L557 LiquidityPool.sol#L591
FILE : 2023-03-polynomial/src/KangarooVault.sol
376: function openPosition(uint256 amt, uint256 minCost) external requiresAuth nonReentrant {
383: function closePosition(uint256 amt, uint256 maxCost) external requiresAuth nonReentrant {
389: function clearPendingOpenOrders(uint256 maxCost) external requiresAuth nonReentrant {
395: function clearPendingCloseOrders(uint256 minCost) external requiresAuth nonReentrant {
401: function transferPerpMargin(int256 marginDelta) external requiresAuth nonReentrant {
424: function addCollateral(uint256 additionalCollateral) external requiresAuth nonReentrant {
436: function removeCollateral(uint256 collateralToRemove) external requiresAuth nonReentrant {
450: function executePerpOrders(bytes[] calldata priceUpdateData) external payable requiresAuth nonReentrant {
FILE : 2023-03-polynomial/src/SystemManager.sol
NATSPEC is missing for constructors, state variables , public functions
Context All Contracts
Description Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
(https://docs.soliditylang.org/en/v0.8.17/style-guide.html)
Functions should be grouped according to their visibility and ordered:
constructor receive function (if exists) fallback function (if exists) external public internal private within a grouping, place the view and pure functions last
It can’t be recovered if the tokens accidentally arrive at the contract address, which has happened to many popular projects, so I recommend adding a recovery code to your critical contracts.
Recommended Mitigation Steps Add this code:
/**
*/ function rescueERC20(address account, address externalToken, uint256 amount) public onlyOwner returns (bool) { IERC20(externalToken).transfer(account, amount); return true; } }
At the start of the project, the system may need to be stopped or upgraded, I suggest you have a script beforehand and add it to the documentation. This can also be called an ” EMERGENCY STOP (CIRCUIT BREAKER) PATTERN “.
(https://github.com/maxwoe/solidity_patterns/blob/master/security/EmergencyStop.sol)
#0 - JustDravee
2023-03-26T18:00:49Z
SUSD.safeTransferFrom
)Rest is NC and R.
The amount of signal is good but the warden should do a better effort in terms of presentation
#1 - c4-judge
2023-05-03T03:09:23Z
JustDravee marked the issue as grade-b
#2 - sathishpic22
2023-05-04T11:17:34Z
hi @JustDravee i got 6L still grade- b
#3 - JustDravee
2023-05-05T10:21:51Z
@sathishpic22 I want to thank you for submitting the report, but unfortunately:
Given those penalty factors, I had to adjust the grade accordingly. If some findings were quite interesting or if the quality of the report was better, including the fact of not submitting OOS/Invalid findings, this would've been considered for grade A. But I cannot put this report at the same level as the other grade-A reports
#4 - sathishpic22
2023-05-05T10:28:56Z
Oh thank you sir. Yes, I have improved my reports quality as per your suggestions. Thanks for your clarifications
On Fri, May 5, 2023, 3:52 PM Dravee @.***> wrote:
@sathishpic22 https://github.com/sathishpic22 I want to thank you for submitting the report, but unfortunately:
- The presentation/readability quality is quite low, making it difficult to read
- It has a lot of invalid/Out of scope findings
- The low-severity findings were generic ones (meaning: no unique signal)
Given those penalty factors, I had to adjust the grade accordingly. If some findings were quite interestin or if the quality of the report was better, including the fact of not submitting OOS/Invalid findings, this would've been considered for grade A. But I cannot put this report at the same level as the other grade-A reports
— Reply to this email directly, view it on GitHub https://github.com/code-423n4/2023-03-polynomial-findings/issues/40#issuecomment-1536045982, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOA6PHIDYNI3X35QAQS3VKTXETIETANCNFSM6AAAAAAV63NK2E . You are receiving this because you were mentioned.Message ID: @.***>