Because there is no allowance set from == EarlyZEROVesting to spender == VestedZeroNFT, the TX will always revert (IERC20Errors.ERC20InsufficientBalance)
This approval is necessary for the function EarlyZEROVesting::startVesting:
functionstartVesting(uint256 amount,bool stake) external { require(enableVesting || stake,"vesting not enabled; staking only"); earlyZERO.burnFrom(msg.sender, amount);// Approve call here is necessary earlyZERO.approve(address(vesting),amount); ...
Impact Details
The main functionality of the contract EarlyZEROVesting, which is startVesting is broken and always reverts
References
PoC and .png shows the problem
Proof of Concept
Test
tests/test_vested.py
from wake.testing import*from pytypes.openzeppelin.contracts.proxy.ERC1967.ERC1967Proxy import ERC1967Proxyfrom pytypes.contracts.locker.OmnichainStaking import OmnichainStakingfrom pytypes.contracts.locker.LockerToken import LockerTokenfrom pytypes.contracts.locker.LockerLP import LockerLPfrom pytypes.tests.VeToken import VeTokenfrom pytypes.contracts.vesting.earlyzero.EarlyZEROVesting import EarlyZEROVestingfrom pytypes.contracts.vesting.earlyzero.EarlyZERO import EarlyZEROfrom pytypes.contracts.vesting.VestedZeroNFT import VestedZeroNFTfrom pytypes.contracts.vesting.StakingBonus import StakingBonus'''Test written in Wake testing framework (https://getwake.io/) aka boosted brownieDocs: https://ackeeblockchain.com/wake/docs/latest/Repo:https://github.com/Ackee-Blockchain/wakeHow to run this test:Install wake $ pip install eth-wakeTo have actual anvil version $ foundryupAfter installing project dependencies initialize wakeIt will create `tests` folder and process foundry remappings if any $ wake upGenerate python representation of contracts $ wake init pytypesGo to wake `tests` folder and paste this code in tests/test_vested.py and run $ wake test tests/test_vested.py'''defdeploy_with_proxy(contract): impl = contract.deploy() proxy = ERC1967Proxy.deploy(impl, b"")returncontract(proxy)# Print failing tx call tracedefrevert_handler(e: TransactionRevertedError):if e.tx isnotNone:print(e.tx.call_trace)print(e.tx.events)@default_chain.connect()@on_revert(revert_handler)deftest_vested():# ======================DEPLOY========================= # random = default_chain.accounts[9] owner = default_chain.accounts[0] bob = default_chain.accounts[1]# Deploy mock token zero_token = EarlyZERO.deploy(from_=owner) ve_token = VeToken.deploy(100*10**18, from_=bob)# Proxy deployment zero_vesting =deploy_with_proxy(EarlyZEROVesting) omnichain =deploy_with_proxy(OmnichainStaking) staking_bonus =deploy_with_proxy(StakingBonus) vested_zero_nft =deploy_with_proxy(VestedZeroNFT) locker_lp =deploy_with_proxy(LockerLP) locker_token =deploy_with_proxy(LockerToken)# Init deployment zero_vesting.init(zero_token, vested_zero_nft, staking_bonus, from_=owner) omnichain.init(random, locker_token, locker_lp, from_=owner) staking_bonus.init(zero_token, locker_token, vested_zero_nft, 100, from_=owner) vested_zero_nft.init(zero_token, staking_bonus, from_=owner) locker_lp.init(ve_token, omnichain, random, from_=owner) locker_token.init(ve_token, omnichain, random, from_=owner)# Send something to bob zero_token.transfer(bob, 100*10**18, from_=owner) zero_token.transfer(zero_vesting, 100*10**18, from_=owner)# Disable whitelist and blacklist zero_token.toggleWhitelist(False, False, from_=owner) zero_token.approve(zero_vesting, 100*10**18, from_=bob)print(vested_zero_nft.address)print(zero_vesting.address)#zero_token.approve(vested_zero, 100*10**18, from_=zero_vesting) zero_vesting.toggleVesting(from_=owner) zero_vesting.startVesting(100*10**18, True, from_=bob)# Just simply run test# On-revert handler will print call trace and errors of the reverting TX