Dopex SSOV

Smart Contract Audit Report

Audit Summary

Dopex SSOV Audit Report Dopex is building a single-asset staking and options vault in which users deposit $vBNB tokens to earn yield and can purchase BNB calls to earn additional profit.

For this audit, we reviewed the project team's BnbSSOV contract at commit fab8825ea20ebe0aa0a6723b146b3a265db3d07f on the team's private GitHub repository.

We previously reviewed the project team's options protocol here and their staking platform here.

Audit Findings

Please ensure trust in the team prior to investing as they have some control in the ecosystem.
Date: February 17th, 2022.

Contract Overview

  • This contract is designed to enable users to deposit any amount of $vBNB tokens and designate their deposit towards one or many strike prices set for the epoch.
  • In order for users to deposit into the Vault, the owner must first set the strike prices for the next epoch.

  • Once the depositing period has ended, the contract will deploy a new ERC-20 token for each strike price in the epoch representing each call option. The ERC-20 token contract was out of scope for the purposes of this audit, so our team cannot provide an assessment in terms of security.
  • For each of the ERC-20 tokens that were created, the contract will mint an amount equivalent to the BNB value of the $vBNB tokens designated towards that strike price in the epoch.
  • The contract derives the value of $vBNB tokens based on calculations performed in the vBNB token contract. The vBNB token contract was not in scope for this audit, so our team cannot provide an assessment in terms of security.

  • After the call tokens are created for the epoch, users can purchase calls at any valid strike price in the current epoch.
  • As the call tokens are transferred from the contract to the user to represent their purchased calls, it is guaranteed that there will always be enough funds in the Vault to cover any profits.
  • There is a premium and a fee charged on each purchased call; the premium amount is stored in the contract and any fees are transferred to the FeeDistributor address.
  • The premium is calculated using the OptionPricing and VolatilityOracle contracts; these contracts were not provided in the scope of this audit, so our team cannot provide an assessment in terms of security.
  • The purchase fee is calculated using the FeeStrategy contract; this contract was not provided in the scope of this audit, so our team cannot provide an assessment in terms of security.

  • Once the expiry date of the current epoch has passed, anyone can trigger the expireEpoch() function within the expiry delay tolerance window to mark the epoch as expired, pull the settlement price, and calculate profits/losses for each strike in the epoch.
  • The expiry date of each epoch is always on the last Friday of the month.
  • The settlement price is pulled from Chainlink's AggregatorV3 Price Feed; this is considered the industry standard and is resistant to price manipulations.
  • In the event that the expiry delay tolerance window has passed and the epoch has not yet been marked expired, the Governance address must call the expireEpoch() function and manually provide the settlement price for the epoch.
  • Upon ending the epoch, the final balances for each strike price are calculated by deducting any profits from the strike deposits and adding any premium paid from calls.

  • Once the epoch has expired, the calls may need to be settled.
  • Users can only settle their calls if there is any profit.
  • There is a settlement fee charged on each call, which is in part based on the settlement price and the PnL. The settlement fee is calculated in the FeeStrategy contract, which was not provided in the scope of this audit.
  • The settlement fees are transferred to the FeeDistributor address
  • The remaining profit after settlement fees are taken is transferred to the user.
  • The call tokens are burned from the user so that they cannot be exercised again.

  • Once the epoch has expired, users can withdraw their deposited $vBNB tokens from the Vault contract.
  • Upon withdrawal, users are entitled to a portion of the total vBNB balance for the strike price proportional to the amount they initially deposited in relation to the total amount that was deposited for that strike in the epoch.
  • As such, users' yield is funded with premiums paid for calls; users funds are subject to some losses based on the performance of the call options, as profits are funded with user's deposited funds.

  • The owner can choose to end the depositing period for the current epoch at any time.
  • The Governance address can pause and unpause all the functionality of the contract at any time.
  • Upon pausing, the final balances for each strike price in the current epoch are set without settling any calls.
  • When the contract is paused, the Governance address can withdraw any $vBNB tokens from the contract.
  • The Governance address can set the expiry delay tolerance value to any value at any time.
  • The owner can set the vBNB token contract, OptionPricing contract, ChainlinkAggregator, VolatilityOracle, FeeDistributor, FeeStrategy, and Governance addresses in the contract to any address at any time.
  • The owner can add or remove any address from the whitelist at any time.

  • Only whitelisted contracts are allowed to expire the epoch, purchase or settle calls, and withdraw staked funds; otherwise, all users must be Externally-Owned Accounts (EOAs).
  • The team must exercise caution when assigning the staking token and ensure that it is not a fee-on-transfer token; if a fee-on-transfer token is used as the staking token, then this contract must be exempt from all transfer fees.
  • The contract uses ReentrancyGuard in applicable functions to prevent any reentrancy issues.
  • As the contract is implemented with Solidity v0.8.x, it is safe from any underflow/overflow issues.

External Threat Results

Vulnerability CategoryNotesResult
Arbitrary Jump/Storage WriteN/APASS
Centralization of ControlThe Governance address can enter any settlement price after the expiry tolerance period has passed.PASS
Compiler IssuesN/APASS
Delegate Call to Untrusted ContractN/APASS
Dependence on Predictable VariablesN/APASS
Ether/Token TheftN/APASS
Flash LoansN/APASS
Front RunningN/APASS
Improper EventsN/APASS
Improper Authorization SchemeN/APASS
Integer Over/UnderflowN/APASS
Logical IssuesN/APASS
Oracle IssuesN/APASS
Outdated Compiler VersionN/APASS
Race ConditionsN/APASS
ReentrancyN/APASS
Signature IssuesN/APASS
Unbounded LoopsN/APASS
Unused CodeN/APASS
Overall Contract Safety PASS

Inheritence Chart

Smart Contract Audit - Inheritance

Function Graph

Smart Contract Audit - Graph

Functions Overview


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public

 + [Lib] Strings 
    - [Int] toString
    - [Int] toHexString
    - [Int] toHexString

 + [Lib] Clones 
    - [Int] clone #
    - [Int] cloneDeterministic #
    - [Int] predictDeterministicAddress
    - [Int] predictDeterministicAddress

 + [Lib] BokkyPooBahsDateTimeLibrary 
    - [Int] _daysFromDate
    - [Int] _daysToDate
    - [Int] timestampFromDate
    - [Int] timestampFromDateTime
    - [Int] timestampToDate
    - [Int] timestampToDateTime
    - [Int] isValidDate
    - [Int] isValidDateTime
    - [Int] isLeapYear
    - [Int] _isLeapYear
    - [Int] isWeekDay
    - [Int] isWeekEnd
    - [Int] getDaysInMonth
    - [Int] _getDaysInMonth
    - [Int] getDayOfWeek
    - [Int] getDayOfWeek
    - [Int] getYear
    - [Int] getMonth
    - [Int] getDay
    - [Int] getHour
    - [Int] getMinute
    - [Int] getSecond
    - [Int] addYears
    - [Int] addMonths
    - [Int] addDays
    - [Int] addHours
    - [Int] addMinutes
    - [Int] addSeconds
    - [Int] subYears
    - [Int] subMonths
    - [Int] subDays
    - [Int] subHours
    - [Int] subMinutes
    - [Int] subSeconds
    - [Int] diffYears
    - [Int] diffMonths
    - [Int] diffDays
    - [Int] diffHours
    - [Int] diffMinutes
    - [Int] diffSeconds

 + [Int] IERC20 
    - [Ext] totalSupply
    - [Ext] symbol
    - [Ext] decimals
    - [Ext] balanceOf
    - [Ext] transfer #
    - [Ext] allowance
    - [Ext] approve #
    - [Ext] transferFrom #

 + [Lib] SafeMath 
    - [Int] tryAdd
    - [Int] trySub
    - [Int] tryMul
    - [Int] tryDiv
    - [Int] tryMod
    - [Int] add
    - [Int] sub
    - [Int] mul
    - [Int] div
    - [Int] mod
    - [Int] sub
    - [Int] div
    - [Int] mod

 + [Lib] Address 
    - [Int] isContract
    - [Int] sendValue #
    - [Int] functionCall #
    - [Int] functionCall #
    - [Int] functionCallWithValue #
    - [Int] functionCallWithValue #
    - [Int] functionStaticCall
    - [Int] functionStaticCall
    - [Int] functionDelegateCall #
    - [Int] functionDelegateCall #
    - [Int] verifyCallResult

 + [Lib] SafeERC20 
    - [Int] safeTransfer #
    - [Int] safeTransferFrom #
    - [Int] safeApprove #
    - [Int] safeIncreaseAllowance #
    - [Int] safeDecreaseAllowance #
    - [Prv] _callOptionalReturn #

 +  ReentrancyGuard 
    - [Pub]  #

 + [Int] IERC20Upgradeable 
    - [Ext] totalSupply
    - [Ext] balanceOf
    - [Ext] transfer #
    - [Ext] allowance
    - [Ext] approve #
    - [Ext] transferFrom #

 + [Int] IERC20MetadataUpgradeable (IERC20Upgradeable)
    - [Ext] name
    - [Ext] symbol
    - [Ext] decimals

 +  Initializable 

 +  ContextUpgradeable (Initializable)
    - [Int] __Context_init #
       - modifiers: initializer
    - [Int] __Context_init_unchained #
       - modifiers: initializer
    - [Int] _msgSender
    - [Int] _msgData

 +  ERC20Upgradeable (Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable)
    - [Int] __ERC20_init #
       - modifiers: initializer
    - [Int] __ERC20_init_unchained #
       - modifiers: initializer
    - [Pub] name
    - [Pub] symbol
    - [Pub] decimals
    - [Pub] totalSupply
    - [Pub] balanceOf
    - [Pub] transfer #
    - [Pub] allowance
    - [Pub] approve #
    - [Pub] transferFrom #
    - [Pub] increaseAllowance #
    - [Pub] decreaseAllowance #
    - [Int] _transfer #
    - [Int] _mint #
    - [Int] _burn #
    - [Int] _approve #
    - [Int] _beforeTokenTransfer #
    - [Int] _afterTokenTransfer #

 +  ERC20BurnableUpgradeable (Initializable, ContextUpgradeable, ERC20Upgradeable)
    - [Int] __ERC20Burnable_init #
       - modifiers: initializer
    - [Int] __ERC20Burnable_init_unchained #
       - modifiers: initializer
    - [Pub] burn #
    - [Pub] burnFrom #

 +  PausableUpgradeable (Initializable, ContextUpgradeable)
    - [Int] __Pausable_init #
       - modifiers: initializer
    - [Int] __Pausable_init_unchained #
       - modifiers: initializer
    - [Pub] paused
    - [Int] _pause #
       - modifiers: whenNotPaused
    - [Int] _unpause #
       - modifiers: whenPaused

 +  ERC20PausableUpgradeable (Initializable, ERC20Upgradeable, PausableUpgradeable)
    - [Int] __ERC20Pausable_init #
       - modifiers: initializer
    - [Int] __ERC20Pausable_init_unchained #
       - modifiers: initializer
    - [Int] _beforeTokenTransfer #

 + [Int] IAccessControlUpgradeable 
    - [Ext] hasRole
    - [Ext] getRoleAdmin
    - [Ext] grantRole #
    - [Ext] revokeRole #
    - [Ext] renounceRole #

 + [Int] IAccessControlEnumerableUpgradeable (IAccessControlUpgradeable)
    - [Ext] getRoleMember
    - [Ext] getRoleMemberCount

 + [Lib] StringsUpgradeable 
    - [Int] toString
    - [Int] toHexString
    - [Int] toHexString

 + [Int] IERC165Upgradeable 
    - [Ext] supportsInterface

 +  ERC165Upgradeable (Initializable, IERC165Upgradeable)
    - [Int] __ERC165_init #
       - modifiers: initializer
    - [Int] __ERC165_init_unchained #
       - modifiers: initializer
    - [Pub] supportsInterface

 +  AccessControlUpgradeable (Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable)
    - [Int] __AccessControl_init #
       - modifiers: initializer
    - [Int] __AccessControl_init_unchained #
       - modifiers: initializer
    - [Pub] supportsInterface
    - [Pub] hasRole
    - [Int] _checkRole
    - [Pub] getRoleAdmin
    - [Pub] grantRole #
       - modifiers: onlyRole
    - [Pub] revokeRole #
       - modifiers: onlyRole
    - [Pub] renounceRole #
    - [Int] _setupRole #
    - [Int] _setRoleAdmin #
    - [Int] _grantRole #
    - [Int] _revokeRole #

 + [Lib] EnumerableSetUpgradeable 
    - [Prv] _add #
    - [Prv] _remove #
    - [Prv] _contains
    - [Prv] _length
    - [Prv] _at
    - [Prv] _values
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] values
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] values
    - [Int] add #
    - [Int] remove #
    - [Int] contains
    - [Int] length
    - [Int] at
    - [Int] values

 +  AccessControlEnumerableUpgradeable (Initializable, IAccessControlEnumerableUpgradeable, AccessControlUpgradeable)
    - [Int] __AccessControlEnumerable_init #
       - modifiers: initializer
    - [Int] __AccessControlEnumerable_init_unchained #
       - modifiers: initializer
    - [Pub] supportsInterface
    - [Pub] getRoleMember
    - [Pub] getRoleMemberCount
    - [Int] _grantRole #
    - [Int] _revokeRole #

 +  ERC20PresetMinterPauserUpgradeable (Initializable, ContextUpgradeable, AccessControlEnumerableUpgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable)
    - [Pub] initialize #
       - modifiers: initializer
    - [Int] __ERC20PresetMinterPauser_init #
       - modifiers: initializer
    - [Int] __ERC20PresetMinterPauser_init_unchained #
       - modifiers: initializer
    - [Pub] mint #
    - [Pub] pause #
    - [Pub] unpause #
    - [Int] _beforeTokenTransfer #

 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

 +  Pausable (Context)
    - [Pub]  #
    - [Pub] paused
    - [Int] _pause #
       - modifiers: whenNotPaused
    - [Int] _unpause #
       - modifiers: whenPaused

 +  Ownable (Context)
    - [Pub]  #
    - [Pub] owner
    - [Pub] renounceOwnership #
       - modifiers: onlyOwner
    - [Pub] transferOwnership #
       - modifiers: onlyOwner
    - [Int] _transferOwnership #

 +  ContractWhitelist (Ownable)
    - [Ext] addToContractWhitelist #
       - modifiers: onlyOwner
    - [Ext] removeFromContractWhitelist #
    - [Pub] isContract

 + [Int] IVolatilityOracle 
    - [Ext] getVolatility

 + [Int] IOptionPricing 
    - [Ext] getOptionPrice

 + [Int] ISSOV 
    - [Ext] epochStrikeTokens
    - [Ext] getAddress
    - [Ext] currentEpoch
    - [Ext] epochStrikes
    - [Ext] settle #

 + [Int] IERC20SSOV (ISSOV)
    - [Ext] purchase #
    - [Ext] deposit #
    - [Ext] depositMultiple #

 + [Int] IFeeStrategy 
    - [Ext] calculatePurchaseFees
    - [Ext] calculateSettlementFees

 + [Int] IVBNB 
    - [Ext] mint ($)
    - [Ext] redeem #
    - [Ext] redeemUnderlying #
    - [Ext] balanceOf
    - [Ext] balanceOfUnderlying #
    - [Ext] exchangeRateCurrent #
    - [Ext] exchangeRateStored

 + [Int] IChainlinkV3Aggregator 
    - [Ext] decimals
    - [Ext] latestRoundData

 +  BnbSSOV (ContractWhitelist, ReentrancyGuard, Pausable, IERC20SSOV)
    - [Pub]  #
    - [Ext] pause #
       - modifiers: onlyGovernance
    - [Ext] unpause #
       - modifiers: onlyGovernance
    - [Ext] updateExpireDelayTolerance #
       - modifiers: onlyGovernance
    - [Ext] setAddresses #
       - modifiers: onlyOwner
    - [Ext] emergencyWithdraw #
       - modifiers: onlyGovernance,whenPaused
    - [Ext] expireEpoch #
       - modifiers: whenNotPaused,isEligibleSender,nonReentrant
    - [Ext] expireEpoch #
       - modifiers: onlyGovernance,whenNotPaused,nonReentrant
    - [Int] _updateFinalEpochBalances #
    - [Ext] bootstrap #
       - modifiers: onlyOwner,whenNotPaused
    - [Ext] setStrikes #
       - modifiers: onlyOwner,whenNotPaused
    - [Ext] deposit #
       - modifiers: nonReentrant
    - [Ext] depositMultiple #
       - modifiers: nonReentrant
    - [Int] _deposit #
       - modifiers: whenNotPaused,isEligibleSender
    - [Ext] purchase #
       - modifiers: whenNotPaused,nonReentrant,isEligibleSender
    - [Ext] settle #
       - modifiers: whenNotPaused,nonReentrant,isEligibleSender
    - [Ext] withdraw #
       - modifiers: whenNotPaused,nonReentrant,isEligibleSender
    - [Pub] getMonthlyExpiryFromTimestamp
    - [Int] concatenate
    - [Pub] vbnbToBnb
    - [Pub] bnbToVbnb
    - [Pub] calculatePnl
    - [Pub] calculatePremium
    - [Pub] calculatePurchaseFees
    - [Pub] calculateSettlementFees
    - [Pub] getEpochTimes
       - modifiers: epochGreaterThanZero
    - [Ext] getEpochStrikes
       - modifiers: epochGreaterThanZero
    - [Ext] getEpochStrikeTokens
       - modifiers: epochGreaterThanZero
    - [Ext] getTotalEpochStrikeDeposits
       - modifiers: epochGreaterThanZero
    - [Ext] getUserEpochDeposits
       - modifiers: epochGreaterThanZero
    - [Ext] getTotalEpochCallsPurchased
       - modifiers: epochGreaterThanZero
    - [Ext] getUserEpochCallsPurchased
       - modifiers: epochGreaterThanZero
    - [Ext] getTotalEpochPremium
       - modifiers: epochGreaterThanZero
    - [Ext] getUserEpochPremium
       - modifiers: epochGreaterThanZero
    - [Pub] getUsdPrice
    - [Pub] getAddress