// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {SafeVesting} from "../src/SafeVesting.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract TestToken is ERC20 { constructor() ERC20("Test", "TEST") {} function mint(address to, uint256 amount) external { _mint(to, amount); } } contract SafeVestingTest is Test { SafeVesting vesting; address beneficiary = makeAddr("beneficiary"); uint64 start; function setUp() public { start = uint64(block.timestamp + 100); vesting = new SafeVesting(beneficiary, start, 1000); vm.deal(address(vesting), 10 ether); } function testFixedScheduleAndBeneficiary() public view { assertEq(vesting.owner(), beneficiary); assertEq(vesting.start(), start); assertEq(vesting.duration(), 1000); } function testBeneficiaryCannotBeChangedOrRenounced() public { vm.startPrank(beneficiary); vm.expectRevert(SafeVesting.BeneficiaryImmutable.selector); vesting.transferOwnership(makeAddr("replacement")); vm.expectRevert(SafeVesting.BeneficiaryImmutable.selector); vesting.renounceOwnership(); vm.stopPrank(); assertEq(vesting.owner(), beneficiary); } function testLinearReleaseSemantics() public { vesting.release(); assertEq(beneficiary.balance, 0); vm.warp(start + 500); vesting.release(); assertEq(beneficiary.balance, 5 ether); vm.warp(start + 1000); vesting.release(); assertEq(beneficiary.balance, 10 ether); } function testAnyoneCanTriggerButOnlyBeneficiaryReceives() public { vm.warp(start + 1000); address caller = makeAddr("caller"); vm.prank(caller); vesting.release(); assertEq(caller.balance, 0); assertEq(beneficiary.balance, 10 ether); } function testERC20ReleaseUsesSameSchedule() public { TestToken token = new TestToken(); token.mint(address(vesting), 1000 ether); vm.warp(start + 250); vesting.release(address(token)); assertEq(token.balanceOf(beneficiary), 250 ether); vm.warp(start + 1000); vesting.release(address(token)); assertEq(token.balanceOf(beneficiary), 1000 ether); } }