From 3cc7f2b7f04b09b6cde33f6f78d154eb0f17d82a Mon Sep 17 00:00:00 2001 From: mejango Date: Wed, 23 Aug 2023 09:10:02 -0400 Subject: [PATCH] added beneficiary --- contracts/JBTokenStore.sol | 8 ++- contracts/interfaces/IJBTokenStore.sol | 8 ++- .../configure_for.test.js | 2 +- test/jb_token_store/claim_for.test.js | 63 +++++++++++++++++-- 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/contracts/JBTokenStore.sol b/contracts/JBTokenStore.sol index a7e010798..7b0f40d9f 100644 --- a/contracts/JBTokenStore.sol +++ b/contracts/JBTokenStore.sol @@ -298,10 +298,12 @@ contract JBTokenStore is JBControllerUtility, JBOperatable, IJBTokenStore { /// @param _holder The owner of the tokens being claimed. /// @param _projectId The ID of the project whose tokens are being claimed. /// @param _amount The amount of tokens to claim. + /// @param _beneficiary The account into which the claimed tokens will go. function claimFor( address _holder, uint256 _projectId, - uint256 _amount + uint256 _amount, + address _beneficiary ) external override requirePermission(_holder, _projectId, JBOperations.CLAIM) { // Get a reference to the project's current token. IJBToken _token = tokenOf[_projectId]; @@ -324,9 +326,9 @@ contract JBTokenStore is JBControllerUtility, JBOperatable, IJBTokenStore { } // Mint the equivalent amount of the project's token for the holder. - _token.mint(_projectId, _holder, _amount); + _token.mint(_projectId, _beneficiary, _amount); - emit Claim(_holder, _projectId, _unclaimedBalance, _amount, msg.sender); + emit Claim(_holder, _projectId, _unclaimedBalance, _amount, _beneficiary, msg.sender); } /// @notice Allows a holder to transfer unclaimed tokens to another account. diff --git a/contracts/interfaces/IJBTokenStore.sol b/contracts/interfaces/IJBTokenStore.sol index caa45fab6..11ffbe247 100644 --- a/contracts/interfaces/IJBTokenStore.sol +++ b/contracts/interfaces/IJBTokenStore.sol @@ -38,6 +38,7 @@ interface IJBTokenStore { uint256 indexed projectId, uint256 initialUnclaimedBalance, uint256 amount, + address beneficiary, address caller ); @@ -87,7 +88,12 @@ interface IJBTokenStore { bool preferClaimedTokens ) external; - function claimFor(address holder, uint256 projectId, uint256 amount) external; + function claimFor( + address holder, + uint256 projectId, + uint256 amount, + address benenficiary + ) external; function transferFrom( address holder, diff --git a/test/jb_funding_cycle_store/configure_for.test.js b/test/jb_funding_cycle_store/configure_for.test.js index 0db537c61..db3e2758b 100644 --- a/test/jb_funding_cycle_store/configure_for.test.js +++ b/test/jb_funding_cycle_store/configure_for.test.js @@ -15,7 +15,7 @@ import ijbFundingCycleBallot from '../../artifacts/contracts/interfaces/IJBFundi import { BigNumber } from 'ethers'; import errors from '../helpers/errors.json'; -describe.only('JBFundingCycleStore::configureFor(...)', function () { +describe('JBFundingCycleStore::configureFor(...)', function () { const PROJECT_ID = 2; const EMPTY_FUNDING_CYCLE = { diff --git a/test/jb_token_store/claim_for.test.js b/test/jb_token_store/claim_for.test.js index 278873343..6fea18e18 100644 --- a/test/jb_token_store/claim_for.test.js +++ b/test/jb_token_store/claim_for.test.js @@ -15,7 +15,7 @@ describe('JBTokenStore::claimFor(...)', function () { const TOKEN_SYMBOL = 'TEST'; async function setup() { - const [deployer, controller, newHolder, projectOwner] = await ethers.getSigners(); + const [deployer, controller, newHolder, beneficiary, projectOwner] = await ethers.getSigners(); const mockJbDirectory = await deployMockContract(deployer, jbDirectory.abi); const mockJbFundingCycleStore = await deployMockContract(deployer, jbFundingCycleStore.abi); @@ -39,6 +39,7 @@ describe('JBTokenStore::claimFor(...)', function () { return { controller, newHolder, + beneficiary, projectOwner, mockJbDirectory, mockJbProjects, @@ -82,7 +83,7 @@ describe('JBTokenStore::claimFor(...)', function () { // Claim the unclaimed tokens const claimForTx = await jbTokenStore .connect(controller) - .claimFor(newHolder.address, PROJECT_ID, amountToClaim); + .claimFor(newHolder.address, PROJECT_ID, amountToClaim, newHolder.address); expect(await jbTokenStore.unclaimedBalanceOf(newHolder.address, PROJECT_ID)).to.equal( numTokens - amountToClaim, @@ -92,14 +93,64 @@ describe('JBTokenStore::claimFor(...)', function () { await expect(claimForTx) .to.emit(jbTokenStore, 'Claim') - .withArgs(newHolder.address, PROJECT_ID, numTokens, amountToClaim, controller.address); + .withArgs(newHolder.address, PROJECT_ID, numTokens, amountToClaim, newHolder.address, controller.address); + }); + it('Should claim tokens to a different beneficiary and emit event', async function () { + const { + controller, + newHolder, + projectOwner, + mockJbDirectory, + mockJbOperatorStore, + mockJbProjects, + jbTokenStore, + beneficiary, + CLAIM_INDEX, + addrs + } = await setup(); + + // Mint access: + await mockJbDirectory.mock.controllerOf.withArgs(PROJECT_ID).returns(controller.address); + + // Issue access: + await mockJbProjects.mock.ownerOf.withArgs(PROJECT_ID).returns(controller.address); + + await jbTokenStore.connect(controller).issueFor(PROJECT_ID, TOKEN_NAME, TOKEN_SYMBOL); + await mockJbOperatorStore.mock.hasPermission + .withArgs(controller.address, newHolder.address, PROJECT_ID, CLAIM_INDEX) + .returns(true); + + // Mint more unclaimed tokens + const numTokens = 20; + await jbTokenStore + .connect(controller) + .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ false); + + const amountToClaim = numTokens - 1; + + // Claim the unclaimed tokens + const claimForTx = await jbTokenStore + .connect(controller) + .claimFor(newHolder.address, PROJECT_ID, amountToClaim, beneficiary.address); + + expect(await jbTokenStore.unclaimedBalanceOf(newHolder.address, PROJECT_ID)).to.equal( + numTokens - amountToClaim, + ); + expect(await jbTokenStore.unclaimedBalanceOf(beneficiary.address, PROJECT_ID)).to.equal(0); + expect(await jbTokenStore.balanceOf(newHolder.address, PROJECT_ID)).to.equal(numTokens - amountToClaim); + expect(await jbTokenStore.balanceOf(beneficiary.address, PROJECT_ID)).to.equal(amountToClaim); + expect(await jbTokenStore.totalSupplyOf(PROJECT_ID)).to.equal(numTokens); + + await expect(claimForTx) + .to.emit(jbTokenStore, 'Claim') + .withArgs(newHolder.address, PROJECT_ID, numTokens, amountToClaim, beneficiary.address, controller.address); }); it(`Can't claim tokens if projectId isn't found`, async function () { const { newHolder, jbTokenStore } = await setup(); const numTokens = 1; await expect( - jbTokenStore.connect(newHolder).claimFor(newHolder.address, PROJECT_ID, numTokens), + jbTokenStore.connect(newHolder).claimFor(newHolder.address, PROJECT_ID, numTokens, newHolder.address), ).to.be.revertedWith(errors.TOKEN_NOT_FOUND); }); @@ -119,7 +170,7 @@ describe('JBTokenStore::claimFor(...)', function () { .mintFor(newHolder.address, PROJECT_ID, numTokens, /* preferClaimedTokens= */ false); await expect( - jbTokenStore.connect(newHolder).claimFor(newHolder.address, PROJECT_ID, numTokens + 1), + jbTokenStore.connect(newHolder).claimFor(newHolder.address, PROJECT_ID, numTokens + 1, newHolder.address), ).to.be.revertedWith(errors.INSUFFICIENT_UNCLAIMED_TOKENS); }); it(`Can't claim unclaimed tokens if caller lacks permission`, async function () { @@ -128,7 +179,7 @@ describe('JBTokenStore::claimFor(...)', function () { await expect( jbTokenStore .connect(controller) - .claimFor(/* holder */ newHolder.address, PROJECT_ID, /* amount= */ 1), + .claimFor(/* holder */ newHolder.address, PROJECT_ID, /* amount= */ 1, newHolder.address), ).to.be.reverted; }); });