Sunday, February 20, 2022

Static Analysis of Smart Contract

 

Tasks


The assessment objectives and respective statuses are summarized in the following table:


Objective

Status

  1. Audit any issues with Roles or Privileges 

    within the contract and what the impact 

    may be for any vulnerabilities due to 

    privileges.

Objective met - suicide vulnerabilies 

identified pertaining to improper access 

control

  1. Static Analysis issues

Objective met - ran slither on all contracts,

 identified over 14 vulnerabilities.

  1. Compile and Deploy the contract 

    set to a Local instance like Ganache to 

    perform testing on the contract set.

     In particular the "Private-Sale.sol" 

    contract may have some issues with

     protecting tokens. Show how it can be 

    exploited.

Objective met - Reentry exploit code

 produced and deployed to Ganache 

environment.

  1. Any other dynamic findings like fuzzing or

     formal verification and symbolic

     execution.

Objective not met - Did not perform 

dynamic testing using MithX due to 

commercial license required.




Summarized Findings


The following SmartContracts were subjected to static analysis using slither for common vulnerabilities:


  1. Private-Sale.sol,

  2. Halborn-Pool.sol,

  3. MyTokens.sol


The vulnerabilities identified with business impact are:


  1. Reentry (sev: high) - 3 instances found,

  2. State-variable-shadowing (high),

  3. Suicidal (sev: high) - 2 instances found,

  4. Unchecked-transfer (sev: high),

  5. Contract locking (sev: Med)


Note, all vulnerabilities with the impact of High & Medium are highlighted in yellow in the evidence section below. 

There were 2 instances found of improper access control or rather known as a “suicide” vulnerability in MyToken.sol & 

HalbornPool.sol respectively, it meant that a malicious attacker could self-destruct the contract. These vulnerabilities are 

categorized as SWC-106 at https://swcregistry.io/docs/SWC-106



The following vulnerabilities with informational severity are:


  1. Low level calls 

  2. Different Pragma Directives were used

  3. Conformance to Solidity naming conventions

  4. Dead Code

  5. Unused State Variable

  6. State Variable that Could be declared constant

  7. Different pragma in used

  8. Dead-Code

  9. Incorrect versions of Solidity


A Proof of Concept (PoC) exploit was produced for Private-Sales.sol to drain the Bank of all Ether by exploiting a Reentry

 vulnerability in lines 35-47. The PoC was built in remix and connected to the Ganache environment. The full Reentry exploit code (attacker.sol) 

was also added to this report. It was noted that the following SmartContracts did not return any issues during analysis:


  1. Migrations.sol,

  2. HalbornInterface.sol


Evidences

Static Analysis: Private-Sale.sol


$ slither Private-Sale.sol 


Reentrancy in PrivateSale.getRefund(uint256) (Private-Sale.sol#35-47):

    External calls:

    - (refunded) = msg.sender.call{value: quantity * TICKET}() (Private-Sale.sol#41)

    State variables written after the call(s):

    - purchasedTickets[msg.sender] -= quantity (Private-Sale.sol#45)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities


solc-0.8.10 is not recommended for deployment

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity


Low level call in PrivateSale.getRefund(uint256) (Private-Sale.sol#35-47):

    - (refunded) = msg.sender.call{value: quantity * TICKET}() (Private-Sale.sol#41)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls




Static Analysis: HalbornPool.sol


$ slither HalbornPool.sol 


Halborn._balances (MyToken.sol#12) shadows:

    - ERC20._balances (openzeppelin/contracts/token/ERC20/ERC20.sol#36)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing


Halborn.EmergencyDestroy(address) (MyToken.sol#30-33) allows anyone to destruct the contract

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal


DefiPool.deposit(uint256,address) (HalbornPool.sol#82-94) ignores return value by HAL.transferFrom(_sender,address(this),_amount) (HalbornPool.sol#87)

DefiPool.withdraw(address) (HalbornPool.sol#100-119) ignores return value by HAL.transfer(_user,userBalance[_user]) (HalbornPool.sol#117)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer


Contract locking ether found:

    Contract DefiPool (HalbornPool.sol#20-134) has payable functions:

    - HalbornInterface.deposit(uint256,address) (HalbornInterface.sol#6-7)

    - HalbornInterface.withdraw(address) (HalbornInterface.sol#9-10)

    - DefiPool.deposit(uint256,address) (HalbornPool.sol#82-94)

    - DefiPool.withdraw(address) (HalbornPool.sol#100-119)

    But does not have a function to withdraw the ether

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether


Reentrancy in DefiPool.withdraw(address) (HalbornPool.sol#100-119):

    External calls:

    - HAL.transfer(_user,userBalance[_user]) (HalbornPool.sol#117)

    State variables written after the call(s):

    - userBalance[_user] = userBalance[_user] - initialUserBalance (HalbornPool.sol#118)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1


Reentrancy in DefiPool.deposit(uint256,address) (HalbornPool.sol#82-94):

    External calls:

    - HAL.transferFrom(_sender,address(this),_amount) (HalbornPool.sol#87)

    State variables written after the call(s):

    - depositStart[msg.sender] = depositStart[msg.sender] + block.timestamp (HalbornPool.sol#91)

    - userBalance[_sender] = userBalance[_sender] + _amount (HalbornPool.sol#89)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2


Reentrancy in DefiPool.deposit(uint256,address) (HalbornPool.sol#82-94):

    External calls:

    - HAL.transferFrom(_sender,address(this),_amount) (HalbornPool.sol#87)

    Event emitted after the call(s):

    - Deposit(msg.sender,msg.value,block.timestamp) (HalbornPool.sol#93)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3


Different versions of Solidity is used:

    - Version used: ['^0.8.0', '^0.8.2']

    - ^0.8.2 (HalbornInterface.sol#2)

    - ^0.8.2 (HalbornPool.sol#2)

    - ^0.8.2 (MyToken.sol#2)

    - ^0.8.0 (openzeppelin/contracts/access/Ownable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/security/Pausable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/ERC20.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/IERC20.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4)

    - ^0.8.0 (openzeppelin/contracts/utils/Context.sol#4)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used


Context._msgData() (openzeppelin/contracts/utils/Context.sol#21-23) is never used and should be removed

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code


Pragma version^0.8.2 (HalbornInterface.sol#2) allows old versions

Pragma version^0.8.2 (HalbornPool.sol#2) allows old versions

Pragma version^0.8.2 (MyToken.sol#2) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/access/Ownable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/security/Pausable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/ERC20.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/IERC20.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/utils/Context.sol#4) allows old versions

solc-0.8.2 is not recommended for deployment

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity


Parameter DefiPool.deposit(uint256,address)._amount (HalbornPool.sol#82) is not in mixedCase

Parameter DefiPool.deposit(uint256,address)._sender (HalbornPool.sol#82) is not in mixedCase

Parameter DefiPool.withdraw(address)._user (HalbornPool.sol#100) is not in mixedCase

Function DefiPool.EmergencyWithraw(address,address,uint256) (HalbornPool.sol#131-133) is not in mixedCase

Variable DefiPool.HAL (HalbornPool.sol#44) is not in mixedCase

Function Halborn.EmergencyDestroy(address) (MyToken.sol#30-33) is not in mixedCase

Parameter Halborn.EmergencyDestroy(address)._to (MyToken.sol#30) is not in mixedCase

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions


Halborn.constructor() (MyToken.sol#13-15) uses literals with too many digits:

    - _mint(msg.sender,10000000000 * 10 ** decimals()) (MyToken.sol#14)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits


DefiPool.allowed (HalbornPool.sol#27) is never used in DefiPool (HalbornPool.sol#20-134)

Halborn._balances (MyToken.sol#12) is never used in Halborn (MyToken.sol#9-34)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable


transfer(address,uint256) should be declared external:

    - DefiPool.transfer(address,uint256) (HalbornPool.sol#32-37)

deposit(uint256,address) should be declared external:

    - DefiPool.deposit(uint256,address) (HalbornPool.sol#82-94)

withdraw(address) should be declared external:

    - DefiPool.withdraw(address) (HalbornPool.sol#100-119)

getContractBalance() should be declared external:

    - DefiPool.getContractBalance() (HalbornPool.sol#124-129)

EmergencyWithraw(address,address,uint256) should be declared external:

    - DefiPool.EmergencyWithraw(address,address,uint256) (HalbornPool.sol#131-133)

pause() should be declared external:

    - Halborn.pause() (MyToken.sol#16-18)

unpause() should be declared external:

    - Halborn.unpause() (MyToken.sol#20-22)

mint(address,uint256) should be declared external:

    - Halborn.mint(address,uint256) (MyToken.sol#24-26)

transferbyOwner(address,address,uint256) should be declared external:

    - Halborn.transferbyOwner(address,address,uint256) (MyToken.sol#27-29)

EmergencyDestroy(address) should be declared external:

    - Halborn.EmergencyDestroy(address) (MyToken.sol#30-33)

renounceOwnership() should be declared external:

    - Ownable.renounceOwnership() (openzeppelin/contracts/access/Ownable.sol#54-56)

transferOwnership(address) should be declared external:

    - Ownable.transferOwnership(address) (openzeppelin/contracts/access/Ownable.sol#62-65)

name() should be declared external:

    - ERC20.name() (openzeppelin/contracts/token/ERC20/ERC20.sol#62-64)

symbol() should be declared external:

    - ERC20.symbol() (openzeppelin/contracts/token/ERC20/ERC20.sol#70-72)

totalSupply() should be declared external:

    - ERC20.totalSupply() (openzeppelin/contracts/token/ERC20/ERC20.sol#94-96)

balanceOf(address) should be declared external:

    - ERC20.balanceOf(address) (openzeppelin/contracts/token/ERC20/ERC20.sol#101-103)

transfer(address,uint256) should be declared external:

    - ERC20.transfer(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#113-116)

approve(address,uint256) should be declared external:

    - ERC20.approve(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#132-135)

transferFrom(address,address,uint256) should be declared external:

    - ERC20.transferFrom(address,address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#150-164)

increaseAllowance(address,uint256) should be declared external:

    - ERC20.increaseAllowance(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#178-181)

decreaseAllowance(address,uint256) should be declared external:

    - ERC20.decreaseAllowance(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#197-205)

burn(uint256) should be declared external:

    - ERC20Burnable.burn(uint256) (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#20-22)

burnFrom(address,uint256) should be declared external:

    - ERC20Burnable.burnFrom(address,uint256) (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#35-42)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external

HalbornPool.sol analyzed (11 contracts with 77 detectors), 54 result(s) found










Static Analysis: MyTokens.sol



$ slither MyToken.sol 


Halborn._balances (MyToken.sol#12) shadows:

    - ERC20._balances (openzeppelin/contracts/token/ERC20/ERC20.sol#36)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing


Halborn.EmergencyDestroy(address) (MyToken.sol#30-33) allows anyone to destruct the contract

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal


Different versions of Solidity is used:

    - Version used: ['^0.8.0', '^0.8.2']

    - ^0.8.2 (MyToken.sol#2)

    - ^0.8.0 (openzeppelin/contracts/access/Ownable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/security/Pausable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/ERC20.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/IERC20.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#4)

    - ^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4)

    - ^0.8.0 (openzeppelin/contracts/utils/Context.sol#4)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used


Context._msgData() (openzeppelin/contracts/utils/Context.sol#21-23) is never used and should be removed

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code


Pragma version^0.8.2 (MyToken.sol#2) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/access/Ownable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/security/Pausable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/ERC20.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/IERC20.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4) allows old versions

Pragma version^0.8.0 (openzeppelin/contracts/utils/Context.sol#4) allows old versions

solc-0.8.10 is not recommended for deployment

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity


Function Halborn.EmergencyDestroy(address) (MyToken.sol#30-33) is not in mixedCase

Parameter Halborn.EmergencyDestroy(address)._to (MyToken.sol#30) is not in mixedCase

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions


Halborn.constructor() (MyToken.sol#13-15) uses literals with too many digits:

    - _mint(msg.sender,10000000000 * 10 ** decimals()) (MyToken.sol#14)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits


Halborn._balances (MyToken.sol#12) is never used in Halborn (MyToken.sol#9-34)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable


pause() should be declared external:

    - Halborn.pause() (MyToken.sol#16-18)

unpause() should be declared external:

    - Halborn.unpause() (MyToken.sol#20-22)

mint(address,uint256) should be declared external:

    - Halborn.mint(address,uint256) (MyToken.sol#24-26)

transferbyOwner(address,address,uint256) should be declared external:

    - Halborn.transferbyOwner(address,address,uint256) (MyToken.sol#27-29)

EmergencyDestroy(address) should be declared external:

    - Halborn.EmergencyDestroy(address) (MyToken.sol#30-33)

renounceOwnership() should be declared external:

    - Ownable.renounceOwnership() (openzeppelin/contracts/access/Ownable.sol#54-56)

transferOwnership(address) should be declared external:

    - Ownable.transferOwnership(address) (openzeppelin/contracts/access/Ownable.sol#62-65)

name() should be declared external:

    - ERC20.name() (openzeppelin/contracts/token/ERC20/ERC20.sol#62-64)

symbol() should be declared external:

    - ERC20.symbol() (openzeppelin/contracts/token/ERC20/ERC20.sol#70-72)

totalSupply() should be declared external:

    - ERC20.totalSupply() (openzeppelin/contracts/token/ERC20/ERC20.sol#94-96)

balanceOf(address) should be declared external:

    - ERC20.balanceOf(address) (openzeppelin/contracts/token/ERC20/ERC20.sol#101-103)

transfer(address,uint256) should be declared external:

    - ERC20.transfer(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#113-116)

approve(address,uint256) should be declared external:

    - ERC20.approve(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#132-135)

transferFrom(address,address,uint256) should be declared external:

    - ERC20.transferFrom(address,address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#150-164)

increaseAllowance(address,uint256) should be declared external:

    - ERC20.increaseAllowance(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#178-181)

decreaseAllowance(address,uint256) should be declared external:

    - ERC20.decreaseAllowance(address,uint256) (openzeppelin/contracts/token/ERC20/ERC20.sol#197-205)

burn(uint256) should be declared external:

    - ERC20Burnable.burn(uint256) (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#20-22)

burnFrom(address,uint256) should be declared external:

    - ERC20Burnable.burnFrom(address,uint256) (openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol#35-42)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external

MyToken.sol analyzed (8 contracts with 77 detectors), 35 result(s) found