Skip to content

Latest commit

 

History

History
124 lines (93 loc) · 6.98 KB

Tokensoft.md

File metadata and controls

124 lines (93 loc) · 6.98 KB

Protocol Website Twitter Contest Pot nSLOC Length Start End
TokenSoft Website Twitter 14,000 USDC 650 4 days July 17, 2023 July 21, 2023

What is Tokensoft?

Tokensoft stands as a prominent provider of enterprise services and tools for blockchain foundations and projects. Our ultimate goal is to restore integrity to financial markets by seamlessly merging existing regulations with decentralized technology. In the dynamic realm of digital assets, security, efficiency, and compliance take center stage. Tokensoft equips teams with robust digital asset solutions that empower them to confidently navigate their challenges with convenience.

Audit scope

Issues found by Mlome

Severity Title Count
Medium Airdrop amount superior than type(uint120).max will result in stucked voting tokens [M-01]

[M-01] - Airdrop amount superior than type(uint120).max will result in stucked voting tokens

Summary

If the airdrop amount is more than type(uint120).max the transaction will not revert as expected but will result in voting tokens being stucked.

Vulnerability Detail

The following check is made on the local variable totalAmount that is already downcasted to uint120 instead of the uint256 _totalAmount argument. Hence, the test will always pass.

File claim/Distributor.sol

L47:	function _initializeDistributionRecord(
    address beneficiary,
    uint256 _totalAmount
  ) internal virtual {
    uint120 totalAmount = uint120(_totalAmount); // @audit - dangerous downcasting

    // Checks
    //@audit - this check will always pass because the check is on local variable instead of argument
    require(totalAmount <= type(uint120).max, 'Distributor: totalAmount > type(uint120).max');

    // Effects - note that the existing claimed quantity is re-used during re-initialization
    records[beneficiary] = DistributionRecord(true, totalAmount, records[beneficiary].claimed);
    emit InitializeDistributionRecord(beneficiary, totalAmount);
  }

Impact

An airdrop of more than type(uint120).max tokens will not revert as expected. Instead, the uint120(_totalAmount) tokens will be distributed and the total amount will be mint as voting tokens in AdvancedDistributor.sol > _initializeDistributionRecord().

File claim/abstract/AdvencedDistributor.sol

L77:	function _initializeDistributionRecord(
    address beneficiary,
    uint256 totalAmount
  ) internal virtual override {
    super._initializeDistributionRecord(beneficiary, totalAmount);

    // add voting power through ERC20Votes extension
    //@audit - tokensToVotes(totalAmount) is minted
    _mint(beneficiary, tokensToVotes(totalAmount));
  }

Then, once claiming the tokens only uint120(_totalAmount) voting tokens will be burned is AdvancedDistributor.sol > _executeClaim()

File claim/abstract/AdvencedDistributor.sol

L87:	function _executeClaim(
    address beneficiary,
    uint256 totalAmount
  ) internal virtual override returns (uint256 _claimed) {
    _claimed = super._executeClaim(beneficiary, totalAmount);

    // reduce voting power through ERC20Votes extension
    //@audit - only tokensToVotes(_claimed) amount is burned, i.e. uint120(totalAmount)
    _burn(beneficiary, tokensToVotes(_claimed));
  }

This results in totalAmount - uint120(totalAmount) voting tokens being stucked and a wrong tracking of voting power.

Tool used

Manual Review

Recommendation

Check the argument instead of the local variable:

File claim/Distributor.sol

L47:	function _initializeDistributionRecord(
    address beneficiary,
    uint256 _totalAmount
  ) internal virtual {
    uint120 totalAmount = uint120(_totalAmount); // @audit - dangerous downcasting

    // Checks
-    require(totalAmount <= type(uint120).max, 'Distributor: totalAmount > type(uint120).max');
+    require(_totalAmount <= type(uint120).max, 'Distributor: totalAmount > type(uint120).max');

    // Effects - note that the existing claimed quantity is re-used during re-initialization
    records[beneficiary] = DistributionRecord(true, totalAmount, records[beneficiary].claimed);
    emit InitializeDistributionRecord(beneficiary, totalAmount);
  }