From bd0a06fb6e4bee65367b8faa4c214e3e93730223 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:54:26 -0700 Subject: [PATCH 1/9] fix: correct handling of non-18 decimal reward tokens (#35) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * fix: ensure reward fees are only transferred if the reward token is set, sherlock 185 * fix: correct handling of non-18 decimal reward tokens * fix: revert rewards collection change from different branch * fix: revert rewards collection test change from different branch * fix: correct reversion commit --------- Co-authored-by: Steven Valeri --- contracts/PointTokenVault.sol | 28 +++++++----- contracts/test/PointTokenVault.t.sol | 65 ++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index 991b462..da162e9 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -32,8 +32,8 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall // Merkle root distribution. bytes32 public currRoot; bytes32 public prevRoot; - mapping(address => mapping(bytes32 => uint256)) public claimedPTokens; // user => pointsId => claimed - mapping(address => mapping(bytes32 => uint256)) public claimedRedemptionRights; // user => pointsId => claimed + mapping(address => mapping(bytes32 => uint256)) public claimedPTokens; // user => pointsId => PTokens claimed + mapping(address => mapping(bytes32 => uint256)) public claimedRedemptionRights; // user => pointsId => Rewards redeemed mapping(bytes32 => PToken) public pTokens; // pointsId => pTokens @@ -188,7 +188,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()); + uint256 pTokensToBurn = FixedPointMathLib.divWadUp(amountToClaim * scalingFactor, rewardsPerPToken); + pTokens[pointsId].burn(msg.sender, pTokensToBurn); uint256 claimed = claimedPTokens[msg.sender][pointsId]; @@ -205,12 +207,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; @@ -241,7 +248,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()); + uint256 pTokensToMint = FixedPointMathLib.divWadDown(_amountToConvert * scalingFactor, rewardsPerPToken); // Dust guard. if (pTokensToMint == 0) { diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index f870d6f..f8dfba6 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -5,17 +5,13 @@ 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 { @@ -409,6 +405,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 ); @@ -519,7 +540,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); @@ -568,17 +589,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); From 64158736c8f0ac2b47489949b2bd2badb733066d Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:54:40 -0700 Subject: [PATCH 2/9] fix: ensure reward fees are only transferred if the reward token is set (#36) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * fix: ensure reward fees are only transferred if the reward token is set * refactor: don't revert on collect fees if the reward token is unset, just skip the transfer --------- Co-authored-by: Steven Valeri --- contracts/PointTokenVault.sol | 16 ++++++++++------ contracts/test/PointTokenVault.t.sol | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index da162e9..bc37858 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -89,7 +89,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall error ProofInvalidOrExpired(); error ClaimTooLarge(); - error RewardsNotReleased(); + error RewardsNotLive(); error CantConvertMerkleRedemption(); error PTokenAlreadyDeployed(); error DepositExceedsCap(); @@ -177,7 +177,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall (params.rewardToken, params.rewardsPerPToken, params.isMerkleBased); if (address(rewardToken) == address(0)) { - revert RewardsNotReleased(); + revert RewardsNotLive(); } if (isMerkleBased) { @@ -239,7 +239,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall (params.rewardToken, params.rewardsPerPToken, params.isMerkleBased); if (address(rewardToken) == address(0)) { - revert RewardsNotReleased(); + revert RewardsNotLive(); } if (isMerkleBased) { @@ -357,9 +357,13 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall } 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)) { + rewardToken.safeTransfer(feeCollector, rewardTokenFee); + rewardTokenFeeAcc[_pointsId] = 0; + } else { + rewardTokenFee = 0; // Do not collect reward token fees if the reward token is unset. + } } emit FeesCollected(_pointsId, feeCollector, pTokenFee, rewardTokenFee); diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index f8dfba6..0e4f58d 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -556,9 +556,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); @@ -722,9 +722,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 From e2852448a423f2e35c288d28410bcc107bb3b972 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:54:57 -0700 Subject: [PATCH 3/9] chore: trusted claimer -> trusted receiver (#37) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * chore: trusted claimer -> trusted receiver --------- Co-authored-by: Steven Valeri --- contracts/PointTokenVault.sol | 16 ++++++++-------- contracts/test/PointTokenVault.t.sol | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index bc37858..6f65b9a 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -41,7 +41,7 @@ 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 // Fees uint256 public mintFee; @@ -66,7 +66,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 @@ -95,7 +95,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall error DepositExceedsCap(); error PTokenNotDeployed(); error AmountTooSmall(); - error NotTrustedClaimer(); + error NotTrustedReceiver(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -149,8 +149,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 +161,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 diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index 0e4f58d..51191dd 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -503,7 +503,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); @@ -514,12 +514,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); From 31f96c57deb2265cfc8a2beab609039af55b9ab6 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:55:12 -0700 Subject: [PATCH 4/9] fix: separate deposit cap tracking from contract balance changes (#38) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * fix: separate deposit cap tracking from contract balance changes * chore: move interaction below effects for deposit --------- Co-authored-by: Steven Valeri --- contracts/PointTokenVault.sol | 12 +++++--- contracts/test/PointTokenVault.t.sol | 41 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index 6f65b9a..ffa948e 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -43,6 +43,8 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall mapping(address => mapping(address => bool)) public trustedReceivers; // owner => delegate => trustedReceivers + mapping(address => uint256) public totalDeposited; // token => total deposited amount + // Fees uint256 public mintFee; uint256 public redemptionFee; @@ -110,25 +112,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); diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index 51191dd..f5c74cd 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -146,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); From 026db9843ca9a37722f3a8f34830ef8bdb4b26c5 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:56:49 -0700 Subject: [PATCH 5/9] Chore/agpl (#40) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * chore: AGPL --------- Co-authored-by: Steven Valeri --- contracts/PToken.sol | 2 +- contracts/PointTokenVault.sol | 2 +- contracts/script/PointTokenVault.s.sol | 2 +- contracts/test/PointTokenVault.t.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/PToken.sol b/contracts/PToken.sol index 58adba1..7d7c3f0 100644 --- a/contracts/PToken.sol +++ b/contracts/PToken.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.13; import {ERC20} from "solmate/tokens/ERC20.sol"; diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index ffa948e..aa22263 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.13; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; diff --git a/contracts/script/PointTokenVault.s.sol b/contracts/script/PointTokenVault.s.sol index d752798..bb23945 100644 --- a/contracts/script/PointTokenVault.s.sol +++ b/contracts/script/PointTokenVault.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.13; import {BatchScript} from "forge-safe/src/BatchScript.sol"; diff --git a/contracts/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index f5c74cd..d6c17b1 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; From 67253d75452a2f61a603a2b9297d9003f576e5d0 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:58:55 -0700 Subject: [PATCH 6/9] Chore/collect fees after effects (#39) * Feat/mapping comment update (#18) * feat: update mapping comments * feat: clean comment * Update contracts/PointTokenVault.sol Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> --------- Co-authored-by: Josh Levine <24902242+jparklev@users.noreply.github.com> * chore: collect fees after effects --------- Co-authored-by: Steven Valeri --- contracts/PointTokenVault.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index aa22263..27c41d2 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -356,15 +356,15 @@ 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) { ERC20 rewardToken = redemptions[_pointsId].rewardToken; if (address(rewardToken) != address(0)) { - rewardToken.safeTransfer(feeCollector, rewardTokenFee); rewardTokenFeeAcc[_pointsId] = 0; + rewardToken.safeTransfer(feeCollector, rewardTokenFee); } else { rewardTokenFee = 0; // Do not collect reward token fees if the reward token is unset. } From b4086ec9f7b30de6e37fb4bca62465dc4700a47c Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:02:40 -0700 Subject: [PATCH 7/9] feat: add ExecutionFailed error and improve error handling in execute function (#42) --- contracts/PointTokenVault.sol | 5 +++++ contracts/test/PointTokenVault.t.sol | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index 27c41d2..68c98ce 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -98,6 +98,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall error PTokenNotDeployed(); error AmountTooSmall(); error NotTrustedReceiver(); + error ExecutionFailed(address to, bytes data); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -386,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/test/PointTokenVault.t.sol b/contracts/test/PointTokenVault.t.sol index d6c17b1..c2aff10 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -275,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( @@ -991,4 +1002,8 @@ contract CallEcho { function callEcho(Echo echo, string calldata message) public { echo.echo(message); } + + function fail() public pure { + revert("Failed"); + } } From fcaa1f78c178247b195e8e462c3a1482843933c3 Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:02:50 -0700 Subject: [PATCH 8/9] chore: lock sol version to 0.8.24 (#41) --- contracts/PToken.sol | 2 +- contracts/PointTokenVault.sol | 2 +- contracts/script/PointTokenVault.s.sol | 2 +- contracts/test/PointTokenVault.t.sol | 2 +- .../invariant/PointTokenVaultInvariants.t.sol | 34 +++---- .../handlers/PointTokenVaultHandler.sol | 89 ++++++++----------- contracts/test/mock/MockPointTokenVault.sol | 2 +- .../mock/script/MockPointTokenVault.s.sol | 29 +++--- 8 files changed, 74 insertions(+), 88 deletions(-) diff --git a/contracts/PToken.sol b/contracts/PToken.sol index 7d7c3f0..ebbeca9 100644 --- a/contracts/PToken.sol +++ b/contracts/PToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {ERC20} from "solmate/tokens/ERC20.sol"; diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index 68c98ce..554b040 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; diff --git a/contracts/script/PointTokenVault.s.sol b/contracts/script/PointTokenVault.s.sol index bb23945..cb4068d 100644 --- a/contracts/script/PointTokenVault.s.sol +++ b/contracts/script/PointTokenVault.s.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.13; +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 c2aff10..65040a6 100644 --- a/contracts/test/PointTokenVault.t.sol +++ b/contracts/test/PointTokenVault.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.13; +pragma solidity =0.8.24; import {Test, console} from "forge-std/Test.sol"; import {PointTokenVault} from "../PointTokenVault.sol"; 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); From 5f59e28ff8fe899c9177d9a5a1dfe90903deed8e Mon Sep 17 00:00:00 2001 From: Josh Levine <24902242+jparklev@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:44:48 -0700 Subject: [PATCH 9/9] chore: add comment about not supporting > 18 decimals (#43) --- contracts/PointTokenVault.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/PointTokenVault.sol b/contracts/PointTokenVault.sol index 554b040..4652556 100644 --- a/contracts/PointTokenVault.sol +++ b/contracts/PointTokenVault.sol @@ -193,7 +193,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall _verifyClaimAndUpdateClaimed(_claim, claimHash, msg.sender, claimedRedemptionRights); } - uint256 scalingFactor = 10 ** (18 - rewardToken.decimals()); + 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); @@ -253,7 +253,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall rewardToken.safeTransferFrom(msg.sender, address(this), _amountToConvert); - uint256 scalingFactor = 10 ** (18 - rewardToken.decimals()); + uint256 scalingFactor = 10 ** (18 - rewardToken.decimals()); // Only tokens with 18 decimals or fewer are supported. uint256 pTokensToMint = FixedPointMathLib.divWadDown(_amountToConvert * scalingFactor, rewardsPerPToken); // Dust guard.