diff --git a/contracts/PToken.sol b/contracts/PToken.sol index 58adba1..ebbeca9 100644 --- a/contracts/PToken.sol +++ b/contracts/PToken.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.24; import {ERC20} from "solmate/tokens/ERC20.sol"; diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index c3497b8..4652556 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.24; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; @@ -41,7 +41,9 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall mapping(address => uint256) public caps; // asset => deposit cap - mapping(address => mapping(address => bool)) public trustedClaimers; // owner => delegate => trustedClaimers + mapping(address => mapping(address => bool)) public trustedReceivers; // owner => delegate => trustedReceivers + + mapping(address => uint256) public totalDeposited; // token => total deposited amount // Fees uint256 public mintFee; @@ -66,7 +68,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall event Deposit(address indexed depositor, address indexed receiver, address indexed token, uint256 amount); event Withdraw(address indexed withdrawer, address indexed receiver, address indexed token, uint256 amount); - event TrustClaimer(address indexed owner, address indexed delegate, bool isTrusted); + event TrustReceiver(address indexed owner, address indexed delegate, bool isTrusted); event RootUpdated(bytes32 prevRoot, bytes32 newRoot); event PTokensClaimed( address indexed account, address indexed receiver, bytes32 indexed pointsId, uint256 amount, uint256 fee @@ -89,13 +91,14 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall error ProofInvalidOrExpired(); error ClaimTooLarge(); - error RewardsNotReleased(); + error RewardsNotLive(); error CantConvertMerkleRedemption(); error PTokenAlreadyDeployed(); error DepositExceedsCap(); error PTokenNotDeployed(); error AmountTooSmall(); - error NotTrustedClaimer(); + error NotTrustedReceiver(); + error ExecutionFailed(address to, bytes data); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -110,25 +113,27 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall _setFeeCollector(_feeCollector); } - // Rebasing and fee-on-transfer tokens must be wrapped before depositing. + // Rebasing and fee-on-transfer tokens must be wrapped before depositing. ie, they are not supported natively. function deposit(ERC20 _token, uint256 _amount, address _receiver) public { uint256 cap = caps[address(_token)]; if (cap != type(uint256).max) { - if (_amount + _token.balanceOf(address(this)) > cap) { + if (totalDeposited[address(_token)] + _amount > cap) { revert DepositExceedsCap(); } } - _token.safeTransferFrom(msg.sender, address(this), _amount); - balances[_receiver][_token] += _amount; + totalDeposited[address(_token)] += _amount; + + _token.safeTransferFrom(msg.sender, address(this), _amount); emit Deposit(msg.sender, _receiver, address(_token), _amount); } function withdraw(ERC20 _token, uint256 _amount, address _receiver) public { balances[msg.sender][_token] -= _amount; + totalDeposited[address(_token)] -= _amount; _token.safeTransfer(_receiver, _amount); @@ -149,8 +154,8 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall revert PTokenNotDeployed(); } - if (_account != _receiver && !trustedClaimers[_account][_receiver]) { - revert NotTrustedClaimer(); + if (_account != _receiver && !trustedReceivers[_account][_receiver]) { + revert NotTrustedReceiver(); } uint256 pTokenFee = FixedPointMathLib.mulWadUp(_claim.amountToClaim, mintFee); @@ -161,9 +166,9 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall emit PTokensClaimed(_account, _receiver, pointsId, _claim.amountToClaim, pTokenFee); } - function trustClaimer(address _account, bool _isTrusted) public { - trustedClaimers[msg.sender][_account] = _isTrusted; - emit TrustClaimer(msg.sender, _account, _isTrusted); + function trustReceiver(address _account, bool _isTrusted) public { + trustedReceivers[msg.sender][_account] = _isTrusted; + emit TrustReceiver(msg.sender, _account, _isTrusted); } /// @notice Redeems point tokens for rewards @@ -177,7 +182,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall (params.rewardToken, params.rewardsPerPToken, params.isMerkleBased); if (address(rewardToken) == address(0)) { - revert RewardsNotReleased(); + revert RewardsNotLive(); } if (isMerkleBased) { @@ -188,7 +193,9 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall _verifyClaimAndUpdateClaimed(_claim, claimHash, msg.sender, claimedRedemptionRights); } - uint256 pTokensToBurn = FixedPointMathLib.divWadUp(amountToClaim, rewardsPerPToken); + uint256 scalingFactor = 10 ** (18 - rewardToken.decimals()); // Only tokens with 18 decimals or fewer are supported. + uint256 pTokensToBurn = FixedPointMathLib.divWadUp(amountToClaim * scalingFactor, rewardsPerPToken); + pTokens[pointsId].burn(msg.sender, pTokensToBurn); uint256 claimed = claimedPTokens[msg.sender][pointsId]; @@ -205,12 +212,17 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall rewardsToTransfer = amountToClaim; feelesslyRedeemedPTokens[msg.sender][pointsId] += pTokensToBurn; } else { - // If some or all of the pTokens need to be charged a fee. - uint256 redeemableWithFee = pTokensToBurn - feelesslyRedeemable; - // fee = amount of pTokens that are not feeless * rewardsPerPToken * redemptionFee - fee = FixedPointMathLib.mulWadUp( - FixedPointMathLib.mulWadUp(redeemableWithFee, rewardsPerPToken), redemptionFee - ); + // Calculate the fee. Scope avoids stack too deep errors. + { + // If some or all of the pTokens need to be charged a fee. + uint256 redeemableWithFee = pTokensToBurn - feelesslyRedeemable; + // fee = amount of pTokens that are not feeless * rewardsPerPToken * redemptionFee + fee = FixedPointMathLib.mulWadUp( + FixedPointMathLib.mulWadUp(redeemableWithFee, rewardsPerPToken), redemptionFee + ); + + fee = fee / scalingFactor; // Downscale to reward token decimals. + } rewardTokenFeeAcc[pointsId] += fee; rewardsToTransfer = amountToClaim - fee; @@ -232,7 +244,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall (params.rewardToken, params.rewardsPerPToken, params.isMerkleBased); if (address(rewardToken) == address(0)) { - revert RewardsNotReleased(); + revert RewardsNotLive(); } if (isMerkleBased) { @@ -241,7 +253,8 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall rewardToken.safeTransferFrom(msg.sender, address(this), _amountToConvert); - uint256 pTokensToMint = FixedPointMathLib.divWadDown(_amountToConvert, rewardsPerPToken); // Round down for mint. + uint256 scalingFactor = 10 ** (18 - rewardToken.decimals()); // Only tokens with 18 decimals or fewer are supported. + uint256 pTokensToMint = FixedPointMathLib.divWadDown(_amountToConvert * scalingFactor, rewardsPerPToken); // Dust guard. if (pTokensToMint == 0) { @@ -344,14 +357,18 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall (uint256 pTokenFee, uint256 rewardTokenFee) = (pTokenFeeAcc[_pointsId], rewardTokenFeeAcc[_pointsId]); if (pTokenFee > 0) { - pTokens[_pointsId].mint(feeCollector, pTokenFee); pTokenFeeAcc[_pointsId] = 0; + pTokens[_pointsId].mint(feeCollector, pTokenFee); } if (rewardTokenFee > 0) { - // There will only be a positive rewardTokenFee if there are reward tokens in this contract available for transfer. - redemptions[_pointsId].rewardToken.safeTransfer(feeCollector, rewardTokenFee); - rewardTokenFeeAcc[_pointsId] = 0; + ERC20 rewardToken = redemptions[_pointsId].rewardToken; + if (address(rewardToken) != address(0)) { + rewardTokenFeeAcc[_pointsId] = 0; + rewardToken.safeTransfer(feeCollector, rewardTokenFee); + } else { + rewardTokenFee = 0; // Do not collect reward token fees if the reward token is unset. + } } emit FeesCollected(_pointsId, feeCollector, pTokenFee, rewardTokenFee); @@ -370,6 +387,10 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall assembly { success := delegatecall(_txGas, _to, add(_data, 0x20), mload(_data), 0, 0) } + + if (!success) { + revert ExecutionFailed(_to, _data); + } } function _authorizeUpgrade(address _newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} diff --git a/contracts/script/PointTokenVault.s.sol b/contracts/script/PointTokenVault.s.sol index d752798..cb4068d 100644 --- a/contracts/script/PointTokenVault.s.sol +++ b/contracts/script/PointTokenVault.s.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.24; import {BatchScript} from "forge-safe/src/BatchScript.sol"; diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index f870d6f..65040a6 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -1,21 +1,17 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity =0.8.24; import {Test, console} from "forge-std/Test.sol"; import {PointTokenVault} from "../PointTokenVault.sol"; import {PToken} from "../PToken.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {ERC1967Utils} from "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {LibString} from "solady/utils/LibString.sol"; -import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; - import {PointTokenVaultScripts} from "../script/PointTokenVault.s.sol"; contract PointTokenVaultTest is Test { @@ -150,6 +146,47 @@ contract PointTokenVaultTest is Test { assertEq(pointTokenVault.balances(vitalik, newMockToken), 2e18); // Total 2 tokens deposited } + function test_DepositCapRewardSameAsDeposit() public { + // Set up an 18 decimal token as both deposit and reward token + MockERC20 token = new MockERC20("Example Token", "EX", 18); + + vm.startPrank(operator); + // Set deposit cap for token to 5000 + pointTokenVault.setCap(address(token), 5000e18); + + // Set token as reward token with 1:1 ratio + pointTokenVault.setRedemption(eigenPointsId, token, 1e18, false); + vm.stopPrank(); + + // Mint tokens to users + token.mint(vitalik, 5000e18); + token.mint(toly, 2000e18); + + // Vitalik deposits 4000 tokens + vm.startPrank(vitalik); + token.approve(address(pointTokenVault), 4000e18); + pointTokenVault.deposit(token, 4000e18, vitalik); + vm.stopPrank(); + + // Toly converts 2000 tokens to pTokens + vm.startPrank(toly); + token.approve(address(pointTokenVault), 2000e18); + pointTokenVault.convertRewardsToPTokens(toly, eigenPointsId, 2000e18); + vm.stopPrank(); + + // Assert current token balance in vault + assertEq(token.balanceOf(address(pointTokenVault)), 6000e18); + + // Try to deposit 1000 tokens, which should succeed + vm.startPrank(vitalik); + token.approve(address(pointTokenVault), 1000e18); + pointTokenVault.deposit(token, 1000e18, vitalik); + vm.stopPrank(); + + // Assert that 5000 tokens have been deposited + assertEq(pointTokenVault.balances(vitalik, token), 5000e18); + } + function test_DeployPToken() public { // Can't deploy the same token twice vm.expectRevert(PointTokenVault.PTokenAlreadyDeployed.selector); @@ -238,6 +275,17 @@ contract PointTokenVaultTest is Test { pointTokenVault.execute( address(callEcho), abi.encodeWithSelector(CallEcho.callEcho.selector, echo, "Hello"), GAS_LIMIT ); + + // Test that failed calls revert with ExecutionFailed error + vm.prank(admin); + vm.expectRevert( + abi.encodeWithSelector( + PointTokenVault.ExecutionFailed.selector, + address(callEcho), + abi.encodeWithSelector(CallEcho.fail.selector) + ) + ); + pointTokenVault.execute(address(callEcho), abi.encodeWithSelector(CallEcho.fail.selector), GAS_LIMIT); } event PTokensClaimed( @@ -409,6 +457,31 @@ contract PointTokenVaultTest is Test { assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18 - 1); } + function test_RedeemRewardsWith6DecimalToken() public { + // Setup a mock 6-decimal token (like USDC) + MockERC20 usdcReward = new MockERC20("USDC Reward", "USDC", 6); + + // Mint 1,000,000 USDC to the vault + usdcReward.mint(address(pointTokenVault), 1_000_000 * 1e6); + + // Set redemption parameters (1 pToken = 1 USDC) + vm.prank(operator); + pointTokenVault.setRedemption(eigenPointsId, usdcReward, 1e18, false); + + // Mint 1 pToken to vitalik + vm.startPrank(address(pointTokenVault)); + pointTokenVault.pTokens(eigenPointsId).mint(vitalik, 1e18); + vm.stopPrank(); + + // Vitalik redeems 1 pToken for 1 USDC + vm.prank(vitalik); + pointTokenVault.redeemRewards(PointTokenVault.Claim(eigenPointsId, 1e6, 1e6, new bytes32[](0)), vitalik); + + // Check balances + assertEq(usdcReward.balanceOf(vitalik), 1e6, "Vitalik should receive 1 USDC"); + assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 0, "Vitalik should have 0 pTokens left"); + } + event RewardsClaimed( address indexed owner, address indexed receiver, bytes32 indexed pointsId, uint256 amount, uint256 tax ); @@ -482,7 +555,7 @@ contract PointTokenVaultTest is Test { assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18); } - function test_TrustedClaimer() public { + function test_TrustedReceiver() public { bytes32 root = 0x4e40a10ce33f33a4786960a8bb843fe0e170b651acd83da27abc97176c4bed3c; bytes32[] memory proof = new bytes32[](1); @@ -493,12 +566,12 @@ contract PointTokenVaultTest is Test { // Toly tries to claim vitalik's pTokens (should fail) vm.prank(toly); - vm.expectRevert(PointTokenVault.NotTrustedClaimer.selector); + vm.expectRevert(PointTokenVault.NotTrustedReceiver.selector); pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.6e18, proof), vitalik, toly); // Vitalik delegates claiming rights to Toly vm.prank(vitalik); - pointTokenVault.trustClaimer(toly, true); + pointTokenVault.trustReceiver(toly, true); // Toly claims the half of Vitalik's pTokens vm.prank(toly); @@ -519,7 +592,7 @@ contract PointTokenVaultTest is Test { event RewardsConverted(address indexed owner, address indexed receiver, bytes32 indexed pointsId, uint256 amount); - function test_MintPTokensForRewards() public { + function test_ConvertRewardsToPTokens() public { bytes32 root = 0x4e40a10ce33f33a4786960a8bb843fe0e170b651acd83da27abc97176c4bed3c; bytes32[] memory proof = new bytes32[](1); @@ -535,9 +608,9 @@ contract PointTokenVaultTest is Test { // Cannot redeem pTokens or convert rewards before redemption data is set bytes32[] memory empty = new bytes32[](0); - vm.expectRevert(PointTokenVault.RewardsNotReleased.selector); + vm.expectRevert(PointTokenVault.RewardsNotLive.selector); pointTokenVault.redeemRewards(PointTokenVault.Claim(eigenPointsId, 2e18, 2e18, empty), vitalik); - vm.expectRevert(PointTokenVault.RewardsNotReleased.selector); + vm.expectRevert(PointTokenVault.RewardsNotLive.selector); pointTokenVault.convertRewardsToPTokens(vitalik, eigenPointsId, 1e18); vm.prank(operator); @@ -568,17 +641,43 @@ contract PointTokenVaultTest is Test { assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 0); } + function test_ConvertRewardsToPTokensWith6DecimalToken() public { + // Setup a mock 6-decimal token (like USDC) + MockERC20 usdcReward = new MockERC20("USDC Reward", "USDC", 6); + + // Mint 1,000,000 USDC to vitalik + usdcReward.mint(vitalik, 1_000_000 * 1e6); + + // Set redemption parameters (1 pToken = 1 USDC) + vm.prank(operator); + pointTokenVault.setRedemption(eigenPointsId, usdcReward, 1e18, false); + + // Approve USDC spend + vm.prank(vitalik); + usdcReward.approve(address(pointTokenVault), type(uint256).max); + + // Vitalik converts 1 USDC to 1 pToken + vm.prank(vitalik); + pointTokenVault.convertRewardsToPTokens(vitalik, eigenPointsId, 1e6); + + // Check balances + assertEq(usdcReward.balanceOf(vitalik), 999_999 * 1e6, "Vitalik should have 999,999 USDC left"); + assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18, "Vitalik should receive 1 pToken"); + } + event FeeCollectorSet(address feeCollector); - + function test_setFeeCollector() public { vm.prank(admin); - vm.expectEmit(true,true,true,true); + vm.expectEmit(true, true, true, true); emit FeeCollectorSet(toly); pointTokenVault.setFeeCollector(toly); vm.expectRevert( abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, address(vitalik), pointTokenVault.DEFAULT_ADMIN_ROLE() + IAccessControl.AccessControlUnauthorizedAccount.selector, + address(vitalik), + pointTokenVault.DEFAULT_ADMIN_ROLE() ) ); vm.prank(vitalik); @@ -675,9 +774,23 @@ contract PointTokenVaultTest is Test { vm.prank(toly); pointTokenVault.redeemRewards(PointTokenVault.Claim(eigenPointsId, 1.8e18, 1.8e18, empty), toly); + // Unset redemption + vm.prank(operator); + pointTokenVault.setRedemption(eigenPointsId, ERC20(address(0)), 0, false); + + // No reward token fees are collected. + vm.expectEmit(true, true, true, true); + emit FeesCollected(eigenPointsId, pointTokenVault.feeCollector(), 0.1e18, 0); + pointTokenVault.collectFees(eigenPointsId); + assertEq(rewardToken.balanceOf(pointTokenVault.feeCollector()), 0); + + // Set redemption again + vm.prank(operator); + pointTokenVault.setRedemption(eigenPointsId, rewardToken, 2e18, false); + // Collect fees vm.expectEmit(true, true, true, true); - emit FeesCollected(eigenPointsId, pointTokenVault.feeCollector(), 0.1e18, 0.09e18); + emit FeesCollected(eigenPointsId, pointTokenVault.feeCollector(), 0, 0.09e18); pointTokenVault.collectFees(eigenPointsId); // Check balances after fee collection @@ -889,4 +1002,8 @@ contract CallEcho { function callEcho(Echo echo, string calldata message) public { echo.echo(message); } + + function fail() public pure { + revert("Failed"); + } } diff --git a/contracts/test/invariant/PointTokenVaultInvariants.t.sol b/contracts/test/invariant/PointTokenVaultInvariants.t.sol index 5118a6b..703e689 100644 --- a/contracts/test/invariant/PointTokenVaultInvariants.t.sol +++ b/contracts/test/invariant/PointTokenVaultInvariants.t.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {Test, console, console2} from "forge-std/Test.sol"; @@ -11,7 +11,7 @@ import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; contract PointTokenVaultInvariants is Test { PointTokenVaultHandler handler; - + MockPointTokenVault pointTokenVault; function setUp() public { @@ -21,21 +21,14 @@ contract PointTokenVaultInvariants is Test { // Deploy the PointTokenVault pointTokenVault = scripts.run("0.0.1"); - address[3] memory admins = [ - makeAddr("admin"), - makeAddr("operator"), - makeAddr("merkleUpdater") - ]; + address[3] memory admins = [makeAddr("admin"), makeAddr("operator"), makeAddr("merkleUpdater")]; pointTokenVault.grantRole(pointTokenVault.DEFAULT_ADMIN_ROLE(), admins[0]); pointTokenVault.grantRole(pointTokenVault.MERKLE_UPDATER_ROLE(), admins[2]); pointTokenVault.grantRole(pointTokenVault.OPERATOR_ROLE(), admins[1]); pointTokenVault.revokeRole(pointTokenVault.DEFAULT_ADMIN_ROLE(), address(this)); - handler = new PointTokenVaultHandler( - pointTokenVault, - admins - ); + handler = new PointTokenVaultHandler(pointTokenVault, admins); bytes4[] memory selectors = new bytes4[](5); selectors[0] = handler.deposit.selector; @@ -44,25 +37,26 @@ contract PointTokenVaultInvariants is Test { selectors[3] = handler.redeem.selector; selectors[4] = handler.convertRewardsToPTokens.selector; - targetSelector( - FuzzSelector({ - addr: address(handler), - selectors: selectors - }) - ); + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); targetContract(address(handler)); } function invariant_point_earning_token_balances_remain_accurate_over_time() public view { - require(handler.checkPointEarningTokenGhosts(), "local pointsEarningTokens balances do not match balances stored in contract"); + require( + handler.checkPointEarningTokenGhosts(), + "local pointsEarningTokens balances do not match balances stored in contract" + ); } function invariant_claimed_ptoken_balances_remain_accurate_over_time() public view { - require(handler.checkClaimedPTokensGhosts(), "local claimed pTokens balances do not match balances stored in contract"); + require( + handler.checkClaimedPTokensGhosts(), + "local claimed pTokens balances do not match balances stored in contract" + ); } function invariant_ptoken_total_supplies_equal_sum_of_balances() public view { require(handler.checkSumOfPTokenBalances(), "sum of a pToken's balances does not equal its total supply"); } -} \ No newline at end of file +} diff --git a/contracts/test/invariant/handlers/PointTokenVaultHandler.sol b/contracts/test/invariant/handlers/PointTokenVaultHandler.sol index 81c3f3c..befe81d 100644 --- a/contracts/test/invariant/handlers/PointTokenVaultHandler.sol +++ b/contracts/test/invariant/handlers/PointTokenVaultHandler.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {Test, console, console2} from "forge-std/Test.sol"; @@ -83,10 +83,7 @@ contract PointTokenVaultHandler is Test { delete expectedErrors; } - constructor( - MockPointTokenVault pointTokenVault_, - address[3] memory admins - ) { + constructor(MockPointTokenVault pointTokenVault_, address[3] memory admins) { pointTokenVault = pointTokenVault_; vm.prank(admins[1]); @@ -117,12 +114,10 @@ contract PointTokenVaultHandler is Test { } } - function deposit( - uint256 actorIndex, - uint256 dstIndex, - uint256 amount, - uint256 tokenIndex - ) public useRandomActor(actorIndex) { + function deposit(uint256 actorIndex, uint256 dstIndex, uint256 amount, uint256 tokenIndex) + public + useRandomActor(actorIndex) + { actorIndex = bound(actorIndex, 0, 12); dstIndex = bound(dstIndex, 0, 12); amount = bound(amount, 0, 1000000000000 * 1e18); @@ -150,12 +145,10 @@ contract PointTokenVaultHandler is Test { } } - function withdraw( - uint256 actorIndex, - uint256 dstIndex, - uint256 amount, - uint256 tokenIndex - ) public useRandomActor(actorIndex) { + function withdraw(uint256 actorIndex, uint256 dstIndex, uint256 amount, uint256 tokenIndex) + public + useRandomActor(actorIndex) + { actorIndex = bound(actorIndex, 0, 12); dstIndex = bound(dstIndex, 0, 12); amount = bound(amount, 0, 1000000000000 * 1e18); @@ -200,19 +193,17 @@ contract PointTokenVaultHandler is Test { bytes32[] memory emptyProof = new bytes32[](0); - MockPointTokenVault.Claim memory claim = MockPointTokenVault.Claim( - pointsIds[pointsIdIndex], - totalClaimable, - amount, - emptyProof - ); + MockPointTokenVault.Claim memory claim = + MockPointTokenVault.Claim(pointsIds[pointsIdIndex], totalClaimable, amount, emptyProof); uint256 pTokenBalanceBefore = pointTokenVault.pTokens(pointsIds[pointsIdIndex]).balanceOf(actors[dstIndex].addr); uint256 claimedBalanceBefore = pointTokenVault.claimedPTokens(actors[dstIndex].addr, pointsIds[pointsIdIndex]); try pointTokenVault.claimPTokens(claim, actors[dstIndex].addr) { - uint256 pTokenBalanceAfter = pointTokenVault.pTokens(pointsIds[pointsIdIndex]).balanceOf(actors[dstIndex].addr); - uint256 claimedBalanceAfter = pointTokenVault.claimedPTokens(actors[dstIndex].addr, pointsIds[pointsIdIndex]); + uint256 pTokenBalanceAfter = + pointTokenVault.pTokens(pointsIds[pointsIdIndex]).balanceOf(actors[dstIndex].addr); + uint256 claimedBalanceAfter = + pointTokenVault.claimedPTokens(actors[dstIndex].addr, pointsIds[pointsIdIndex]); claimedPTokensGhosts[actors[dstIndex].addr][pointsIds[pointsIdIndex]] += amount; pTokenBalanceGhosts[actors[dstIndex].addr][pointsIds[pointsIdIndex]] += amount; @@ -254,17 +245,14 @@ contract PointTokenVaultHandler is Test { pointTokenVault.setRedemption(pointsIds[pointsIdIndex], rewardToken, rewardPerPToken, false); vm.startPrank(currentActor); - MockPointTokenVault.Claim memory redemptionClaim = MockPointTokenVault.Claim( - pointsIds[pointsIdIndex], - rewardTokenAmount, - rewardTokenAmount, - new bytes32[](0) - ); + MockPointTokenVault.Claim memory redemptionClaim = + MockPointTokenVault.Claim(pointsIds[pointsIdIndex], rewardTokenAmount, rewardTokenAmount, new bytes32[](0)); uint256 rewardBalanceBefore = rewardToken.balanceOf(currentActor); try pointTokenVault.redeemRewards(redemptionClaim, currentActor) { - pTokenBalanceGhosts[currentActor][pointsIds[pointsIdIndex]] -= FixedPointMathLib.divWadUp(rewardTokenAmount, rewardPerPToken); + pTokenBalanceGhosts[currentActor][pointsIds[pointsIdIndex]] -= + FixedPointMathLib.divWadUp(rewardTokenAmount, rewardPerPToken); assertEq(rewardToken.balanceOf(currentActor) - rewardBalanceBefore, rewardTokenAmount); } catch (bytes memory reason) { console.log("Unexpected revert: redeem failed!"); @@ -301,7 +289,7 @@ contract PointTokenVaultHandler is Test { try pointTokenVault.convertRewardsToPTokens(actors[dstIndex].addr, pointsIds[pointsIdIndex], amount) { uint256 pTokenAmount = FixedPointMathLib.divWadDown(amount, rewardPerPToken); - + pTokenBalanceGhosts[actors[dstIndex].addr][pointsIds[pointsIdIndex]] += pTokenAmount; assertEq(senderRewardTokenBalanceBefore - rewardToken.balanceOf(currentActor), amount); @@ -310,7 +298,7 @@ contract PointTokenVaultHandler is Test { // This can be re-added when issue #13 is resolved. // Tests that pTokenAmount is never 0 if rewardAmount > 0 // if (amount > 0) { - // assertGt(pToken.balanceOf(actors[dstIndex].addr) - receiverPTokenBalanceBefore, 0); + // assertGt(pToken.balanceOf(actors[dstIndex].addr) - receiverPTokenBalanceBefore, 0); // } } catch (bytes memory reason) { console.log("Unexpected error: conversion failed!"); @@ -320,19 +308,11 @@ contract PointTokenVaultHandler is Test { // Helper functions --- - function _simpleClaim( - uint256 pointsIdIndex, - uint256 pTokenAmount, - uint256 actorIndex - ) internal { + function _simpleClaim(uint256 pointsIdIndex, uint256 pTokenAmount, uint256 actorIndex) internal { bytes32[] memory emptyProof = new bytes32[](0); - MockPointTokenVault.Claim memory pTokenClaim = MockPointTokenVault.Claim( - pointsIds[pointsIdIndex], - type(uint256).max, - pTokenAmount, - emptyProof - ); + MockPointTokenVault.Claim memory pTokenClaim = + MockPointTokenVault.Claim(pointsIds[pointsIdIndex], type(uint256).max, pTokenAmount, emptyProof); pointTokenVault.claimPTokens(pTokenClaim, actors[actorIndex].addr); claimedPTokensGhosts[actors[actorIndex].addr][pointsIds[pointsIdIndex]] += pTokenAmount; @@ -346,8 +326,13 @@ contract PointTokenVaultHandler is Test { pointEarningTokenGhosts[actors[i].addr][address(pointEarningTokens[j])] != pointTokenVault.balances(actors[i].addr, pointEarningTokens[j]) ) { - console.log("Ghost balance:", pointEarningTokenGhosts[actors[i].addr][address(pointEarningTokens[j])]); - console.log("Balance according to contract:", pointTokenVault.balances(actors[i].addr, pointEarningTokens[j])); + console.log( + "Ghost balance:", pointEarningTokenGhosts[actors[i].addr][address(pointEarningTokens[j])] + ); + console.log( + "Balance according to contract:", + pointTokenVault.balances(actors[i].addr, pointEarningTokens[j]) + ); return false; } @@ -358,14 +343,16 @@ contract PointTokenVaultHandler is Test { } function checkClaimedPTokensGhosts() public view returns (bool) { - for (uint i; i < actors.length; i++) { + for (uint256 i; i < actors.length; i++) { for (uint256 j; j < pointsIds.length; j++) { if ( claimedPTokensGhosts[actors[i].addr][pointsIds[j]] != pointTokenVault.claimedPTokens(actors[i].addr, pointsIds[j]) ) { console.log("Ghost balance:", claimedPTokensGhosts[actors[i].addr][pointsIds[j]]); - console.log("Balance according to contract:", pointTokenVault.claimedPTokens(actors[i].addr, pointsIds[j])); + console.log( + "Balance according to contract:", pointTokenVault.claimedPTokens(actors[i].addr, pointsIds[j]) + ); return false; } @@ -376,7 +363,7 @@ contract PointTokenVaultHandler is Test { } function checkSumOfPTokenBalances() public view returns (bool) { - uint256 sumOfBalances; + uint256 sumOfBalances; for (uint256 i; i < pointsIds.length; i++) { sumOfBalances = 0; for (uint256 j; j < actors.length; j++) { @@ -399,4 +386,4 @@ contract PointTokenVaultHandler is Test { currentActor = actors[index].addr; actor_ = actors[index]; } -} \ No newline at end of file +} diff --git a/contracts/test/mock/MockPointTokenVault.sol b/contracts/test/mock/MockPointTokenVault.sol index 44a6806..03ff6aa 100644 --- a/contracts/test/mock/MockPointTokenVault.sol +++ b/contracts/test/mock/MockPointTokenVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {console} from "forge-std/Test.sol"; diff --git a/contracts/test/mock/script/MockPointTokenVault.s.sol b/contracts/test/mock/script/MockPointTokenVault.s.sol index ad6e344..13a1ed2 100644 --- a/contracts/test/mock/script/MockPointTokenVault.s.sol +++ b/contracts/test/mock/script/MockPointTokenVault.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {BatchScript} from "forge-safe/src/BatchScript.sol"; @@ -55,15 +55,18 @@ contract MockPointTokenVaultScripts is BatchScript { } function run(string memory version) public returns (MockPointTokenVault) { - MockPointTokenVault pointTokenVaultImplementation = new MockPointTokenVault{salt: keccak256(abi.encode(version))}(); - - MockPointTokenVault pointTokenVault = MockPointTokenVault(payable( - address( - new ERC1967Proxy{salt: keccak256(abi.encode(version))}( - address(pointTokenVaultImplementation), - abi.encodeCall(MockPointTokenVault.initialize, (msg.sender)) // msg.sender is admin + MockPointTokenVault pointTokenVaultImplementation = + new MockPointTokenVault{salt: keccak256(abi.encode(version))}(); + + MockPointTokenVault pointTokenVault = MockPointTokenVault( + payable( + address( + new ERC1967Proxy{salt: keccak256(abi.encode(version))}( + address(pointTokenVaultImplementation), + abi.encodeCall(MockPointTokenVault.initialize, (msg.sender)) // msg.sender is admin + ) ) - )) + ) ); return pointTokenVault; @@ -87,7 +90,8 @@ contract MockPointTokenVaultScripts is BatchScript { function upgrade() public { vm.startBroadcast(); - MockPointTokenVault currentPointTokenVault = MockPointTokenVault(payable(0xbff7Fb79efC49504afc97e74F83EE618768e63E9)); + MockPointTokenVault currentPointTokenVault = + MockPointTokenVault(payable(0xbff7Fb79efC49504afc97e74F83EE618768e63E9)); MockPointTokenVault PointTokenVaultImplementation = new MockPointTokenVault(); @@ -124,8 +128,9 @@ contract MockPointTokenVaultScripts is BatchScript { address pointTokenVault = 0xbff7Fb79efC49504afc97e74F83EE618768e63E9; - bytes memory txn = - abi.encodeWithSelector(MockPointTokenVault.setCap.selector, 0x791a051631c9c4cDf4E03Fb7Aec3163AE164A34B, 10e18); + bytes memory txn = abi.encodeWithSelector( + MockPointTokenVault.setCap.selector, 0x791a051631c9c4cDf4E03Fb7Aec3163AE164A34B, 10e18 + ); addToBatch(pointTokenVault, 0, txn); executeBatch(SEOPLIA_ADMIN_SAFE, true);