T09 · Insecure Skill Coding Practices
Warning
- Location
- references/vesting-factory.md:91
- Finding
- Unchecked ERC20 Transfer Result Can Leave Vesting Wallets Unfunded<![CDATA[ ## Vulnerability Details **File Location**: `references/vesting-factory.md:91` **Vulnerability Type**: Unchecked ERC20 transfer return value **Risk Level**: Medium ### Vulnerable Code ```solidity IERC20(token).transfer(wallet, _getAllocation(entries[i].label)); ``` ### Technical Analysis The Foundry deployment example invokes `IERC20.transfer` without checking its Boolean return value. Although many ERC20 implementations revert when a transfer fails, some compliant and legacy tokens instead return `false`. If such a token is used, the script can continue after a failed transfer and log the newly created vesting wallet as though it had been funded. The wallet would exist and its schedule would appear valid, but it would not hold the promised allocation. The use of OpenZeppelin `SafeERC20` is recommended because it supports tokens that return `false`, tokens that return no value, and tokens that revert. ### Attack Path 1. The operator configures the deployment script with a token that returns `false` on failed transfers. 2. A vesting wallet is successfully created for a beneficiary. 3. The funding transfer fails because of insufficient balance, token restrictions, a pause, a blacklist, or adversarial token behavior. 4. The script does not inspect the returned value and therefore continues execution. 5. The wallet is logged or published as created and funded even though it has no corresponding token allocation. 6. The beneficiary later attempts to release vested tokens but receives nothing. ### Impact Assessment This issue does not grant additional system privileges. Its scope is the token funding operation performed by the deployment script. Affected beneficiaries may receive unfunded or underfunded vesting wallets. This can result in failed token distributions, inaccurate cap-table records, breach of investor commitments, operational recovery costs, and financial loss. An adversarial or nonstandard token can deliberately trigger this failur ...[truncated 88 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Use OpenZeppelin `SafeERC20` and verify the resulting wallet balance: ```solidity import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; using SafeERC20 for IERC20; uint256 allocation = _getAllocation(entries[i].label); uint256 balanceBefore = IERC20(token).balanceOf(wallet); IERC20(token).safeTransfer(wallet, allocation); require( IERC20(token).balanceOf(wallet) == balanceBefore + allocation, "Unexpected vesting-wallet funding amount" ); ``` Additional hardening measures: 1. Validate that the token address contains contract code before starting deployment. 2. Check the sender's available token balance against the total planned allocation. 3. Reject zero beneficiary addresses, zero allocations, and invalid schedule parameters. 4. Record the intended and actual funded amounts in deployment output. 5. Abort the entire batch on a funding mismatch instead of continuing with later entries. 6. Add tests using tokens that return `false`, return no value, charge transfer fees, pause transfers, or enforce blacklists. 7. For fee-on-transfer or rebasing tokens, explicitly reject them or calculate allocations from verified balance changes. ]]>
