From 1df847b30ddc76bc87d63f17ec61bf6a2bb5cd0f Mon Sep 17 00:00:00 2001 From: pavelvm5 Date: Mon, 26 Aug 2024 18:00:25 +0500 Subject: [PATCH] accept right changes + removed outdated condition for combined test --- .../interfaces/IAggregatedStakeToken.sol | 194 ----------- src/contracts/interfaces/IStakeToken.sol | 162 ---------- tests/ERC20Std.t.sol | 98 ------ tests/ExchangeRate.t.sol | 2 +- tests/erc-4626/ERC4626.t.sol | 301 ------------------ tests/utils/ActionsLibrary.sol | 44 --- tests/utils/InvariantHandler.sol | 78 ----- 7 files changed, 1 insertion(+), 878 deletions(-) delete mode 100644 src/contracts/interfaces/IAggregatedStakeToken.sol delete mode 100644 src/contracts/interfaces/IStakeToken.sol delete mode 100644 tests/ERC20Std.t.sol delete mode 100644 tests/erc-4626/ERC4626.t.sol delete mode 100644 tests/utils/ActionsLibrary.sol delete mode 100644 tests/utils/InvariantHandler.sol diff --git a/src/contracts/interfaces/IAggregatedStakeToken.sol b/src/contracts/interfaces/IAggregatedStakeToken.sol deleted file mode 100644 index ced764c..0000000 --- a/src/contracts/interfaces/IAggregatedStakeToken.sol +++ /dev/null @@ -1,194 +0,0 @@ -pragma solidity ^0.8.10; - -interface IAggregatedStakeToken { - event Approval(address indexed owner, address indexed spender, uint256 value); - event AssetConfigUpdated(address indexed asset, uint256 emission); - event AssetIndexUpdated(address indexed asset, uint256 index); - event Cooldown(address indexed user, uint256 amount); - event DefaultWithdrawalWindowChanged(uint256 cooldownSeconds); - event DistributionEndChanged(uint256 endTimestamp); - event EIP712DomainChanged(); - event ExchangeRateChanged(uint216 exchangeRate); - event FundsReturned(uint256 amount); - event Initialized(uint64 version); - event MaxSlashablePercentageChanged(uint256 newPercentage); - event PendingAdminChanged(address indexed newPendingAdmin, uint256 role); - event Redeem(address indexed from, address indexed to, uint256 assets, uint256 shares); - event RewardsAccrued(address user, uint256 amount); - event RewardsClaimed(address indexed from, address indexed to, uint256 amount); - event RoleClaimed(address indexed newAdmin, uint256 role); - event Slashed(address indexed destination, uint256 amount); - event SlashingExitWindowDurationChanged(uint256 windowSeconds); - event SlashingSettled(); - event Staked(address indexed from, address indexed to, uint256 assets, uint256 shares); - event Transfer(address indexed from, address indexed to, uint256 value); - event UserIndexUpdated(address indexed user, address indexed asset, uint256 index); - - struct AssetConfigInput { - uint128 emissionPerSecond; - uint256 totalStaked; - address underlyingAsset; - } - - function CLAIM_HELPER_ROLE() external view returns (uint256); - - function COOLDOWN_ADMIN_ROLE() external view returns (uint256); - - function DOMAIN_SEPARATOR() external view returns (bytes32); - - function EMISSION_MANAGER() external view returns (address); - - function EXCHANGE_RATE_UNIT() external view returns (uint256); - - function INITIAL_EXCHANGE_RATE() external view returns (uint216); - - function LOWER_BOUND() external view returns (uint256); - - function PRECISION() external view returns (uint8); - - function REWARDS_VAULT() external view returns (address); - - function REWARD_TOKEN() external view returns (address); - - function SLASH_ADMIN_ROLE() external view returns (uint256); - - function STAKED_TOKEN() external view returns (address); - - function UNSTAKE_WINDOW() external view returns (uint256); - - function allowance(address owner, address spender) external view returns (uint256); - - function approve(address spender, uint256 value) external returns (bool); - - function assets( - address - ) external view returns (uint128 emissionPerSecond, uint128 lastUpdateTimestamp, uint256 index); - - function balanceOf(address account) external view returns (uint256); - - function claimRewards(address to, uint256 amount) external; - - function claimRewardsAndRedeem(address to, uint256 claimAmount, uint256 redeemAmount) external; - - function claimRewardsAndRedeemOnBehalf( - address from, - address to, - uint256 claimAmount, - uint256 redeemAmount - ) external; - - function claimRewardsOnBehalf( - address from, - address to, - uint256 amount - ) external returns (uint256); - - function claimRoleAdmin(uint256 role) external; - - function configureAssets(AssetConfigInput[] memory assetsConfigInput) external; - - function cooldown() external; - - function cooldownOnBehalfOf(address from) external; - - function decimals() external view returns (uint8); - - function distributionEnd() external view returns (uint256); - - function eip712Domain() - external - view - returns ( - bytes1 fields, - string memory name, - string memory version, - uint256 chainId, - address verifyingContract, - bytes32 salt, - uint256[] memory extensions - ); - - function getAdmin(uint256 role) external view returns (address); - - function getDefaultCooldownSeconds() external view returns (uint256); - - function getExchangeRate() external view returns (uint216); - - function getMaxSlashablePercentage() external view returns (uint256); - - function getPendingAdmin(uint256 role) external view returns (address); - - function getTotalRewardsBalance(address staker) external view returns (uint256); - - function getUserAssetData(address user, address asset) external view returns (uint256); - - function inPostSlashingPeriod() external view returns (bool); - - function initialize( - string memory name, - string memory symbol, - address slashingAdmin, - address cooldownPauseAdmin, - address claimHelper, - uint256 maxSlashablePercentage, - uint256 cooldownSeconds - ) external; - - function name() external view returns (string memory); - - function nonces(address owner) external view returns (uint256); - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function previewRedeem(uint256 shares) external view returns (uint256); - - function previewStake(uint256 assets) external view returns (uint256); - - function redeem(address to, uint256 amount) external; - - function redeemOnBehalf(address from, address to, uint256 amount) external; - - function returnFunds(uint256 amount) external; - - function setDefaultCooldownSeconds(uint256 cooldownSeconds) external; - - function setDistributionEnd(uint256 newDistributionEnd) external; - - function setMaxSlashablePercentage(uint256 percentage) external; - - function setPendingAdmin(uint256 role, address newPendingAdmin) external; - - function settleSlashing() external; - - function slash(address destination, uint256 amount) external returns (uint256); - - function stake(address to, uint256 amount) external; - - function stakeWithPermit( - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function stakerRewardsToClaim(address) external view returns (uint256); - - function stakersCooldowns(address) external view returns (uint40 timestamp, uint216 amount); - - function symbol() external view returns (string memory); - - function totalSupply() external view returns (uint256); - - function transfer(address to, uint256 value) external returns (bool); - - function transferFrom(address from, address to, uint256 value) external returns (bool); -} diff --git a/src/contracts/interfaces/IStakeToken.sol b/src/contracts/interfaces/IStakeToken.sol deleted file mode 100644 index 3be5869..0000000 --- a/src/contracts/interfaces/IStakeToken.sol +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IERC4626} from 'openzeppelin-contracts/contracts/interfaces/IERC4626.sol'; - -interface IStakeToken is IERC4626 { - struct CooldownSnapshot { - /// @notice Represent the time of unlocking funds for redemption - uint40 cooldownEnd; - /// @notice Represents the sice (in seconds) of the withdrawal window when the snapshot was taken - uint40 withdrawalWindowSeconds; - /// @notice Amount of tokens available for redeem - uint104 amount; - } - - struct SmConfig { - /// @notice Seconds available to redeem once the cooldown period is fulfilled - uint40 defaultWithdrawalWindowSeconds; - /// @notice Seconds between starting cooldown and being able to withdraw - uint40 defaultCooldownSeconds; - /// @notice The address of the underlying asset - address stakedToken; - // reserved for future use - } - - /// @notice Thrown when an entity tries to slash that is not listed as admin. - /// @param caller The caller. - error OnlySlashingAdmin(address caller); - /// @notice Thrown when a passed amount is zero. - error ZeroAmount(); - /// @notice Throw when the passed assets amount corresponds to zero shares. - /// @param assets The asset amount. - error ZeroSharesAfterConversion(uint256 assets); - /// @notice Thrown when there are no funds available to slash. - error NoFundsAvailable(); - /// @notice Thrown when trying to redeem before the cooldown is read. - /// @param cooldownEndTimestamp The timestamp at which the shares can be redeemed. - error CooldownNotReady(uint40 cooldownEndTimestamp); - /// @notice Thrown when trying to redeem after the cooldown has been expired. - /// @param expirationTimestamp The timestamp at which the cooldown expired. - error CooldownExpired(uint40 expirationTimestamp); - /// @notice Throw when the cooldown amount is zero. - error ZeroAmountRedeemable(); - - event Cooldown(address indexed user, uint256 amount); - event Slashed(address indexed destination, uint256 amount); - event DefaultCooldownSecondsChanged(uint256 cooldownSeconds); - event DefaultWithdrawalWindowChanged(uint256 withdrawalWindowSeconds); - event ExchangeRateChanged(uint216 exchangeRate); - event FundsReturned(uint256 amount); - event SlashingSettled(); - event SlashingAdminChanged(address newAdmin); - - function MIN_ASSETS_REMAINING() external returns (uint256); - - /** - * @dev Redeems shares, and stop earning rewards - * @param to Address to redeem to - * @param amount Amount of shares to redeem - */ - function redeem(address to, uint256 amount) external; - - /** - * @dev Activates the cooldown period to withdraw - * - It can't be called if the user is not staking - */ - function cooldown() external; - - /** - * @dev Allows staking a certain amount of STAKED_TOKEN with gasless approvals (permit) - * @param amount The amount to be staked - * @param deadline The permit execution deadline - * @param v The v component of the signed message - * @param r The r component of the signed message - * @param s The s component of the signed message - */ - function stakeWithPermit( - uint256 amount, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @dev Returns the current exchange rate - * @return exchangeRate as 18 decimal precision uint216 - */ - function getExchangeRate() external view returns (uint216); - - /** - * @dev Executes a slashing of the underlying of a certain amount, transferring the seized funds - * to destination. Decreasing the amount of underlying will automatically adjust the exchange rate. - * A call to `slash` will start a slashing event which has to be settled via `settleSlashing`. - * As long as the slashing event is ongoing, stake and slash are deactivated. - * - MUST NOT be called when a previous slashing is still ongoing - * @param destination the address where seized funds will be transferred - * @param amount the amount to be slashed - * - if the amount bigger than maximum allowed, the maximum will be slashed instead. - * @return amount the amount slashed - */ - function slash(address destination, uint256 amount) external returns (uint256); - - /** - * @dev Getter of the cooldown seconds - * @return cooldownSeconds the amount of seconds between starting the cooldown and being able to redeem - */ - function getDefaultCooldownSeconds() external view returns (uint256); - - /** - * @dev Setter of cooldown seconds - * Can only be called by the owner - * @param cooldownSeconds the new amount of seconds you have to wait between starting the cooldown and being able to redeem - */ - function setDefaultCooldownSeconds(uint256 cooldownSeconds) external; - - /** - * @dev Activates the cooldown period to withdraw - * - It can't be called if the user is not staking - */ - function cooldownOnBehalfOf(address from) external; - - /** - * @dev Getter for the withdraw window - * @return withdrawWindow in seconds - */ - function getWithdrawalWindow() external returns (uint256); - - /** - * @dev returns the exact amount of assets that would be redeemed for the provided number of shares - * @param shares the number of shares to redeem - * @return uint256 assets the number of assets that would be redeemed - */ - function previewRedeem(uint256 shares) external view returns (uint256); - - /** - * @dev Redeems shares for a user. Only the claim helper contract is allowed to call this function - * @param from Address to redeem from - * @param to Address to redeem to - * @param amount Amount of shares to redeem - */ - function redeemOnBehalf(address from, address to, uint256 amount) external; - - /** - * @dev Getter for the pending cooldown of a user - * @return pending cooldown - */ - function stakersCooldowns(address user) external view returns (CooldownSnapshot memory); - - /** - * @dev Getter of the currently slashable assets - * @return maxSlashableAssets the maximum amount of assets that could be slashed at this moment - * - MUST consider minAssetsRemaining - */ - function getMaxSlashableAssets() external view returns (uint256); - - /** - * @dev Sets the paused state on the token - * - MUST be permissioned - */ - function setPaused(bool paused) external; -} diff --git a/tests/ERC20Std.t.sol b/tests/ERC20Std.t.sol deleted file mode 100644 index 416d33b..0000000 --- a/tests/ERC20Std.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; -import {StakeToken} from '../src/contracts/StakeToken.sol'; -import {ERC20} from 'openzeppelin-contracts/contracts/token/ERC20/ERC20.sol'; -import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol'; -import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'; -import {IERC20Errors} from 'openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol'; -import {StakeTestBase} from './utils/StakeTestBase.sol'; - -// @pavelvm5 in this file shares are messed with assets, so I think we should fix these tests in future -// cause they are not valid + add more complex tests with slashing to find out the order of error -contract ERC20Std is StakeTestBase { - function test_name() external { - assertEq('Stake Test', stakeToken.name()); - } - - function test_symbol() external { - assertEq('stkTest', stakeToken.symbol()); - } - - // mint - function test_stake(uint104 amount) public { - vm.assume(amount > 0); - _stake(amount, user); - assertEq(stakeToken.totalAssets(), amount); - assertEq(stakeToken.totalAssets(), stakeToken.balanceOf(user)); - } - - // burn - function test_redeem(uint104 amountStaked, uint104 amountRedeemed) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - address destination = vm.addr(100); - - _stake(amountStaked, user); - assertEq(stakeToken.balanceOf(user), stakeToken.convertToShares(amountStaked)); - - vm.prank(user); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - _redeem(amountRedeemed, user, destination); - - assertEq(stakeToken.totalAssets(), amountStaked - amountRedeemed); - assertEq(stakeToken.balanceOf(user), stakeToken.convertToAssets(amountStaked - amountRedeemed)); - assertEq(underlying.balanceOf(destination), amountRedeemed); - } - - function test_approve(uint256 amount) public { - assertTrue(stakeToken.approve(user, amount)); - assertEq(stakeToken.allowance(address(this), user), amount); - } - - function test_resetApproval(uint256 amount) public { - test_approve(amount); - assertTrue(stakeToken.approve(user, 0)); - assertEq(stakeToken.allowance(address(this), user), 0); - } - - function test_transfer(uint104 amountStake, uint104 amountTransfer, address otherUser) external { - vm.assume(otherUser != address(proxyAdmin) && otherUser != user && otherUser != address(0)); - vm.assume(amountStake > 1); - vm.assume(amountTransfer <= amountStake); - test_stake(amountStake); - vm.startPrank(user); - stakeToken.transfer(otherUser, amountTransfer); - assertEq(stakeToken.balanceOf(otherUser), amountTransfer); - assertEq(stakeToken.balanceOf(user), amountStake - amountTransfer); - vm.stopPrank(); - } - - function test_transferFrom( - uint104 amountStake, - uint104 amountTransfer, - address otherUser - ) external { - vm.assume(otherUser != address(proxyAdmin) && otherUser != user && otherUser != address(0)); - vm.assume(amountTransfer <= amountStake); - test_stake(amountStake); - vm.prank(user); - stakeToken.approve(address(this), amountStake); - assertTrue(stakeToken.transferFrom(user, otherUser, amountTransfer)); - assertEq(stakeToken.allowance(user, address(this)), amountStake - amountTransfer); - assertEq(stakeToken.balanceOf(user), amountStake - amountTransfer); - assertEq(stakeToken.balanceOf(otherUser), amountTransfer); - } - - function test_stakeToZeroShouldRevert() external { - uint104 amount = 100; - deal(address(underlying), user, amount); - vm.startPrank(user); - underlying.approve(address(stakeToken), amount); - vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidReceiver.selector, address(0))); - stakeToken.deposit(amount, address(0)); - vm.stopPrank(); - } -} diff --git a/tests/ExchangeRate.t.sol b/tests/ExchangeRate.t.sol index d72f79e..ba00391 100644 --- a/tests/ExchangeRate.t.sol +++ b/tests/ExchangeRate.t.sol @@ -82,7 +82,7 @@ contract ExchangeRateTest is StakeTestBase { uint192 assetsToSlash, uint192 assetsToCheck ) public { - vm.assume(1e20 > assets && assets > stakeToken.MIN_ASSETS_REMAINING()); + vm.assume(assets > stakeToken.MIN_ASSETS_REMAINING()); vm.assume(assetsToSlash > 0 && assetsToSlash < assets); vm.assume(assets - stakeToken.MIN_ASSETS_REMAINING() >= assetsToSlash); diff --git a/tests/erc-4626/ERC4626.t.sol b/tests/erc-4626/ERC4626.t.sol deleted file mode 100644 index 9c49763..0000000 --- a/tests/erc-4626/ERC4626.t.sol +++ /dev/null @@ -1,301 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; -import {StakeToken} from '../../src/contracts/StakeToken.sol'; -import {IStakeToken} from '../../src/contracts/interfaces/IStakeToken.sol'; - -import {IERC20Errors} from 'openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol'; -import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {ERC20} from 'openzeppelin-contracts/contracts/token/ERC20/ERC20.sol'; -import {ProxyAdmin} from 'openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol'; -import {TransparentUpgradeableProxy} from 'openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'; -import {StakeTestBase} from '../utils/StakeTestBase.sol'; - -contract Cooldown is StakeTestBase { - function test_maxWithdraw(uint104 amountToStake, address fuzzUser) public { - vm.assume(amountToStake > 0); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - _stake(amountToStake, fuzzUser); - - uint256 zeroAssetsDueToCooldown = stakeToken.maxWithdraw(fuzzUser); - assertEq(zeroAssetsDueToCooldown, 0); - - vm.startPrank(fuzzUser); - stakeToken.cooldown(); - - uint256 allAssets = stakeToken.maxWithdraw(fuzzUser); - assertEq(amountToStake, allAssets); - } - - function test_maxRedeem(uint104 amountToStake, address fuzzUser) public { - vm.assume(amountToStake > 0); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - uint256 sharesToMint = stakeToken.convertToShares(amountToStake); - _stake(amountToStake, fuzzUser); - - uint256 zeroSharesDueToCooldown = stakeToken.maxRedeem(fuzzUser); - assertEq(zeroSharesDueToCooldown, 0); - - vm.startPrank(fuzzUser); - stakeToken.cooldown(); - - uint256 allShares = stakeToken.maxWithdraw(fuzzUser); - assertEq(allShares, sharesToMint); - } - - // Due to default 1e18 exchange rate there's no rounding here at all, so I checked these values striclty - function test_previewFunctions(uint104 assets) public view { - uint256 shares = stakeToken.convertToShares(assets); - - uint256 previewDeposit = stakeToken.previewDeposit(assets); - assertEq(previewDeposit, shares); - - uint256 previewMint = stakeToken.previewMint(shares); - assertEq(previewMint, assets); - - uint256 previewWithdraw = stakeToken.previewWithdraw(assets); - assertEq(previewWithdraw, shares); - - uint256 previewRedeem = stakeToken.previewRedeem(shares); - assertEq(previewRedeem, assets); - } - - function test_deposit(uint104 amount, address fuzzUser) public { - vm.assume(amount > 0); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - deal(address(underlying), fuzzUser, amount); - vm.startPrank(fuzzUser); - underlying.approve(address(stakeToken), amount); - - uint256 numberOfShares = stakeToken.deposit(amount, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amount); - assertEq(stakeToken.totalAssets(), stakeToken.balanceOf(fuzzUser)); - - assertEq(stakeToken.totalSupply(), numberOfShares); - } - - function test_mint(uint104 amount, address fuzzUser) public { - vm.assume(amount > 0); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - uint256 shares = stakeToken.convertToShares(amount); - - deal(address(underlying), fuzzUser, amount); - vm.startPrank(fuzzUser); - underlying.approve(address(stakeToken), amount); - - stakeToken.mint(shares, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amount); - assertEq(stakeToken.totalAssets(), stakeToken.balanceOf(fuzzUser)); - - assertEq(stakeToken.totalSupply(), shares); - } - - function test_redeem(uint104 amountStaked, uint104 amountRedeemed, address fuzzUser) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - uint256 sharesToRedeem = stakeToken.convertToShares(amountRedeemed); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.prank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - - vm.startPrank(fuzzUser); - stakeToken.redeem(sharesToRedeem, destination, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amountStaked - amountRedeemed); - assertEq( - stakeToken.balanceOf(fuzzUser), - stakeToken.convertToShares(amountStaked - amountRedeemed) - ); - - assertEq(stakeToken.totalSupply(), stakeToken.balanceOf(fuzzUser)); - assertEq(underlying.balanceOf(destination), amountRedeemed); - } - - function test_redeemWithApprove( - uint104 amountStaked, - uint104 amountRedeemed, - address fuzzUser - ) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0) && fuzzUser != user); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - uint256 sharesToRedeem = stakeToken.convertToShares(amountRedeemed); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.prank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - - vm.startPrank(fuzzUser); - stakeToken.approve(user, sharesToRedeem); - vm.stopPrank(); - - vm.startPrank(user); - stakeToken.redeem(sharesToRedeem, destination, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amountStaked - amountRedeemed); - assertEq( - stakeToken.balanceOf(fuzzUser), - stakeToken.convertToShares(amountStaked - amountRedeemed) - ); - - assertEq(stakeToken.totalSupply(), stakeToken.balanceOf(fuzzUser)); - assertEq(underlying.balanceOf(destination), amountRedeemed); - } - - function test_redeemWithoutApprove( - uint104 amountStaked, - uint104 amountRedeemed, - address fuzzUser - ) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0) && fuzzUser != user); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - uint256 sharesToRedeem = stakeToken.convertToShares(amountRedeemed); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.prank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - - vm.startPrank(user); - vm.expectRevert( - abi.encodeWithSelector( - IERC20Errors.ERC20InsufficientAllowance.selector, - address(user), - 0, - sharesToRedeem - ) - ); - stakeToken.redeem(sharesToRedeem, destination, fuzzUser); - } - - function test_withdraw(uint104 amountStaked, uint104 amountRedeemed, address fuzzUser) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0)); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.startPrank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - stakeToken.withdraw(amountRedeemed, destination, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amountStaked - amountRedeemed); - assertEq( - stakeToken.balanceOf(fuzzUser), - stakeToken.convertToShares(amountStaked - amountRedeemed) - ); - - assertEq(stakeToken.totalSupply(), stakeToken.balanceOf(fuzzUser)); - assertEq(underlying.balanceOf(destination), amountRedeemed); - } - - function test_withdrawWithApprove( - uint104 amountStaked, - uint104 amountRedeemed, - address fuzzUser - ) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0) && fuzzUser != user); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - uint256 sharesToRedeem = stakeToken.convertToShares(amountRedeemed); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.prank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - - vm.startPrank(fuzzUser); - stakeToken.approve(user, sharesToRedeem); - vm.stopPrank(); - - vm.startPrank(user); - stakeToken.withdraw(amountRedeemed, destination, fuzzUser); - vm.stopPrank(); - - assertEq(stakeToken.totalAssets(), amountStaked - amountRedeemed); - assertEq( - stakeToken.balanceOf(fuzzUser), - stakeToken.convertToShares(amountStaked - amountRedeemed) - ); - - assertEq(stakeToken.totalSupply(), stakeToken.balanceOf(fuzzUser)); - assertEq(underlying.balanceOf(destination), amountRedeemed); - } - - function test_withdrawWithoutApprove( - uint104 amountStaked, - uint104 amountRedeemed, - address fuzzUser - ) public { - vm.assume(amountStaked > 0); - vm.assume(amountRedeemed != 0 && amountRedeemed <= amountStaked); - vm.assume(fuzzUser != address(proxyAdmin) && fuzzUser != address(0) && fuzzUser != user); - - address destination = vm.addr(100); - uint256 shares = stakeToken.convertToShares(amountStaked); - uint256 sharesToRedeem = stakeToken.convertToShares(amountRedeemed); - - _stake(amountStaked, fuzzUser); - assertEq(stakeToken.balanceOf(fuzzUser), shares); - - vm.startPrank(fuzzUser); - stakeToken.cooldown(); - vm.warp(block.timestamp + stakeToken.getDefaultCooldownSeconds()); - vm.stopPrank(); - - vm.startPrank(user); - - vm.expectRevert( - abi.encodeWithSelector( - IERC20Errors.ERC20InsufficientAllowance.selector, - address(user), - 0, - sharesToRedeem - ) - ); - - stakeToken.withdraw(amountRedeemed, destination, fuzzUser); - } -} diff --git a/tests/utils/ActionsLibrary.sol b/tests/utils/ActionsLibrary.sol deleted file mode 100644 index 9071c46..0000000 --- a/tests/utils/ActionsLibrary.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; -import {SafeERC20} from 'openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol'; -import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {OwnableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol'; -import {IStakeToken} from '../../src/contracts/interfaces/IStakeToken.sol'; - -library ActionsLibrary { - using SafeERC20 for IERC20; - - function helper_deposit( - IStakeToken self, - Vm vm, - uint256 amount, - address actor, - address receiver - ) internal returns (uint256) { - vm.startPrank(actor); - IERC20(self.asset()).forceApprove(address(self), amount); - uint256 shares = self.deposit(amount, receiver); - vm.stopPrank(); - return shares; - } - - function helper_cooldown_and_warp(IStakeToken self, Vm vm, address actor) internal { - vm.prank(actor); - self.cooldown(); - IStakeToken.CooldownSnapshot memory snapshot = self.stakersCooldowns(actor); - vm.warp(snapshot.cooldownEnd + 1); - } - - function helper_slash( - IStakeToken self, - Vm vm, - address caller, - address target, - uint256 assets - ) internal returns (uint256) { - vm.prank(caller); - return self.slash(target, assets); - } -} diff --git a/tests/utils/InvariantHandler.sol b/tests/utils/InvariantHandler.sol deleted file mode 100644 index 23c9804..0000000 --- a/tests/utils/InvariantHandler.sol +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import 'forge-std/Test.sol'; -import {SafeERC20} from 'openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol'; -import {OwnableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol'; -import {IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {CommonBase} from 'forge-std/Base.sol'; -import {StdCheats} from 'forge-std/StdCheats.sol'; -import {StdUtils} from 'forge-std/StdUtils.sol'; -import {IStakeToken} from '../../src/contracts/interfaces/IStakeToken.sol'; -import {StataStakeTestBase} from './StataStakeTestBase.sol'; -import {ActionsLibrary} from './ActionsLibrary.sol'; - -contract InvariantHandler is CommonBase, StdCheats, StdUtils { - using SafeERC20 for IERC20; - using ActionsLibrary for IStakeToken; - - IStakeToken private _stakeToken; - address private _slashingAdmin; - address[] public actors; - - // tracking variables - uint256 internal currentActorIndex; - address internal currentActor; - uint256 public ghost_sumOfStakedAssets = 0; - uint256 public ghost_lastExchangeRate; - - modifier useActor(uint256 actorIndexSeed) { - currentActorIndex = bound(actorIndexSeed, 0, actors.length - 1); - currentActor = actors[currentActorIndex]; - _; - } - - constructor(IStakeToken stakeToken, address slashingAdmin) { - _stakeToken = stakeToken; - _slashingAdmin = slashingAdmin; - actors.push(vm.addr(1000)); - actors.push(vm.addr(1001)); - actors.push(vm.addr(1002)); - ghost_lastExchangeRate = stakeToken.getExchangeRate(); - } - - function stake(uint256 assets, uint256 actorIndexSeed) external useActor(actorIndexSeed) { - assets = bound(assets, 1, type(uint64).max); - deal(_stakeToken.asset(), currentActor, assets); - _stakeToken.helper_deposit(vm, assets, currentActor, currentActor); - ghost_sumOfStakedAssets += assets; - } - - function cooldown(uint256 actorIndexSeed) external useActor(actorIndexSeed) { - if (IERC20(address(_stakeToken)).balanceOf(currentActor) == 0) return; - _stakeToken.helper_cooldown_and_warp(vm, currentActor); - } - - function redeem(uint256 assets, uint256 actorIndexSeed) external useActor(actorIndexSeed) { - IStakeToken.CooldownSnapshot memory snapshot = _stakeToken.stakersCooldowns(currentActor); - if (snapshot.amount == 0 || snapshot.cooldownEnd <= block.timestamp) return; - _stakeToken.redeem(currentActor, assets); - ghost_sumOfStakedAssets -= snapshot.amount > assets ? assets : snapshot.amount; - } - - function transfer(uint256 shares, uint256 actorIndexSeed) external useActor(actorIndexSeed) { - shares = bound(shares, 0, IERC20(address(_stakeToken)).balanceOf(currentActor)); - IERC20(address(_stakeToken)).transfer(_getNextActor(), shares); - } - - function slash(uint256 assets) external { - assets = bound(assets, 1, ghost_sumOfStakedAssets); - ghost_lastExchangeRate = _stakeToken.getExchangeRate(); - ghost_sumOfStakedAssets -= _stakeToken.helper_slash(vm, _slashingAdmin, vm.addr(0xB0B), assets); - } - - function _getNextActor() internal view returns (address) { - if (currentActorIndex == actors.length - 1) return actors[0]; - return actors[currentActorIndex + 1]; - } -}