BeamSwap Staking

Smart Contract Audit Report

Audit Summary

BeamSwap Staking Audit Report BeamSwap is releasing stGLINT, a new ERC-20 token that allows users to earn dividends in various reward tokens.

For this audit, we reviewed the project team's BeamChefV2, stGlint, and Staking contracts at commit 2f90175a761d088e994ebae6dfec4e3dbfd0d0e1 on the team's private GitHub repository.

We previously reviewed the project team's trading and staking contracts here and Decentralized Exchange here.

Audit Findings

Low findings were identified and the team should consider resolving these issues. In addition, centralized aspects are present.
Date: May 16th, 2023.
Updated: May 17th, 2023 with changes from commit 1e00d358073dbb4c1af11ee314a590c971ca086a to commit 090fe53d720922281e28bedded91807cde78896d.
Updated: May 30th, 2023 with changes from commit 090fe53d720922281e28bedded91807cde78896d to commit 2f90175a761d088e994ebae6dfec4e3dbfd0d0e1.

Finding #1 - BeamChefV2 - High (Resolved)

Description: The payOrLockupPendingBeam() function pays out a portion of accumulated rewards as stGLINT tokens but does not subtract the stGLINT tokens from the rewards paid out.
Risk/Impact: The contract will mint insufficient GLINT tokens preventing some users from claiming their rewards.
Recommendation: The stGlintAmount must be subtracted from the pendingRewards amount before the GLINT tokens are transferred to the claiming user.
Resolution: The team has implemented the above recommendation.

Finding #2 - BeamChefV2 - High (Resolved)

Description: The payOrLockupPendingBeam() function calls the stGlint contract's convertTo() function without giving an allowance to the contract.
Risk/Impact: The convertTo() function will cause a DOS in the payOrLockupPendingBeam() function as there will be insufficient allowance to transfer GLINT tokens to the contract.
Recommendation: The stGlint address must be given allowance before each convertTo() call. Alternatively, the team can give max allowance to the stGlint address in the constructor.
Resolution: The team has given the stGlint address the max allowance in the BeamChefV2 constructor.

Finding #3 - Dividends - Low

Description: The addDividendsToPending() function is a non-access-controlled function that does not verify the specified token is a valid rewards token.
Risk/Impact: Users can add pending tokens for an invalid rewards token.
Recommendation: This function should be access-controlled or verify the specified token is a valid rewards token.
Resolution: The team has not yet addressed this issue.

Finding #4 - Dividends - Informational

Description: When a new rewards cycle starts, _updateDividendsInfo() function adds the currentDistributionAmount to the dividends distributed amount when the actual amount being distributed is currentDistributionAmount - currentCycleDistributedAmount.
Recommendation: The function should instead add currentDistributionAmount - currentCycleDistributedAmount to the distributedAmount.

Contracts Overview

  • The contracts utilize ReentrancyGuard to protect against reentrancy attacks in applicable functions.
  • As the contracts are implemented with Solidity v0.8, they are safe from any possible overflows/underflows.
  • The contracts properly account for fee-on-transfer tokens where applicable.
BeamChefV2 Contract:
  • Any user may deposit stGLINT into staking pools to earn rewards in GLINT tokens, stGlint tokens, and potentially other rewards from additional reward distributors.
  • Users may additionally provide a signed message to the contract allowing them to approve and deposit in one transaction to save on gas fees.
  • Users will receive a reward amount on each block based on the amount staked and the amount of points allocated to the pool.
  • A "stGlint ratio" of all pending rewards will additionally be minted to users as stGLINT tokens.
  • Users may only claim their rewards once per "harvest" period. Each pool has its own harvest period and may vary between pools, with a maximum allowed harvest period of 14 days.
  • On deposits and withdrawals, pending rewards are calculated and minted to the user, if eligible.
  • Users may additionally provide a list of pool ids to harvest any available rewards from each specified pool.
  • A deposit fee of up to 10% which is transferred to the Fee address controlled by the team. Each pool has its own deposit fee and may vary between pools.
  • A Beam Fee is taken from distributed GLINT rewards and minted to a Beam Share wallet controlled by the team.
  • The user may also trigger an emergency withdraw, which will transfer all the user's deposited LP tokens to their wallet address, without calculating rewards.
  • The owner may update the rewards emission rate and points allocated to each pool to any value at any time.
  • The owner may update the deposit fee, harvest interval, and additional reward distributors for each pool at any time.
  • The owner may update the stGlint, Beam Share, and Fee addresses at any time.
  • The owner may update the Beam Share fee and stGlint ratio to any values at any time.
stGlint Contract:
  • This contract defines the stGLINT token.
  • stGLINT tokens may only be transferred to or from a whitelisted address.
  • Users may receive stGLINT tokens by "converting" GLINT tokens at a 1-to-1 ratio.
  • Users may "redeem" their stGLINT tokens at any time. Token redeems will be vested over time.
  • The amount of GLINT tokens received will correspond to the vesting duration where a longer duration will result in more tokens up to the "max redeem duration".
  • The "redeem dividends adjustment" percentage of the stGLINT tokens being redeemed will be "allocated" to the Dividends address.
  • Users may manually allocate and deallocate non-redeem tokens to a specified "usage address". Allocated tokens will be stored in the contract.
  • A "deallocation fee" will be burned from tokens when deallocated. The deallocation fee may vary between usage addresses.
  • Users may "finalize" their redeem once the "min redeem duration" has elapsed.
  • The total stGLINT tokens will be burned and the user will receive the corresponding GLINT tokens after vesting.
  • Any GLINT tokens forfeited due to early redemption will be transferred to the 0xdEaD address.
  • The tokens currently allocated to the Dividends address will be deallocated.
  • Users may cancel a current redeem at any time.
  • Users may update the Dividend address their tokens are allocated to during a redeem at any time.
  • The owner may update the min and max redeem ratios and min and max redeem durations at any time.
  • The owner may update the redeem dividends adjustment at any time.
  • The owner may update the Dividends address at any time.
  • The owner may update the deallocation fee for any usage address at any time.
  • The owner may add and remove any address from the whitelist at any time.
Dividends Contract:
  • This contract allows users to "allocate" stGLINT tokens in order to earn rewards in potentially multiple tokens.
  • Users may allocate and deallocate tokens through the stGLINT contract.
  • Users may harvest their pending dividends for all rewards token at any time.
  • Users will earn rewards for all reward tokens during each rewards cycle based on the amount of stGLINT tokens they have allocated relative to the total allocated stGLINT tokens.
  • Any address may add tokens to the currently pending dividends for a specified rewards token.
  • The "cycle dividends percent" of pending tokens will be distributed each rewards cycle.
  • The cycle dividends percent may vary between each rewards token.
  • The owner may withdraw all tokens from the contract at any time.
  • The owner may enable and disable distribution for a rewards token at any time.
  • The owner may remove a rewards token at any time.
  • The owner may update the cycle dividends percent for any rewards token at any time.

Audit Results

Vulnerability Category Notes Result
Arbitrary Jump/Storage Write N/A PASS
Centralization of Control
  • The stGlint owner may add and remove any address from the whitelist.
  • The Dividends owner may withdraw all tokens from the contract.
PASS
Compiler Issues N/A PASS
Delegate Call to Untrusted Contract N/A PASS
Dependence on Predictable Variables N/A PASS
Ether/Token Theft N/A PASS
Flash Loans N/A PASS
Front Running N/A PASS
Improper Events N/A PASS
Improper Authorization Scheme N/A PASS
Integer Over/Underflow N/A PASS
Logical Issues N/A PASS
Oracle Issues N/A PASS
Outdated Compiler Version N/A PASS
Race Conditions N/A PASS
Reentrancy N/A PASS
Signature Issues N/A PASS
Sybil Attack N/A PASS
Unbounded Loops N/A PASS
Unused Code N/A PASS
Overall Contract Safety   PASS

BeamChefV2 Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

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

 +  ReentrancyGuard 
    - [Pub] Constructor #
    - [Prv] _nonReentrantBefore #
    - [Prv] _nonReentrantAfter #

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

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

 + [Int] IstGlint (IERC20)
    - [Ext] usageAllocations
    - [Ext] allocateFromUsage #
    - [Ext] convertTo #
    - [Ext] deallocateFromUsage #
    - [Ext] isTransferWhitelisted

 + [Int] IMultipleRewards 
    - [Ext] onBeamReward #
    - [Ext] pendingTokens
    - [Ext] rewardToken
    - [Ext] poolRewardsPerSec

 + [Int] IBeamSwapPair 
    - [Ext] initialize #
    - [Ext] permit #

 + [Lib] BoringERC20 
    - [Int] returnDataToString
    - [Int] safeSymbol
    - [Int] safeName
    - [Int] safeDecimals
    - [Int] safeTransfer #
    - [Int] safeTransferFrom #

 +  BeamChefV2 (Ownable, ReentrancyGuard)
    - [Pub] Constructor #
    - [Pub] startFarming #
       - modifiers: onlyOwner
    - [Ext] poolLength
    - [Pub] add #
       - modifiers: onlyOwner
    - [Pub] set #
       - modifiers: onlyOwner,validatePoolByPid
    - [Ext] pendingTokens
       - modifiers: validatePoolByPid
    - [Ext] poolRewardsPerSec
       - modifiers: validatePoolByPid
    - [Ext] poolRewarders
       - modifiers: validatePoolByPid
    - [Pub] canHarvest
       - modifiers: validatePoolByPid
    - [Ext] massUpdatePools #
       - modifiers: nonReentrant
    - [Int] _massUpdatePools #
    - [Ext] updatePool #
       - modifiers: nonReentrant
    - [Int] _updatePool #
       - modifiers: validatePoolByPid
    - [Pub] depositWithPermit #
       - modifiers: nonReentrant,validatePoolByPid
    - [Pub] deposit #
       - modifiers: nonReentrant
    - [Int] _deposit #
       - modifiers: validatePoolByPid
    - [Pub] withdraw #
       - modifiers: nonReentrant,validatePoolByPid
    - [Pub] emergencyWithdraw #
       - modifiers: nonReentrant
    - [Int] payOrLockupPendingBeam #
    - [Int] safeBeamTransfer #
    - [Pub] updateEmissionRate #
       - modifiers: onlyOwner
    - [Pub] updateAllocPoint #
       - modifiers: onlyOwner
    - [Ext] poolTotalLp
    - [Pub] harvestMany #
       - modifiers: nonReentrant
    - [Pub] setbeamShareAddress #
       - modifiers: onlyOwner
    - [Pub] setbeamSharePercent #
       - modifiers: onlyOwner
    - [Pub] setFeeAddress #
       - modifiers: onlyOwner
    - [Ext] updateStGlintRatio #
       - modifiers: onlyOwner
    - [Ext] updateStGlintAddress #
       - modifiers: onlyOwner

stGlint Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

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

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

 + [Int] IERC20Permit 
    - [Ext] permit #
    - [Ext] nonces
    - [Ext] DOMAIN_SEPARATOR

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

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

 + [Int] IERC20Metadata (IERC20)
    - [Ext] name
    - [Ext] symbol
    - [Ext] decimals

 +  ERC20 (Context, IERC20, IERC20Metadata)
    - [Pub] Constructor #
    - [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] _spendAllowance #
    - [Int] _beforeTokenTransfer #
    - [Int] _afterTokenTransfer #

 +  ReentrancyGuard 
    - [Pub] Constructor #
    - [Prv] _nonReentrantBefore #
    - [Prv] _nonReentrantAfter #

 + [Lib] EnumerableSet 
    - [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

 + [Int] IstGlint (IERC20)
    - [Ext] usageAllocations
    - [Ext] allocateFromUsage #
    - [Ext] convertTo #
    - [Ext] deallocateFromUsage #
    - [Ext] isTransferWhitelisted

 + [Int] IstGlintUsage 
    - [Ext] allocate #
    - [Ext] deallocate #

 +  stGlint (Ownable, ReentrancyGuard, ERC20, IstGlint)
    - [Pub] Constructor #
    - [Ext] getStGlintBalance
    - [Pub] getGlintByVestingDuration
    - [Ext] getUserRedeemsLength
    - [Ext] getUserRedeem
       - modifiers: validateRedeem
    - [Ext] getUsageApproval
    - [Ext] getUsageAllocation
    - [Ext] transferWhitelistLength
    - [Ext] transferWhitelist
    - [Ext] isTransferWhitelisted
    - [Ext] updateRedeemSettings #
       - modifiers: onlyOwner
    - [Ext] updateDividendsAddress #
       - modifiers: onlyOwner
    - [Ext] updateDeallocationFee #
       - modifiers: onlyOwner
    - [Ext] updateTransferWhitelist #
       - modifiers: onlyOwner
    - [Ext] approveUsage #
       - modifiers: nonReentrant
    - [Ext] convert #
       - modifiers: nonReentrant
    - [Ext] convertTo #
       - modifiers: nonReentrant
    - [Ext] redeem #
       - modifiers: nonReentrant
    - [Ext] finalizeRedeem #
       - modifiers: nonReentrant,validateRedeem
    - [Ext] updateRedeemDividendsAddress #
       - modifiers: nonReentrant,validateRedeem
    - [Ext] cancelRedeem #
       - modifiers: nonReentrant,validateRedeem
    - [Ext] allocate #
       - modifiers: nonReentrant
    - [Ext] allocateFromUsage #
       - modifiers: nonReentrant
    - [Ext] deallocate #
       - modifiers: nonReentrant
    - [Ext] deallocateFromUsage #
       - modifiers: nonReentrant
    - [Int] _convert #
    - [Int] _finalizeRedeem #
    - [Int] _allocate #
    - [Int] _deallocate #
    - [Int] _deleteRedeemEntry #
    - [Int] _beforeTokenTransfer
    - [Int] _currentBlockTimestamp

Dividends Contract

Smart Contract Audit - Inheritance

Smart Contract Audit - Graph


 ($) = payable function
 # = non-constant function
 
 Int = Internal
 Ext = External
 Pub = Public
 
 +  Context 
    - [Int] _msgSender
    - [Int] _msgData

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

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

 + [Int] IERC20Permit 
    - [Ext] permit #
    - [Ext] nonces
    - [Ext] DOMAIN_SEPARATOR

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

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

 +  ReentrancyGuard 
    - [Pub] Constructor #
    - [Prv] _nonReentrantBefore #
    - [Prv] _nonReentrantAfter #

 + [Lib] EnumerableSet 
    - [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

 + [Int] IDividends 
    - [Ext] distributedTokensLength
    - [Ext] distributedToken
    - [Ext] isDistributedToken
    - [Ext] addDividendsToPending #

 + [Int] IstGlintUsage 
    - [Ext] allocate #
    - [Ext] deallocate #

 +  Dividends (Ownable, ReentrancyGuard, IstGlintUsage, IDividends)
    - [Pub] Constructor #
    - [Ext] cycleDurationSeconds
    - [Ext] distributedTokensLength
    - [Ext] distributedToken
       - modifiers: validateDistributedTokensIndex
    - [Ext] isDistributedToken
    - [Pub] nextCycleStartTime
    - [Ext] pendingDividendsAmount
    - [Pub] updateCurrentCycleStartTime #
    - [Ext] updateDividendsInfo #
       - modifiers: validateDistributedToken
    - [Ext] massUpdateDividendsInfo #
    - [Ext] harvestDividends #
       - modifiers: nonReentrant
    - [Ext] harvestAllDividends #
       - modifiers: nonReentrant
    - [Ext] addDividendsToPending #
       - modifiers: nonReentrant
    - [Pub] emergencyWithdraw #
       - modifiers: nonReentrant,onlyOwner
    - [Ext] emergencyWithdrawAll #
       - modifiers: nonReentrant,onlyOwner
    - [Ext] allocate #
       - modifiers: nonReentrant,stGlintTokenOnly
    - [Ext] deallocate #
       - modifiers: nonReentrant,stGlintTokenOnly
    - [Ext] enableDistributedToken #
       - modifiers: onlyOwner
    - [Ext] disableDistributedToken #
       - modifiers: onlyOwner
    - [Ext] updateCycleDividendsPercent #
       - modifiers: onlyOwner
    - [Ext] removeTokenFromDistributedTokens #
       - modifiers: onlyOwner
    - [Int] _dividendsAmountPerSecond
    - [Int] _updateDividendsInfo #
    - [Int] _updateUser #
    - [Int] _harvestDividends #
    - [Int] _safeTokenTransfer #
    - [Int] _currentBlockTimestamp

About SourceHat

SourceHat has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1800+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!

Contact us today to get a free quote for a smart contract audit of your project!

What is a SourceHat Audit?

Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.

How Do I Interpret the Findings?

Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:

  • High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
  • Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
  • Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
  • Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.