Velhalla Staking - Audit Report

Audit Summary

VelhallaStaking Audit Report Velhalla is building a new Staking token with DAO and voting properties.

For this audit, we reviewed the following contracts on the Binance Smart Chain Testnet:

Audit Findings

Please ensure trust in the team prior to investing as they have substantial control in the ecosystem.
Date: January 8th, 2022.
Updated: January 19th, 2022 to address resolved issues and changes by the team.

Contracts Overview

Velhalla Staking Contract:
  • Any user may deposit $SCAR tokens in order to earn rewards in the form of more $SCAR tokens.
  • Staked tokens are locked within the staking contract until the lock time of 2 weeks has elapsed. Users are minted an equal amount of $VHT.
  • Users receive rewards per block based on the token stake time, multiplier bonus, and points allocated to the pool.
  • Users may withdraw their staked funds when the lock time has elapsed; rewards are then automatically claimed. An equal amount of the user's $VHT are burned.
  • Reward $SCAR are stored in the contract address which must be funded by the team.
  • The user can trigger an emergency withdraw, which will transfer all the user's deposited tokens to their wallet address, without calculating rewards.
  • The contract utilizes SafeMath to prevent overflow/underflow attacks in applicable functions.
  • The owner can withdraw all reward funds from the contract at any time.
  • The owner can update the reward multiplier to any value greater than 100% (1x) at any time.
  • The owner can update the allocation points of any staking pool at any time.
  • The owner can pause or unpause all functionality of the contract at any time.
  • The owner can halt all functionality except withdrawals at any time and once halted the contract cannot be unhalted.
  • The owner can disable staking withdrawals at any time.
  • The owner can enable and/or disable emergency withdrawals at any time.
VelhallaToken Contract:
  • The total supply of the token is set to 39,903 $VHT.
  • The owner can mint or burn any amount of $VHT at any time.
  • At the time of writing this report, 97.4% of the total supply is held by the team.
  • The other two holders own a cumulative 2.53% of the total supply.
  • Each Velhalla token represents votes intended to be used in a DAO where one token represents one vote.
  • Users may delegate their votes to another address allowing them to vote on behalf of the user.
  • Once votes are delegated, the user must explicitly delegate back to themselves to regain their votes.
  • Users also have the option to delegate through the use of a signed message generated off-chain, allowing for a gasless delegation for the user.
tSCARToken Contract:
  • The total supply of the token is set to 10 billion $tSCAR [10,000,000,000].
  • The owner can mint any amount of $tSCAR at any time.
  • While there is no burn functionality present, tokens can be sent to the 0x0..dead address to reduce the circulating supply, if desired.
  • At the time of writing this report, 98.6% of the total supply is held by the team.
  • The next five holders own a cumulative 1.07% of the total supply.
  • Each $tSCAR token represents votes intended to be used in a DAO where one token represents one vote.
  • Users may delegate their votes to another address allowing them to vote on behalf of the user.
  • Once votes are delegated, the user must explicitly delegate back to themselves to regain their votes.
  • Users also have the option to delegate through the use of a signed message generated off-chain, allowing for a gasless delegation for the user.
  • The owner can mint any amount of $tSCAR to any address at any time.

VelhallaToken.sol - Finding #1 - High

Description: The burn() function does not properly pass the parameters to move votes from the source address.
			
function burn(address _from ,uint256 _amount) public onlyOwner {
        _burn(_from, _amount);
        _moveDelegates(address(0), _delegates[_from], _amount);
    }
Risk/Impact: The user address receives more votes instead of having their votes deducted.
Recommendation: The project team should instead move delegates from the user to the 0x0 address.
Resolution: The project team has removed governance from the contract.

VelhallaToken.sol & tSCARToken.sol - Finding #2 - High

Description: The _transfer() function is missing a call to the _moveDelegates function.
			
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal {
        require(sender != address(0), 'BEP20: transfer from the zero address');
        require(recipient != address(0), 'BEP20: transfer to the zero address');

        _balances[sender] = _balances[sender].sub(amount, 'BEP20: transfer amount exceeds balance');
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }
Risk/Impact: Any user could delegate votes to a delegatee, then transfer their tokens to another address which is then able to delegate additional votes to the same delegatee by using those newly acquired tokens.
Recommendation: The project team should add a call to the _moveDelegates() function within the _transfer function so delegated votes from those tokens are also transferred to the new user.
Resolution: The project team has removed governance from the contract.

VelhallaStaking.sol - Finding #3 - Medium

Description: There is an issue within the contract's reward functions in which if there are not enough $SCAR tokens in the contract for rewards, rewards would be funded with users' staked funds.
			
function leaveStaking(uint256 _amount) internal whenNotPaused{
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");

        updatePool();
        uint256 pending = user.amount.mul(pool.acctScarPerShare).div(1e31).sub(user.rewardDebt);
        if(pending > 0) {
            safetScarTransfer(msg.sender, pending);
        }
        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = user.amount.mul(pool.acctScarPerShare).div(1e31);

        vht.burn(msg.sender, _amount);
        emit Withdraw(msg.sender, 0, _amount);
    }
Risk/Impact: If there are not enough $SCAR tokens in the contract for rewards, rewards would be funded with users' staked funds.
Recommendation: The project team should differentiate between staked and reward $SCAR funds in the contract so users are always able to withdraw their original staked amount.
Resolution: The team has implemented a conditional check to confirm there are enough reward funds.

VelhallaStaking.sol - Finding #4 - Medium

Description:In normal scenarios the code enforces the lock time or requires the emergencyWithdrawLock to be true. The forceWithdraw() function appears contradictory to the goal of locking staked tokens.
			
     // Withdraw with ignoring leaveStakingLock
    function forceWithdraw(uint256 _amount) public whenNotPaused {
        leaveStaking(_amount);
    }
Risk/Impact:The forceWithdraw() function always users to withdraw their staked funds and rewards at any time bypassing the lock duration limitation.
Recommendation: The forceWithdraw() function should be removed.
Resolution: The forceWithdraw() function has been removed.

Optimization - Finding #5

Description: Several functions are declared public, but are never called internally.
			
Functions:
- VelhallaToken: mint, burn, safetScarTransfer
- tSCARToken: mint
- VelhallaStaking: updateMultiplier, set, deposit, claim, withdraw, forceWithdraw, emergencyWithdraw, getTotalStakeValue
Recommendation: We recommend declaring these functions external for gas savings on each call.
Resolution: The team has declared these functions external instead of public.

External Threat Results

Vulnerability CategoryNotesResult
Arbitrary Storage WriteN/APASS
Arbitrary JumpN/APASS
Centralization of Control
  • The team retains the ownership controls described above.
  • The owner can pause all functionality of the contract, including emergency withdrawals.
  • The owner can mint to and burn from any address at any time.
  • WARNING
    Delegate Call to Untrusted ContractN/APASS
    Dependence on Predictable VariablesN/APASS
    Deprecated OpcodesN/APASS
    Ether ThiefN/APASS
    ExceptionsN/APASS
    External CallsN/APASS
    Integer Over/UnderflowN/APASS
    Logical IssuesN/APASS
    Multiple SendsN/APASS
    SuicideN/APASS
    State Change External CallsN/APASS
    Unchecked RetvalN/APASS
    User Supplied AssertionN/APASS
    Critical Solidity CompilerN/APASS
    Overall Contract Safety PASS

    VelhallaToken Contract

    BEP20 Token Graph

    Multi-file Token

    												
    ($) = payable function
     # = non-constant function
    
      + [Lib] SafeMath 
        - [Int] add
        - [Int] sub
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] div
        - [Int] mod
        - [Int] mod
        - [Int] min
        - [Int] sqrt
    
     + [Int] IBEP20 
        - [Ext] totalSupply
        - [Ext] decimals
        - [Ext] symbol
        - [Ext] name
        - [Ext] getOwner
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Prv] _functionCallWithValue #
    
     + [Lib] SafeBEP20 
        - [Int] safeTransfer #
        - [Int] safeTransferFrom #
        - [Int] safeApprove #
        - [Int] safeIncreaseAllowance #
        - [Int] safeDecreaseAllowance #
        - [Prv] _callOptionalReturn #
    
     +  Context 
        - [Int]  #
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Int]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     +  Pausable (Context)
        - [Int]  #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  BEP20 (Context, IBEP20, Ownable)
        - [Pub]  #
        - [Ext] getOwner
        - [Pub] name
        - [Pub] decimals
        - [Pub] symbol
        - [Pub] totalSupply
        - [Pub] balanceOf
        - [Pub] transfer #
        - [Pub] allowance
        - [Pub] approve #
        - [Pub] transferFrom #
        - [Pub] increaseAllowance #
        - [Pub] decreaseAllowance #
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Int] _transfer #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _approve #
        - [Int] _burnFrom #
    
     +  tSCARToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId
    
     +  VelhallaToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Pub] burn #
           - modifiers: onlyOwner
        - [Pub]  #
        - [Pub] safetScarTransfer #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId

    tSCARToken Contract

    Bridge Contract Graph

    Multi-file Token

    												
    ($) = payable function
     # = non-constant function
    
     + [Lib] SafeMath 
        - [Int] add
        - [Int] sub
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] div
        - [Int] mod
        - [Int] mod
        - [Int] min
        - [Int] sqrt
    
     + [Int] IBEP20 
        - [Ext] totalSupply
        - [Ext] decimals
        - [Ext] symbol
        - [Ext] name
        - [Ext] getOwner
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Prv] _functionCallWithValue #
    
     + [Lib] SafeBEP20 
        - [Int] safeTransfer #
        - [Int] safeTransferFrom #
        - [Int] safeApprove #
        - [Int] safeIncreaseAllowance #
        - [Int] safeDecreaseAllowance #
        - [Prv] _callOptionalReturn #
    
     +  Context 
        - [Int]  #
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Int]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     +  Pausable (Context)
        - [Int]  #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  BEP20 (Context, IBEP20, Ownable)
        - [Pub]  #
        - [Ext] getOwner
        - [Pub] name
        - [Pub] decimals
        - [Pub] symbol
        - [Pub] totalSupply
        - [Pub] balanceOf
        - [Pub] transfer #
        - [Pub] allowance
        - [Pub] approve #
        - [Pub] transferFrom #
        - [Pub] increaseAllowance #
        - [Pub] decreaseAllowance #
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Int] _transfer #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _approve #
        - [Int] _burnFrom #
    
     +  tSCARToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId

    VelhallaStaking Contract

    Bridge Contract Graph

    Multi-file Token

    												
    ($) = payable function
     # = non-constant function
    
     + [Lib] SafeMath 
        - [Int] add
        - [Int] sub
        - [Int] sub
        - [Int] mul
        - [Int] div
        - [Int] div
        - [Int] mod
        - [Int] mod
        - [Int] min
        - [Int] sqrt
    
     + [Int] IBEP20 
        - [Ext] totalSupply
        - [Ext] decimals
        - [Ext] symbol
        - [Ext] name
        - [Ext] getOwner
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Prv] _functionCallWithValue #
    
     + [Lib] SafeBEP20 
        - [Int] safeTransfer #
        - [Int] safeTransferFrom #
        - [Int] safeApprove #
        - [Int] safeIncreaseAllowance #
        - [Int] safeDecreaseAllowance #
        - [Prv] _callOptionalReturn #
    
     +  Context 
        - [Int]  #
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Int]  #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     +  Pausable (Context)
        - [Int]  #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  BEP20 (Context, IBEP20, Ownable)
        - [Pub]  #
        - [Ext] getOwner
        - [Pub] name
        - [Pub] decimals
        - [Pub] symbol
        - [Pub] totalSupply
        - [Pub] balanceOf
        - [Pub] transfer #
        - [Pub] allowance
        - [Pub] approve #
        - [Pub] transferFrom #
        - [Pub] increaseAllowance #
        - [Pub] decreaseAllowance #
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Int] _transfer #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _approve #
        - [Int] _burnFrom #
    
     +  tSCARToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId
    
     +  VelhallaToken (BEP20)
        - [Pub] mint #
           - modifiers: onlyOwner
        - [Pub] burn #
           - modifiers: onlyOwner
        - [Pub]  #
        - [Pub] safetScarTransfer #
           - modifiers: onlyOwner
        - [Ext] delegates
        - [Ext] delegate #
        - [Ext] delegateBySig #
        - [Ext] getCurrentVotes
        - [Ext] getPriorVotes
        - [Int] _delegate #
        - [Int] _moveDelegates #
        - [Int] _writeCheckpoint #
        - [Int] safe32
        - [Int] getChainId
    
     +  VelhallaStaking (Ownable, Pausable)
        - [Pub]  #
        - [Pub] updateMultiplier #
           - modifiers: onlyOwner
        - [Ext] poolLength
        - [Pub] set #
           - modifiers: onlyOwner
        - [Int] updateStakingPool #
        - [Pub] getMultiplier
        - [Ext] pendingtScar
        - [Pub] updatePool #
           - modifiers: whenNotPaused
        - [Int] enterStaking #
           - modifiers: whenNotPaused
        - [Int] leaveStaking #
           - modifiers: whenNotPaused
        - [Pub] deposit #
           - modifiers: whenNotPaused
        - [Pub] claim #
           - modifiers: whenNotPaused
        - [Pub] withdraw #
           - modifiers: whenNotPaused
        - [Pub] forceWithdraw #
           - modifiers: whenNotPaused
        - [Pub] emergencyWithdraw #
           - modifiers: whenNotPaused
        - [Ext] pause #
           - modifiers: onlyOwner,whenNotPaused
        - [Ext] unpause #
           - modifiers: onlyOwner,whenPaused
        - [Pub] getTotalStakeValue
        - [Pub] getWithdrawLockTime
        - [Pub] setLeaveStakingLock #
           - modifiers: onlyOwner
        - [Pub] setEmergencyWithdrawLock #
           - modifiers: onlyOwner
        - [Int] safetScarTransfer #