Velhalla Staking - Audit Report
Audit Summary
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:
- tSCAR Token contract at 0x3CDdf5411260620175A07f722952e589403B3c9E.
- VelhallaToken contract at 0x892572e7B47A2a2a8Ea382b6c9843fAA87a77e60.
- VelhallaStaking contract at 0x3da5931Cf0Bf3C4AD64229399fa4eaac09B6AD2c .
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:VelhallaToken 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.
tSCARToken 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.
- 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.Risk/Impact: The user address receives more votes instead of having their votes deducted.function burn(address _from ,uint256 _amount) public onlyOwner { _burn(_from, _amount); _moveDelegates(address(0), _delegates[_from], _amount); }
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.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.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); }
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.Risk/Impact: 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); }
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.Risk/Impact:The forceWithdraw() function always users to withdraw their staked funds and rewards at any time bypassing the lock duration limitation.// Withdraw with ignoring leaveStakingLock function forceWithdraw(uint256 _amount) public whenNotPaused { leaveStaking(_amount); }
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.Recommendation: We recommend declaring these functions external for gas savings on each call.Functions: - VelhallaToken: mint, burn, safetScarTransfer - tSCARToken: mint - VelhallaStaking: updateMultiplier, set, deposit, claim, withdraw, forceWithdraw, emergencyWithdraw, getTotalStakeValue
Resolution: The team has declared these functions external instead of public.
External Threat Results
Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Storage Write | N/A | PASS |
Arbitrary Jump | N/A | PASS |
Centralization of Control | WARNING | |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Deprecated Opcodes | N/A | PASS |
Ether Thief | N/A | PASS |
Exceptions | N/A | PASS |
External Calls | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues | N/A | PASS |
Multiple Sends | N/A | PASS |
Suicide | N/A | PASS |
State Change External Calls | N/A | PASS |
Unchecked Retval | N/A | PASS |
User Supplied Assertion | N/A | PASS |
Critical Solidity Compiler | N/A | PASS |
Overall Contract Safety | PASS |
VelhallaToken Contract
($) = 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
($) = 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
($) = 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 #