T09 · Insecure Skill Coding Practices
Error
- Location
- contracts/artifacts/build-info/a5d75f9256251cc8c46c7f853791cdf4.json:1
- Finding
- Repeatable Harvest Claims Can Drain Vault Underlying Assets## Vulnerability Details **File Location**: `contracts/artifacts/build-info/a5d75f9256251cc8c46c7f853791cdf4.json:1` **Vulnerability Type**: Missing reward-claim accounting and unbacked yield payment **Risk Level**: High The affected Solidity source is embedded in the Hardhat build-information artifact. The artifact is minified onto one line. ```solidity /** * @dev Harvest yields without reinvesting * In a real implementation, this would claim rewards from the underlying protocol */ function harvest() external whenNotPaused returns (uint256 yieldAmount) { require(shareBalance[msg.sender] > 0, "No shares to harvest"); // Stub: calculate yield based on shares and mock APR yieldAmount = calculateUserYield(msg.sender); require(yieldAmount > 0, "No yield available"); // Transfer yield to user (in real implementation, this would come from protocol) require( IERC20(underlying).transfer(msg.sender, yieldAmount), "Yield transfer failed" ); accumulatedYield += yieldAmount; // Emit events emit Harvest(msg.sender, yieldAmount); emit ExecutionRecorded( vaultId, "harvest", msg.sender, yieldAmount, 0, block.timestamp ); emit ActionExecuted( vaultId, "harvest", msg.sender, yieldAmount, true, "Harvest successful" ); return yieldAmount; } /** * @dev Calculate yield for a user (stub: 10% annual, daily accrual) */ function calculateUserYield(address user) public view returns (uint256) { if (shareBalance[user] == 0) { return 0; } uint256 userAssets = calculateAssetsFromShares(shareBalance[user]); // Stub: simple calculation (0.027% daily = ~10% APY) return (userAssets * 27) / 100000; } ``` ### Technical Analysis `calculateUserYield()` derives ...[truncated 2737 chars]
- Remediation
- ## Remediation Suggestions 1. Replace the fixed per-call calculation with cumulative reward-index accounting: - Maintain a global reward-per-share accumulator. - Record each user's reward debt or last observed index. - Calculate only rewards accrued since the previous checkpoint. - Update the checkpoint before transferring tokens. 2. Base distributable rewards on realized assets: - Claim rewards from the underlying protocol first. - Measure the actual token balance increase. - Credit only the verified increase to a reward reserve. - Never pay nominal rewards directly from depositor principal. 3. Keep accounting synchronized: - If assets leave the vault, update the corresponding asset or reward-reserve accounting atomically. - Require the actual underlying balance to cover all accounted liabilities. - Add explicit solvency checks around harvest and withdrawal operations. 4. Apply checks-effects-interactions: - Consume the user's accrued reward and update accounting before the external token transfer. - Add a reentrancy guard to all state-changing functions that transfer tokens. 5. Use a safe ERC-20 transfer library, such as OpenZeppelin `SafeERC20`, to support tokens that do not return standard Boolean values. 6. Add invariant and adversarial tests proving that: - A second harvest without new accrual returns zero. - Repeated harvest calls cannot reduce depositor principal. - The actual token balance remains sufficient to satisfy accounted assets. - Total successful claims cannot exceed realized rewards. - Multiple users receive only their proportional accrued rewards. 7. Treat the current contract as a non-production stub and prevent deployment or funding until the reward model receives an independent smart-contract audit.
