VTVL contest - csanuragjain's results

Building no-code token management tools to empower web3 founders and investors, starting with token vesting.

General Information

Platform: Code4rena

Start Date: 20/09/2022

Pot Size: $30,000 USDC

Total HM: 12

Participants: 198

Period: 3 days

Judge: 0xean

Total Solo HM: 2

Id: 164

League: ETH

VTVL

Findings Distribution

Researcher Performance

Rank: 8/198

Findings: 4

Award: $805.45

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: TomJ

Also found by: ayeslick, csanuragjain, pashov

Labels

bug
duplicate
2 (Med Risk)

Awards

548.864 USDC - $548.86

External Links

Lines of code

https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L245

Vulnerability details

Impact

As per the comment in _createClaimUnchecked function, product team wanted _startTimestamp to be from past as well. But there is no limit on how much it could be in the past which means it could be last 100 years as well. In case it is too much in past, it would be possible to make claim for about 99% of amount at just starting of contract with no way for Admin to rectify the mistake (if user claims fast)

Proof of Concept

  1. Admin calls createClaim function and by mistake passes _startTimestamp as block.timestamp-100 years and _endTimestamp as block.timestamp+1month
function createClaim( address _recipient, uint40 _startTimestamp, uint40 _endTimestamp, uint40 _cliffReleaseTimestamp, uint40 _releaseIntervalSecs, uint112 _linearVestAmount, uint112 _cliffAmount ) external onlyAdmin { _createClaimUnchecked(_recipient, _startTimestamp, _endTimestamp, _cliffReleaseTimestamp, _releaseIntervalSecs, _linearVestAmount, _cliffAmount); }
  1. This internally calls _createClaimUnchecked where checks on _startTimestamp is performed. These are basic checks with no max limit on past date of _startTimestamp as shown below
function _createClaimUnchecked( address _recipient, uint40 _startTimestamp, uint40 _endTimestamp, uint40 _cliffReleaseTimestamp, uint40 _releaseIntervalSecs, uint112 _linearVestAmount, uint112 _cliffAmount ) private hasNoClaim(_recipient) { ... require(_startTimestamp > 0, "INVALID_START_TIMESTAMP"); require(_startTimestamp < _endTimestamp, "INVALID_END_TIMESTAMP"); // _endTimestamp must be after _startTimestamp require(_releaseIntervalSecs > 0, "INVALID_RELEASE_INTERVAL"); require((_endTimestamp - _startTimestamp) % _releaseIntervalSecs == 0, "INVALID_INTERVAL_LENGTH"); Claim memory _claim = Claim({ startTimestamp: _startTimestamp, endTimestamp: _endTimestamp, cliffReleaseTimestamp: _cliffReleaseTimestamp, releaseIntervalSecs: _releaseIntervalSecs, cliffAmount: _cliffAmount, linearVestAmount: _linearVestAmount, amountWithdrawn: 0, isActive: true }); ... }
  1. As we can see here the _startTimestamp we provided simply pass all test case and a new claim gets created

  2. Since _startTimestamp is 100 years in past from current date and _endTimestamp is block.timestamp+1month, so once this claim is made claim recipient would be able to make a claim of 99% already since as per contract 100 years have already passed on a vested duration of 100 years 1month

_currentTimestamp-_startTimestamp = block.timestamp-block.timestamp+100 years = 100 years

Make a limit on how back _startTimestamp could be. Like allowing claim to be maximum 1 year past current time

#0 - 0xean

2022-09-25T19:03:56Z

dupe of #292

Findings Information

🌟 Selected for report: Czar102

Also found by: 0xSmartContract, Lambda, Respx, csanuragjain, hansfriese, sashik_eth

Labels

bug
duplicate
2 (Med Risk)

Awards

228.641 USDC - $228.64

External Links

Lines of code

https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L407

Vulnerability details

Impact

Admin can withdraw locked funds

Proof of Concept

  1. User A did some dev work for a protocol and protocol decided to pay the User using the vesting contract
  2. Admin creates the claim for User A with Amount X for duration of 1 month
  3. One month passes and User A has not claimed the amount yet.
  4. Admin wants the steal the allotted amount back from dev but revokeClaim does not allow so
  5. Admin can use a bypass by using Reentrancy attack in withdrawAdmin function
function withdrawAdmin(uint112 _amountRequested) public onlyAdmin { // Allow the owner to withdraw any balance not currently tied up in contracts. uint256 amountRemaining = tokenAddress.balanceOf(address(this)) - numTokensReservedForVesting; require(amountRemaining >= _amountRequested, "INSUFFICIENT_BALANCE"); // Actually withdraw the tokens // Reentrancy note - this operation doesn't touch any of the internal vars, simply transfers // Also following Checks-effects-interactions pattern tokenAddress.safeTransfer(_msgSender(), _amountRequested); // Let the withdrawal known to everyone emit AdminWithdrawn(_msgSender(), _amountRequested); }
  1. Lets say below is status of contract
tokenAddress.balanceOf(address(this)) = 400 numTokensReservedForVesting = 300 amountRemaining = 400-300 = 100
  1. So Admin can withdraw 100 amount securing the 300 amount of User A

  2. Below code tries to transfer 100 amount to Admin contract

tokenAddress.safeTransfer(_msgSender(), _amountRequested);
  1. Assume tokenAddress is a custom ERC20 contract which makes one event call to recipient contract before transferring any amount.

  2. Once call is made to Admin contract, it reenters the withdrawAdmin function again. (Remember transfer has not completed yet). Since no state variables has changed so while reentering the state remains same

tokenAddress.balanceOf(address(this)) = 400 numTokensReservedForVesting = 300 amountRemaining = 400-300 = 100
  1. Again in this reentered state transfer call is made which is success and 100 amount is transferred to Admin
tokenAddress.safeTransfer(_msgSender(), _amountRequested);
  1. Now main call is executed which transfers amount 100 again making net transfer of 200 to Admin instead of 100

Mark the withdrawAdmin function with nonReentrant modifier (openzepplin ReentrancyGuard)

#0 - 0xean

2022-09-24T22:23:09Z

dupe of #6

Incorrect condition may prohibit valid scenario

Contract: https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/token/VariableSupplyERC20Token.sol#L27

Issue: In the constructor of VariableSupplyERC20Token.sol, it is always possible to have both initialSupply_ and maxSupply_ equal to zero (Admin allows unlimited minting with initial 0 tokens). However this is not possible due to below check

require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");

Recommendation: Remove the below check

require(initialSupply_ > 0 || maxSupply_ > 0, "INVALID_AMOUNT");

Awards

9.086 USDC - $9.09

Labels

bug
G (Gas Optimization)

External Links

Unrequired check wasting gas

Contract: https://github.com/code-423n4/2022-09-vtvl/blob/main/contracts/VTVLVesting.sol#L107

Issue: In hasActiveClaim function, there is no need to check require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM"); since it is more than enough to check _claim.isActive==true in hasActiveClaim function

Recommendation: Modify the hasActiveClaim function to eliminate the below require condition

require(_claim.startTimestamp > 0, "NO_ACTIVE_CLAIM");
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter