From edbaad5ec0971ca48cda4b525946958500d4222f Mon Sep 17 00:00:00 2001 From: Ragnar Date: Sun, 21 Sep 2025 00:34:42 +0200 Subject: [PATCH 1/6] Update VestingWallet.sol --- contracts/finance/VestingWallet.sol | 44 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 03024fae75b..4b330606fd1 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -133,14 +133,36 @@ contract VestingWallet is Context, Ownable { * @dev Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. */ function vestedAmount(uint64 timestamp) public view virtual returns (uint256) { - return _vestingSchedule(address(this).balance + released(), timestamp); + uint256 balance = address(this).balance; + uint256 releasedAmount = released(); + + // Check for overflow and cap at type(uint256).max + uint256 totalAllocation = balance; + if (totalAllocation <= type(uint256).max - releasedAmount) { + totalAllocation += releasedAmount; + } else { + totalAllocation = type(uint256).max; + } + + return _vestingSchedule(totalAllocation, timestamp); } /** * @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. */ function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { - return _vestingSchedule(IERC20(token).balanceOf(address(this)) + released(token), timestamp); + uint256 balance = IERC20(token).balanceOf(address(this)); + uint256 releasedAmount = released(token); + + // Check for overflow and cap at type(uint256).max + uint256 totalAllocation = balance; + if (totalAllocation <= type(uint256).max - releasedAmount) { + totalAllocation += releasedAmount; + } else { + totalAllocation = type(uint256).max; + } + + return _vestingSchedule(totalAllocation, timestamp); } /** @@ -153,7 +175,23 @@ contract VestingWallet is Context, Ownable { } else if (timestamp >= end()) { return totalAllocation; } else { - return (totalAllocation * (timestamp - start())) / duration(); + uint256 timeElapsed = timestamp - start(); + uint256 durationValue = duration(); + + // Handle edge case where timeElapsed is 0 + if (timeElapsed == 0) { + return 0; + } + + // Check for overflow in multiplication + if (totalAllocation <= type(uint256).max / timeElapsed) { + return (totalAllocation * timeElapsed) / durationValue; + } else { + // If multiplication would overflow, use a different approach + // Calculate the ratio first to avoid overflow + return (totalAllocation / durationValue) * timeElapsed + + ((totalAllocation % durationValue) * timeElapsed) / durationValue; + } } } } From 742876c781b66cc158d1a436f6d57be0e245995f Mon Sep 17 00:00:00 2001 From: Ragnar Date: Sun, 21 Sep 2025 00:40:34 +0200 Subject: [PATCH 2/6] linter --- contracts/finance/VestingWallet.sol | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 4b330606fd1..81b508b1f41 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -135,7 +135,7 @@ contract VestingWallet is Context, Ownable { function vestedAmount(uint64 timestamp) public view virtual returns (uint256) { uint256 balance = address(this).balance; uint256 releasedAmount = released(); - + // Check for overflow and cap at type(uint256).max uint256 totalAllocation = balance; if (totalAllocation <= type(uint256).max - releasedAmount) { @@ -143,7 +143,7 @@ contract VestingWallet is Context, Ownable { } else { totalAllocation = type(uint256).max; } - + return _vestingSchedule(totalAllocation, timestamp); } @@ -153,7 +153,7 @@ contract VestingWallet is Context, Ownable { function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { uint256 balance = IERC20(token).balanceOf(address(this)); uint256 releasedAmount = released(token); - + // Check for overflow and cap at type(uint256).max uint256 totalAllocation = balance; if (totalAllocation <= type(uint256).max - releasedAmount) { @@ -161,7 +161,7 @@ contract VestingWallet is Context, Ownable { } else { totalAllocation = type(uint256).max; } - + return _vestingSchedule(totalAllocation, timestamp); } @@ -177,20 +177,23 @@ contract VestingWallet is Context, Ownable { } else { uint256 timeElapsed = timestamp - start(); uint256 durationValue = duration(); - + // Handle edge case where timeElapsed is 0 if (timeElapsed == 0) { return 0; } - + // Check for overflow in multiplication if (totalAllocation <= type(uint256).max / timeElapsed) { return (totalAllocation * timeElapsed) / durationValue; } else { // If multiplication would overflow, use a different approach // Calculate the ratio first to avoid overflow - return (totalAllocation / durationValue) * timeElapsed + - ((totalAllocation % durationValue) * timeElapsed) / durationValue; + return + (totalAllocation / durationValue) * + timeElapsed + + ((totalAllocation % durationValue) * timeElapsed) / + durationValue; } } } From 6c897d457843c99c17fce982b5bd0459c59ccd6a Mon Sep 17 00:00:00 2001 From: Ragnar Date: Mon, 22 Sep 2025 18:32:19 +0200 Subject: [PATCH 3/6] Update contracts/finance/VestingWallet.sol Co-authored-by: Hadrien Croubois --- contracts/finance/VestingWallet.sol | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 81b508b1f41..1005a178de5 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -133,18 +133,7 @@ contract VestingWallet is Context, Ownable { * @dev Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. */ function vestedAmount(uint64 timestamp) public view virtual returns (uint256) { - uint256 balance = address(this).balance; - uint256 releasedAmount = released(); - - // Check for overflow and cap at type(uint256).max - uint256 totalAllocation = balance; - if (totalAllocation <= type(uint256).max - releasedAmount) { - totalAllocation += releasedAmount; - } else { - totalAllocation = type(uint256).max; - } - - return _vestingSchedule(totalAllocation, timestamp); + return _vestingSchedule(address(this).balance.saturatingAdd(released()), timestamp); } /** From c37e91624ef9f6ec432d8f78763a52c886a4c3dc Mon Sep 17 00:00:00 2001 From: Ragnar Date: Mon, 22 Sep 2025 18:32:25 +0200 Subject: [PATCH 4/6] Update contracts/finance/VestingWallet.sol Co-authored-by: Hadrien Croubois --- contracts/finance/VestingWallet.sol | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 1005a178de5..8e53cec7f0d 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -140,18 +140,7 @@ contract VestingWallet is Context, Ownable { * @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. */ function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { - uint256 balance = IERC20(token).balanceOf(address(this)); - uint256 releasedAmount = released(token); - - // Check for overflow and cap at type(uint256).max - uint256 totalAllocation = balance; - if (totalAllocation <= type(uint256).max - releasedAmount) { - totalAllocation += releasedAmount; - } else { - totalAllocation = type(uint256).max; - } - - return _vestingSchedule(totalAllocation, timestamp); + return _vestingSchedule(IERC20(token).balanceOf(address(this)).saturatingAdd(released(token)), timestamp); } /** From 47d73d16b19ee59259440eb282e29c0ed3a3f068 Mon Sep 17 00:00:00 2001 From: Ragnar Date: Mon, 22 Sep 2025 18:32:33 +0200 Subject: [PATCH 5/6] Update contracts/finance/VestingWallet.sol Co-authored-by: Hadrien Croubois --- contracts/finance/VestingWallet.sol | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 8e53cec7f0d..fbfaf1ec46c 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -153,26 +153,7 @@ contract VestingWallet is Context, Ownable { } else if (timestamp >= end()) { return totalAllocation; } else { - uint256 timeElapsed = timestamp - start(); - uint256 durationValue = duration(); - - // Handle edge case where timeElapsed is 0 - if (timeElapsed == 0) { - return 0; - } - - // Check for overflow in multiplication - if (totalAllocation <= type(uint256).max / timeElapsed) { - return (totalAllocation * timeElapsed) / durationValue; - } else { - // If multiplication would overflow, use a different approach - // Calculate the ratio first to avoid overflow - return - (totalAllocation / durationValue) * - timeElapsed + - ((totalAllocation % durationValue) * timeElapsed) / - durationValue; - } + return totalAllocation.mulDiv(timestamp - start(), duration()); } } } From 9058959b9fabd64361f44570ba5df9ecdfdacad9 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 23 Sep 2025 10:42:19 +0200 Subject: [PATCH 6/6] Update VestingWallet.sol --- contracts/finance/VestingWallet.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index fbfaf1ec46c..6819b3839e1 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; import {SafeERC20} from "../token/ERC20/utils/SafeERC20.sol"; +import {Math} from "../utils/math/Math.sol"; import {Address} from "../utils/Address.sol"; import {Context} from "../utils/Context.sol"; import {Ownable} from "../access/Ownable.sol"; @@ -33,6 +34,8 @@ import {Ownable} from "../access/Ownable.sol"; * Consider disabling one of the withdrawal methods. */ contract VestingWallet is Context, Ownable { + using Math for uint256; + event EtherReleased(uint256 amount); event ERC20Released(address indexed token, uint256 amount);