Skip to content

Commit

Permalink
feat: add claim trust/delegation function
Browse files Browse the repository at this point in the history
  • Loading branch information
jparklev committed Jul 15, 2024
1 parent f29027c commit fe490b7
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 25 deletions.
15 changes: 13 additions & 2 deletions contracts/PointTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall

mapping(address => uint256) public caps; // asset => deposit cap

mapping(address => mapping(address => bool)) public trustedClaimers; // owner => delegate => trustedClaimers

struct Claim {
bytes32 pointsId;
uint256 totalClaimable;
Expand Down Expand Up @@ -74,6 +76,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall
error DepositExceedsCap();
error PTokenNotDeployed();
error AmountTooSmall();
error NotTrustedClaimer();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand Down Expand Up @@ -116,7 +119,7 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall
/// @param _claim The claim details including the merkle proof
/// @param _account The account to claim for
// Adapted from Morpho's RewardsDistributor.sol (https://github.com/morpho-org/morpho-optimizers/blob/ffd702f045d24b911d6c8c6c2194dd15cf9387ff/src/common/rewards-distribution/RewardsDistributor.sol)
function claimPTokens(Claim calldata _claim, address _account) public {
function claimPTokens(Claim calldata _claim, address _account, address _receiver) public {
bytes32 pointsId = _claim.pointsId;

bytes32 claimHash = keccak256(abi.encodePacked(_account, pointsId, _claim.totalClaimable));
Expand All @@ -126,11 +129,19 @@ contract PointTokenVault is UUPSUpgradeable, AccessControlUpgradeable, Multicall
revert PTokenNotDeployed();
}

pTokens[pointsId].mint(_account, _claim.amountToClaim);
if (_account != _receiver && !trustedClaimers[_account][_receiver]) {
revert NotTrustedClaimer();
}

pTokens[pointsId].mint(_receiver, _claim.amountToClaim);

emit PTokensClaimed(_account, pointsId, _claim.amountToClaim);
}

function trustClaimer(address _account, bool _isTrusted) public {
trustedClaimers[msg.sender][_account] = _isTrusted;
}

/// @notice Redeems rewards for point tokens
/// @param _claim Details of the claim including the amount and merkle proof
/// @param _receiver The account that will receive the msg.sender redeemed rewards
Expand Down
84 changes: 61 additions & 23 deletions contracts/test/PointTokenVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -255,27 +255,27 @@ contract PointTokenVaultTest is Test {
// Can't claim with the wrong proof
vm.prank(vitalik);
vm.expectRevert(PointTokenVault.ProofInvalidOrExpired.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, badProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, badProof), vitalik, vitalik);

// Can't claim with the wrong claimable amount
vm.prank(vitalik);
vm.expectRevert(PointTokenVault.ProofInvalidOrExpired.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.9e18, 0.9e18, goodProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.9e18, 0.9e18, goodProof), vitalik, vitalik);

// Can't claim with the wrong pointsId
vm.prank(vitalik);
vm.expectRevert(PointTokenVault.ProofInvalidOrExpired.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(bytes32("123"), 1e18, 1e18, goodProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(bytes32("123"), 1e18, 1e18, goodProof), vitalik, vitalik);

// Can claim with the right proof
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, goodProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, goodProof), vitalik, vitalik);

assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18);

// Can't use the same proof twice
vm.expectRevert(PointTokenVault.ClaimTooLarge.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, goodProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, goodProof), vitalik, vitalik);
}

function test_DistributionTwoRecipients() public {
Expand All @@ -290,7 +290,7 @@ contract PointTokenVaultTest is Test {

// Vitalik can claim
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, vitalikProof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, vitalikProof), vitalik, vitalik);

assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18);

Expand All @@ -300,9 +300,9 @@ contract PointTokenVaultTest is Test {
// Illia can execute toly's claim, but can only send the tokens to toly
vm.prank(illia);
vm.expectRevert(PointTokenVault.ProofInvalidOrExpired.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), illia);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), illia, illia);

pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), toly);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), toly, toly);

assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(toly), 0.5e18);
}
Expand All @@ -322,10 +322,11 @@ contract PointTokenVaultTest is Test {

bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeCall(
pointTokenVault.claimPTokens, (PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, vitalikProof), vitalik)
pointTokenVault.claimPTokens,
(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, vitalikProof), vitalik, vitalik)
);
calls[1] = abi.encodeCall(
pointTokenVault.claimPTokens, (PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), toly)
pointTokenVault.claimPTokens, (PointTokenVault.Claim(eigenPointsId, 0.5e18, 0.5e18, tolyProof), toly, toly)
);

pointTokenVault.multicall(calls);
Expand Down Expand Up @@ -362,7 +363,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

rewardToken.mint(address(pointTokenVault), 3e18);

Expand All @@ -386,7 +387,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

rewardToken.mint(address(pointTokenVault), 3e18);

Expand Down Expand Up @@ -423,7 +424,9 @@ contract PointTokenVaultTest is Test {

// Vitalik redeems with a valid proof
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, validProofVitalikPToken), vitalik);
pointTokenVault.claimPTokens(
PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, validProofVitalikPToken), vitalik, vitalik
);

// Redeem the tokens for rewards with the wrong proof should fail
bytes32[] memory empty = new bytes32[](0);
Expand Down Expand Up @@ -455,22 +458,57 @@ contract PointTokenVaultTest is Test {

// Can do a partial claim
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik, vitalik);

assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 0.5e18);

// Can only claim the remainder, no more
vm.prank(vitalik);
vm.expectRevert(PointTokenVault.ClaimTooLarge.selector);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.75e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.75e18, proof), vitalik, vitalik);

// Can claim the rest
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik, vitalik);

assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 1e18);
}

function test_TrustedClaimer() public {
bytes32 root = 0x4e40a10ce33f33a4786960a8bb843fe0e170b651acd83da27abc97176c4bed3c;

bytes32[] memory proof = new bytes32[](1);
proof[0] = 0x6d0fcb8de12b1f57f81e49fa18b641487b932cdba4f064409fde3b05d3824ca2;

vm.prank(merkleUpdater);
pointTokenVault.updateRoot(root);

// Toly tries to claim vitalik's pTokens (should fail)
vm.prank(toly);
vm.expectRevert(PointTokenVault.NotTrustedClaimer.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);

// Toly claims the half of Vitalik's pTokens
vm.prank(toly);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik, toly);

// Check balances
assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(toly), 0.5e18);
assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 0);

// Vitalik claims the remaining amount for himself
vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 0.5e18, proof), vitalik, vitalik);

// Check final balances
assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(toly), 0.5e18);
assertEq(pointTokenVault.pTokens(eigenPointsId).balanceOf(vitalik), 0.5e18);
}

event RewardsConverted(address indexed owner, address indexed receiver, bytes32 indexed pointsId, uint256 amount);

function test_MintPTokensForRewards() public {
Expand All @@ -483,7 +521,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

rewardToken.mint(address(pointTokenVault), 3e18);

Expand Down Expand Up @@ -535,7 +573,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

rewardToken.mint(address(pointTokenVault), 3e18);

Expand Down Expand Up @@ -568,7 +606,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

rewardToken.mint(address(pointTokenVault), 3e18);

Expand Down Expand Up @@ -626,7 +664,7 @@ contract PointTokenVaultTest is Test {
// Cannot claim if pToken hasn't been deployed yet
vm.prank(vitalik);
vm.expectRevert(PointTokenVault.PTokenNotDeployed.selector);
mockVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
mockVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);
}

function test_PTokenPause() public {
Expand All @@ -639,7 +677,7 @@ contract PointTokenVaultTest is Test {
pointTokenVault.updateRoot(root);

vm.prank(vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik);
pointTokenVault.claimPTokens(PointTokenVault.Claim(eigenPointsId, 1e18, 1e18, proof), vitalik, vitalik);

PToken pToken = pointTokenVault.pTokens(eigenPointsId);

Expand All @@ -650,15 +688,15 @@ contract PointTokenVaultTest is Test {
// Cannot transfer pTokens
vm.prank(vitalik);
vm.expectRevert(Pausable.EnforcedPause.selector);
pToken.transfer(vitalik, 1e18);
pToken.transfer(toly, 1e18);

// Unpause the pToken
vm.prank(operator);
pointTokenVault.unpausePToken(eigenPointsId);

// Can transfer pTokens
vm.prank(vitalik);
pToken.transfer(vitalik, 1e18);
pToken.transfer(toly, 1e18);
}

// Internal
Expand Down

0 comments on commit fe490b7

Please sign in to comment.