HoldStation - Audit

CertiKCode version: not recorded (see the report)

CertiK reviewed Holdstation (HoldStation - Audit) on 19 Jan 2024: 23 findings, 0 critical; all acknowledged, none fixed.

No open critical findings
Open the reportOpen Holdstation’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings23

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High020
Medium010
Low0200
HighCentralization Related RisksAcknowledged

In the contract `HSStaking` the role `_owner` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-94c4-55604f20-b647-0fa6c675c080/diagrams/centralization_HSStaking-HSStaking-_owner.svg) Since the contract `HSStaking` inherits from `OwnableUpgradeable`, the `_owner` role has authority over the functions below as well: - `renounceOwnership`: Transfer ownership to address(0). - `transferOwnership`: Transfer ownership to the new owner's address. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority, set malicious account to the admin role. In the contract `HSStaking` the role `admin` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-94c4-55604f20-b647-0fa6c675c080/diagrams/centralization_HSStaking-HSStaking-admin.svg) Any compromise to the `admin` account may allow the hacker to take advantage of this authority and do the following: - Update `unstakeNumberEpochs` to a large value to result in users being unable to withdraw their pledges - Update the reward rate results in yields from staking that deviate from expectations - Update the epoch duration to enable attackers to gain substantial profits in a short period --- In the contract `HSTradingVault` the role `_owner` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-94c4-55604f20-b647-0fa6c675c080/diagrams/centralization_HSTradingVault-HSTradingVault-_owner.svg) Since the contract `HSTradingVault` inherits from `OwnableUpgradeable`, the `_owner` role has authority over the functions below as well: - `renounceOwnership`: Transfer ownership to address(0). - `transferOwnership`: Transfer ownership to the new owner's address, which is overridden in the `HSTradingVault`, the new owner cannot be the current admin or manager address. Any compromise to the `_owner` account may allow the hacker to take advantage of this authority and do the following: - Set the `manager` role or transfer the ownership to malicious account - Update the maximum price delta on new epochs from open PNL to affect the staker's PNL - Update the interactive contracts into malicious contracts In the contract `HSTradingVault` the role `manager` has authority over the functions shown in the diagram below. ![](https://accelerator-tasks-prod.acc.corp.certik.com/11ee-94c4-55604f20-b647-0fa6c675c080/diagrams/centralization_HSTradingVault-HSTradingVault-manager.svg) Any compromise to the `manager` account may allow the hacker to take advantage of this authority and do the following: - Set the `admin` role to malicious account - Update daily maximum minting amount for `HSToken` and vault shares - Update the ratio of profits used for destorying of `HSToken` - Update the `HSTokenPriceProvider` contarct to a malicious contract to manipulate the price of `HSToken` tokens - udpate max daily price delta from closed PNL In the contract `HSTradingVault` the role `pnlHandler` has authority over the functions below: - `sendAssets`: Withdraw `asset` from the vault and update the vault status. Any compromise to the `pnlHandler` account may allow the hacker to take advantage of this authority and withdraw `asset` tokens from the vault. Note: Based on design the `pnlHandler` should not be an EOA, it is supposed to be the `HSTradingCallbacks` which is not in the scope. In the contract `HSTradingVault` the role `openTradesPnlFeed` has authority over the functions below: - `updateAccPnlPerTokenUsed`: Updates `shareToAssetsPrice` based on the new `PnL` and starts a new epoch. Any compromise to the `openTradesPnlFeed` account may allow the hacker to take advantage of this authority and exploit by manipulating the share price.

HighCentralized Control of Contract UpgradeAcknowledged

The contracts `HSStaking` and `HSTradingVault` inherits upgradeable contracts, indicating that it is part of an upgradeable system. Upgradeable contracts often pair with a proxy contract that is responsible for managing contract upgrades. The `privileged roles` of the proxy often have the authority to update the implementation contracts. Any compromise of the privileged account could allow a hacker to exploit this authority, potentially altering the implementation contract pointed to by the proxy and thus executing malicious functionality within the implementation contract.

MediumRisk of Revert in `withdraw() ` and `redeem()` duAcknowledged

When a user deposits assets into the vault, the `totalDeposited` value is updated and the assets and shares have a specific ratio. Let's say if a reward is distributed to the vault in the same amount as the user's deposit, the shareToAssetsPrice doubles, allowing the user to withdraw double the assets. However, if the total value of assets withdrawn exceeds the totalDeposited amount, both the `withdraw()` and `redeem()` functions will revert due to the subtraction overflow. ```solidity=667 totalDeposited = isDeposit ? totalDeposited + assets : totalDeposited - assets; ```

LowCheck Effect Interaction Pattern ViolatedAcknowledged

The order of external call/transfer and storage manipulation must follow check effect interaction patterns. --- ```solidity=597 function distributeReward(uint256 assets) external { address sender = _msgSender(); SafeERC20Upgradeable.safeTransferFrom(_assetIERC20(), sender, address(this), assets); accRewardsPerToken += (assets * PRECISION) / totalSupply(); updateShareToAssetsPrice(); totalRewards += assets; emit RewardDistributed(sender, assets); } ``` --- ```solidity=633 function receiveAssets(uint256 assets, address user) external { address sender = _msgSender(); SafeERC20Upgradeable.safeTransferFrom(_assetIERC20(), sender, address(this), assets); uint256 assetsLessDeplete = assets; if (accPnlPerTokenUsed < 0 && accPnlPerToken < 0) { uint256 depleteAmount = (assets * lossesBurnP) / PRECISION / 100; assetsToDeplete += depleteAmount; assetsLessDeplete -= depleteAmount; } ``` --- ```solidity=696 require( dailyMintedHs <= (maxHsSupplyMintDailyP * IERC20Upgradeable(hsToken).totalSupply()) / PRECISION / 100, "ABOVE_INFLATION_LIMIT" ); address sender = _msgSender(); SafeERC20Upgradeable.safeTransferFrom(_assetIERC20(), sender, address(this), assets); int256 accPnlDelta = int256((assets * PRECISION) / supply); accPnlPerToken -= accPnlDelta; accPnlPerTokenUsed -= accPnlDelta; updateShareToAssetsPrice(); ``` --- ```solidity=121 function distributeRewardUsdc(uint256 _amount) external { usdc.transferFrom(_msgSender(), address(this), _amount); if (tokenBalance > 0) { accUsdcPerToken += (_amount * 1e18) / tokenBalance; totalRewardsDistributedUsdc += _amount; } emit UsdcDistributed(_amount); } ```

LowClarification on Allowance ModelAcknowledged

According to the logic of the function `makeWithdrawRequest`. If `tokenCredit` is a zero address and `msg.sender` is not the owner, `msg.sender` who gets the allowance from the `owner` can make a withdrawal request for the `owner`. ```solidity function makeWithdrawRequest(uint256 shares, address owner) external { require(shares > 0, "ZERO_VALUE"); require(openTradesPnlFeed.nextEpochValuesRequestCount() == 0, "END_OF_EPOCH"); address sender = _msgSender(); uint256 unlockEpoch = currentEpoch + withdrawEpochsTimelock(); if (address(tokenCredit) != address(0)) { require(sender == owner, "SENDER_MUSTBE_OWNER"); } else { require(sender == owner || (allowance(owner, sender) > 0 && allowance(owner, sender) >= shares), "NOT_ALLOWED"); } ... } ``` Besides, `msg.sender` who get the allowance from the `owner` can also redeem underlying asset on behalf of the `owner`. The related allowance will be deducted. ```solidity function redeem( uint256 shares, address receiver, address owner ) public override checks(shares, address(tokenCredit) != address(0)) returns (uint256) { if (address(tokenCredit) != address(0)) { require(_msgSender() == owner && _msgSender() == receiver, "SENDER_MUSTBE_OWNER"); } require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); withdrawRequests[owner][currentEpoch] -= shares; uint256 assets = previewRedeem(shares); scaleVariables(shares, assets, false); _withdraw(_msgSender(), receiver, owner, assets, shares); if (address(tokenCredit) != address(0)) { tokenCredit.notifyBalanceChange(_msgSender(), address(0)); } emit Redeemed(receiver, assets, currentEpoch); return assets; } ```

LowConcern on Reward ModelAcknowledged

According to the design of `HSStaking`, stakers can get `esHoldRewards` if they have staked tokens in the contract. The reward state will be updated when users staking, unstaking and claiming reward. ```solidity UserReward storage userReward = esHoldRewards[staker][currentEpoch + 1]; userReward.total = u.stakedTokens; userReward.isEmpty = userReward.total > 0 ? false : true; ``` Besides, based on the logic of the function `calculateEsHoldReward()`, epochs in which reward updates are not triggered will default to using the rewards from the previous update. ```solidity for (uint i = claimedAtEpoch[_staker]; i <= currentEpoch; i++) { UserReward memory userReward = esHoldRewards[_staker][i]; if (userReward.isEmpty) { lastChange = 0; continue; } if (userReward.total > 0) { lastChange = userReward.total; } totalReward += ((lastChange * epochRewards[i]) / PERCENTAGE_BASE); } ``` At last, epoch can only be increased by calling the function `forceNewEpoch()`. If the function is not called for an extended period, the epoch will be prolonged. This implies that each epoch's time period may not be equal.

LowDivide Before MultiplyAcknowledged

Performing integer division before multiplication truncates the low bits, losing the precision of calculation. --- ```solidity=376 return (( collatP <= 100 * PRECISION ? maxDiscountP : collatP <= maxDiscountThresholdP ? (maxDiscountP * (maxDiscountThresholdP - collatP)) / (maxDiscountThresholdP - 100 * PRECISION) : 0 ) * lockDuration) / MAX_LOCK_DURATION; ``` --- ```solidity=554 totalLiability += ((int256(shares) * totalLiability) / int256(supply)) * (isDeposit ? int256(1) : int256(-1)); ```

LowIncompatibility With Deflationary TokensAcknowledged

When transferring standard ERC20 deflationary tokens, the input amount may not be equal to the received amount due to the charged transaction fee. For example, if a user stakes 100 deflationary tokens (with a 10% transaction fee) in a staking contract, only 90 tokens actually arrive in the contract. However, the user can still withdraw 100 tokens from the contract, which causes the contract to lose 10 tokens in such a transaction. As a result, the following token contract involved in the `HSStaking` will need extra validation if any of them is a deflationary token: - `token`, if `token` is the deflationary token, the amount of staking should be revised. - `usdc`, if `usdc` is the deflationary token, the amount of pending reward should be revised. Similarly, in the contract `HSTradingVault`, the following token contract will need validation if a deflationary token implied: - `asset` token for ERC4626

LowLack of Sanity ChecksAcknowledged

In the `HSStaking` contract, the `setTimelock()` and `setUnstakeNumberEpochs()` functions lack sanity checks. ```solidity function setTimelock(uint _timelock) external onlyAdmin { timelock = _timelock; emit TimelockUpdated(_timelock); } function setUnstakeNumberEpochs(uint _value) external onlyAdmin { unstakeNumberEpochs = _value; emit UnstakeNumberEpochs(_value); } ``` When `timelock` is set to 0, anyone can call `forceNewEpoch()` without restrictions, spanning multiple epochs, and then call `claimESHoldReward()` to obtain a significant amount of `rewardToken`. When `unstakeNumberEpochs` is set to 0, stakers can retrieve their staking at any time, which exist potential risks. An attacker monitoring the `distributeRewardUsdc()` transaction could make substantial stakes before the rewards are distributed to the contract and swiftly unstake to capture a significant portion of the rewards.

LowMissing Initialization for `OwnableUpgradeable`Acknowledged

The `HSTradingVault` is initialized via `initialize` function. So, all inherited functions' initialization functions should be invoked. However, the inherited contract `OwnableUpgradeable` is not initialized in the current function `initialize`.

Show 10 more
LowMissing Zero Address ValidationAcknowledged

Addresses are not validated before assignment or external calls, potentially allowing the use of zero addresses and leading to unexpected behavior or vulnerabilities. For example, transferring tokens to a zero address can result in a permanent loss of those tokens. ```solidity=245 holdToken = _holdToken; ``` - `_holdToken` is not zero-checked before being used.

LowPossible Inflation Attack Arising from InheritedAcknowledged

When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation attack and is essentially a problem of slippage. For more details, please refer to https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/token/ERC20/extensions/ERC4626.sol. The exchange formula in the function `_convertToShares()` of `HSTradingVault` contract use `shareToAssetsPrice` as a factor which can be manipulated by executing `distributeReward()`.

LowPotential Inconsistent in `scaleVariables`Acknowledged

The `scaleVariables` function is triggered during `deposit`, `redeem`, and `mint` operations. This function adjusts the `accPnlPerToken` value when it is negative, as illustrated in the following code snippet: ```solidity if (accPnlPerToken < 0) { accPnlPerToken = (accPnlPerToken * int256(supply)) / (isDeposit ? int256(supply + shares) : int256(supply - shares)); } else if (accPnlPerToken > 0) { totalLiability += ((int256(shares) * totalLiability) / int256(supply)) * (isDeposit ? int256(1) : int256(-1)); } ``` However, in scenarios where `accPnlPerToken` is positive, the function does not modify `accPnlPerToken` but instead updates `totalLiability`. Notably, `totalLiability` appears to function solely as a record-keeping variable and does not influence any other state variables in the contract.

LowPotential Redundant Privilege Role `admin`Acknowledged

There are three main privileged role in `HSTradingVault`, owner, manager and admin. However, there is no functionality(except the admin address update) directly connected with admin role.

LowPotential Reward Locked RisksAcknowledged

When `usdc` is distributed to the `HSStaking` contract, the `distributeRewardUsdc()` function only updates `accUsdcPerToken` when `tokenBalance` is not zero. This portion of usdc cannot be distributed as rewards and remains in the contract. ```solidity function distributeRewardUsdc(uint256 _amount) external { usdc.transferFrom(_msgSender(), address(this), _amount); if (tokenBalance > 0) { accUsdcPerToken += (_amount * 1e18) / tokenBalance; totalRewardsDistributedUsdc += _amount; } emit UsdcDistributed(_amount); } ```

LowRedundant StatementsAcknowledged

The linked statements do not affect the functionality of the codebase and appear to be either remnants of test code or older functionality.

LowThird-Party Dependency UsageAcknowledged

The contract is serving as the underlying entity to interact with one or more third party protocols. The scope of the audit treats third party entities as black boxes and assumes their functional correctness. However, in the real world, third parties can be compromised and this may lead to lost or stolen assets. In addition, upgrades of third parties can possibly create severe impacts, such as increasing fees of third parties, migrating to new LP pools, etc. --- ```solidity=14 TokenInterfaceV5 public token; // Hold token ``` - The contract `HSStaking` interacts with third party contract with `TokenInterfaceV5` interface via `token`. --- ```solidity=15 TokenInterfaceV5 public usdc; //usdc ``` - The contract `HSStaking` interacts with third party contract with `TokenInterfaceV5` interface via `usdc`. --- ```solidity=16 TokenInterfaceV5 public rewardToken; //esHold ``` - The contract `HSStaking` interacts with third party contract with `TokenInterfaceV5` interface via `rewardToken`. --- ```solidity=19 address public hsToken; ``` - The contract `HSTradingVault` interacts with third party contract with `IHsToken` interface via `hsToken`. --- ```solidity=25 address public pnlHandler; ``` - The contract `HSTradingVault` allows `pnlHandler` to send assets to arbitrary addresses. --- ```solidity=26 IOpenTradesPnlFeed public openTradesPnlFeed; ``` - The contract `HSTradingVault` interacts with third party contract with `IOpenTradesPnlFeed` interface via `openTradesPnlFeed`. --- ```solidity=27 IHSTokenCredit public tokenCredit; ``` - The contract `HSTradingVault` interacts with third party contract with `IHSTokenCredit` interface via `tokenCredit`. --- ```solidity=28 HSPriceProvider public hsPriceProvider; ``` - The contract `HSTradingVault` interacts with third party contract with `HSPriceProvider` via `hsPriceProvider`.

LowTriggering Mechanism of the `forceNewEpoch()` FunAcknowledged

In the `HSStaking` contract, it allows entering the next epoch after a specific time period (initially set to 3 days) by calling the `forceNewEpoch()` function. In each epoch, users with stakes can obtain rewards. However, suppose there is no manual invocation of the `forceNewEpoch()` function after sufficient time has passed. In that case, it may result in the contract staying in the current epoch for an extended period, leading to unequal durations between epochs or the latency for unstake.

LowUnchecked ERC-20 `transfer()`/`transferFrom()` CaAcknowledged

The return values of the `transfer()` and `transferFrom()` calls in the smart contract are not checked. Some ERC-20 tokens' transfer functions return no values, while others return a bool value, they should be handled with care. If a function returns `false` instead of reverting upon failure, an unchecked failed transfer could be mistakenly considered successful in the contract. ```solidity=122 usdc.transferFrom(_msgSender(), address(this), _amount); ``` --- ```solidity=149 usdc.transfer(staker, pendingUsdc); ``` --- ```solidity=159 token.transferFrom(staker, address(this), _amount); ``` --- ```solidity=186 token.transfer(staker, amount); ```

LowUnprotected InitializerAcknowledged

One or more logic contracts do not protect their initializers. An attacker can call the initializer and assume ownership of the logic contract, whereby she can perform privileged operations that trick unsuspecting users into believing that she is the owner of the upgradeable contract. ```solidity=10 contract HSStaking is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { ``` - `HSStaking` is an upgradeable contract that does not protect its initializer. ```solidity=61 function initialize( ``` - `initialize` is an unprotected initializer function. --- ```solidity=15 contract HSTradingVault is ERC20Upgradeable, ERC4626Upgradeable, OwnableUpgradeable, IVault { ``` - `HSTradingVault` is an upgradeable contract that does not protect its initializer. ```solidity=154 function initialize( ``` - `initialize` is an unprotected initializer function.

Show 3 more
LowUnsafe Integer CastAcknowledged

Type casting refers to changing an variable of one data type into another. The code contains an unsafe cast between integer types, which may result in unexpected truncation or sign flipping of the value. --- ```solidity=551 (accPnlPerToken * int256(supply)) / ``` Casted expression `supply` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=552 (isDeposit ? int256(supply + shares) : int256(supply - shares)); ``` Casted expression `supply + shares` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=554 totalLiability += ((int256(shares) * totalLiability) / int256(supply)) * (isDeposit ? int256(1) : int256(-1)); ``` Casted expression `shares` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=611 int256 accPnlDelta = int256(assets.mulDiv(PRECISION, totalSupply(), MathUpgradeable.Rounding.Up)); ``` Casted expression `assets.mulDiv(PRECISION,totalSupply(),MathUpgradeable.Rounding.Up)` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=614 require(accPnlPerToken <= int256(maxAccPnlPerToken()), "NOT_ENOUGH_ASSETS"); ``` Casted expression `maxAccPnlPerToken()` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=618 require(dailyAccPnlDelta <= int256(maxDailyAccPnlDelta), "MAX_DAILY_PNL"); ``` Casted expression `maxDailyAccPnlDelta` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=620 totalLiability += int256(assets); ``` Casted expression `assets` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=621 totalClosedPnl += int256(assets); ``` Casted expression `assets` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=643 int256 accPnlDelta = int256((assetsLessDeplete * PRECISION) / totalSupply()); ``` Casted expression `(assetsLessDeplete * PRECISION) / totalSupply()` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=649 totalLiability -= int256(assetsLessDeplete); ``` Casted expression `assetsLessDeplete` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```solidity=650 totalClosedPnl -= int256(assetsLessDeplete); ``` Casted expression `assetsLessDeplete` has estimated range [0, 115792089237316195423570985008687907853269984665640564039457584007913129639935] but target type `int256` has range [-57896044618658097711785492504343953926634992332820282019728792003956564819968, 57896044618658097711785492504343953926634992332820282019728792003956564819967]. --- ```soli

LowUnused Parameter in `checks` ModifierAcknowledged

The second parameter in `checks` modifier is never used. However, there are three functions use this modifier and pass different values to the second parameter. --- ```solidity=225 modifier checks(uint256 assetsOrShares, bool checkIndex) { require(shareToAssetsPrice > 0, "PRICE_0"); require(assetsOrShares > 0, "VALUE_0"); _; } ``` --- ```solidity=496 function deposit(uint256 assets, address receiver) public override checks(assets, false) returns (uint256) ``` --- ```solidity=508 function mint(uint256 shares, address receiver) public override checks(shares, false) returns (uint256) ``` --- ```solidity=522 function redeem( uint256 shares, address receiver, address owner ) public override checks(shares, address(tokenCredit) != address(0)) returns (uint256) ```

LowUseful Code May Have Been Commented OutAcknowledged

There exist some variables and functions that are not utilized apart from the commented code. Therefore, it is possible that they should be incorporated into certain code logic, but have been neglected due to oversight or because the commented code has not been uncommented. ```solidity=364 function withdrawEpochsTimelock() public pure returns (uint256) { //fixed for distributing token credit return 5; // uint256 collatP = collateralizationP(); // uint256 overCollatP = (collatP - MathUpgradeable.min(collatP, 100 * PRECISION)); // return // overCollatP > withdrawLockThresholdsP[1] ? WITHDRAW_EPOCHS_LOCKS[2] : overCollatP > withdrawLockThresholdsP[0] // ? WITHDRAW_EPOCHS_LOCKS[1] // : WITHDRAW_EPOCHS_LOCKS[0]; } ``` --- ```solidity=45 uint256[] WITHDRAW_EPOCHS_LOCKS; ``` --- ```solidty=50 uint256[2] public withdrawLockThresholdsP; ``` ```solidity=341 function collateralizationP() public view returns (uint256) { // PRECISION (%) uint256 _maxAccPnlPerToken = maxAccPnlPerToken(); return (( accPnlPerTokenUsed > 0 ? (_maxAccPnlPerToken - uint256(accPnlPerTokenUsed)) : (_maxAccPnlPerToken + uint256(accPnlPerTokenUsed * (-1))) ) * 100 * PRECISION) / _maxAccPnlPerToken; } ```

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Part of Holdstation’s security record

Holdstation
Audits1 audit by 1 firmLast audit Jan 2024, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open Holdstation’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us