Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ironsidesec - PerAddressContinuousVestingMerkle.claim will always revert #26

Closed
sherlock-admin3 opened this issue Jun 5, 2024 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin3
Copy link
Contributor

sherlock-admin3 commented Jun 5, 2024

Ironsidesec

high

PerAddressContinuousVestingMerkle.claim will always revert

Summary

Impact : beneficiary can never claim the tokens from PerAddressContinuousVestingMerkle contract
Likelihood : always, and issue also exists on PerAddressContinuousVestingMerkleDistributor.claim(), PerAddressTrancheVestingMerkleDistributor.claim(). But doesn't exist on PerAddressTrancheVesting.claim() because it is encoding the data properly

Root cause : start, cliff, end values are not encoded when computing getVestedFraction and it will revert when it tries to decode the zero bytes data into start, cliff, end

Vulnerability Detail

Issue flow :

  1. To claim the vested tokens , beneficiary should call PerAddressContinuousVestingMerkle.claim() with start, cliff, end to verify with merkle root. Look at line 68, it is sending new bytes(0) instead of abi.encode(start, cliff, end)

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/PerAddressContinuousVestingMerkle.sol#L67

2024-05-tokensoft-distributor-contracts-update\contracts\packages\hardhat\contracts\claim\PerAddressContinuousVestingMerkle.sol

53:   function claim(
54:     uint256 index, // the beneficiary's index in the merkle root
55:     address beneficiary, // the address that will receive tokens
56:     uint256 totalAmount, // the total claimable by this beneficiary
57:     uint256 start, // the start of the vesting period
58:     uint256 cliff, // cliff time
59:     uint256 end, // the end of the vesting period
60:     bytes32[] calldata merkleProof
61:   )
62:     external
63:     validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount, start, cliff, end)), merkleProof)
64:     nonReentrant

... SNIP ...

68: >>> uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0));
69:     // interactions
70:     _settleClaim(beneficiary, claimedAmount);

... SNIP ...
  1. _executeClaim on line 68 above calls L110 below and data = new bytes(0) is passed in L115 below.

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol#L102-L109

2024-05-tokensoft-distributor-contracts-update\contracts\packages\hardhat\contracts\claim\abstract\AdvancedDistributor.sol

110:   function _executeClaim(
... SNIP ...

114:   ) internal virtual override returns (uint256 _claimed) {
115: >>> _claimed = super._executeClaim(beneficiary, totalAmount, data);
116:     _reconcileVotingPower(beneficiary);
117:   }
  1. L115 above calls L66 below with data = new bytes(0) and it will call getClaimableAmount internall. in L124 below it is again calling getVestedFraction internally to compute how much is vested based on current delayed time between start, cliff, end

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/Distributor.sol#L79

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/Distributor.sol#L123

2024-05-tokensoft-distributor-contracts-update\contracts\packages\hardhat\contracts\claim\abstract\Distributor.sol

66:   function _executeClaim(
67:     address beneficiary,
68:     uint256 _totalAmount,
69:     bytes memory data
70:   ) internal virtual returns (uint256) {
... SNIP ...
79:     
80: >>> uint120 claimableAmount = uint120(getClaimableAmount(beneficiary, data));
81:     require(claimableAmount > 0, 'Distributor: no more tokens claimable right now');
82: 
... SNIP ...
87:   }

119:   function getClaimableAmount(address beneficiary, bytes memory data) public view virtual returns (uint256) {
... SNIP ...
123: 
124: >>>   uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp, data)) /
125:       fractionDenominator;
... SNIP ...
130:   }
  1. In L25 below the data is decoded into start, cliff, end, since we sent data = new bytes(0) from starting, this will trigger the revert.
    https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol#L25
2024-05-tokensoft-distributor-contracts-update\contracts\packages\hardhat\contracts\claim\abstract\PerAddressContinuousVesting.sol

20: 	function getVestedFraction(
21: 		address beneficiary,
22: 		uint256 time, // time is in seconds past the epoch (e.g. block.timestamp)
23:  >>>    bytes memory data
24: 	) public view override returns (uint256) {
25:  >>> (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (uint256, uint256, uint256));
26: 
... SNIP ...
43: 		// some tokens are vested
44: 		return (fractionDenominator * (delayedTime - start)) / (end - start);
45: 	}

Impact

Impact : beneficiary can never claim the tokens from PerAddressContinuousVestingMerkle contract
Likelihood : always, and issue also exists on PerAddressContinuousVestingMerkleDistributor.claim(), PerAddressTrancheVestingMerkleDistributor.claim(). But doesn't exist on PerAddressTrancheVesting.claim() because it is encoding the data properly

Code Snippet

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/PerAddressContinuousVestingMerkle.sol#L67

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol#L102-L109

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/Distributor.sol#L79

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/Distributor.sol#L123

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol#L25

Tool used

Manual Review

Recommendation

https://github.com/sherlock-audit/2024-05-tokensoft-distributor-contracts-update/blob/67edd899612619b0acefdcb0783ef7a8a75caeac/contracts/packages/hardhat/contracts/claim/PerAddressContinuousVestingMerkle.sol#L67

  function claim(
    uint256 index, // the beneficiary's index in the merkle root
    address beneficiary, // the address that will receive tokens
    uint256 totalAmount, // the total claimable by this beneficiary
    uint256 start, // the start of the vesting period
    uint256 cliff, // cliff time
    uint256 end, // the end of the vesting period
    bytes32[] calldata merkleProof
  )
    external
    validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount, start, cliff, end)), merkleProof)
    nonReentrant
  {
    // effects
-   uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0));
+   uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, abi.encode(start, cliff, end));
    // interactions
    _settleClaim(beneficiary, claimedAmount);
  }

Duplicate of #11

@github-actions github-actions bot closed this as completed Jun 6, 2024
@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Jun 6, 2024
@sherlock-admin4 sherlock-admin4 changed the title Atomic Opaque Quail - PerAddressContinuousVestingMerkle.claim will always revert Ironsidesec - PerAddressContinuousVestingMerkle.claim will always revert Jun 7, 2024
@sherlock-admin4 sherlock-admin4 added the Reward A payout will be made for this issue label Jun 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

2 participants