From c2b82886b8cf674db892eb8961c7cd34665eaa9e Mon Sep 17 00:00:00 2001 From: spengrah Date: Wed, 11 Sep 2024 14:40:11 -0500 Subject: [PATCH] allow changes to referral fee percentage on the implementation --- src/PublicLockV14Eligibility.sol | 58 ++++++++++++++++++++++++++------ test/PublicLockEligibility.t.sol | 50 +++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/PublicLockV14Eligibility.sol b/src/PublicLockV14Eligibility.sol index d76abe6..17d6ad6 100644 --- a/src/PublicLockV14Eligibility.sol +++ b/src/PublicLockV14Eligibility.sol @@ -28,6 +28,16 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook /// @dev Thrown when the hat minting fails error HatMintFailed(); + /// @dev Thrown when a non-referrer calls a function only authorized to the referrer + error NotReferrer(); + + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when the referrer fee percentage is set in the implementation contract + event ImplementationReferrerFeePercentageSet(uint256 referrerFeePercentage); + /*////////////////////////////////////////////////////////////// DATA MODELS //////////////////////////////////////////////////////////////*/ @@ -51,9 +61,6 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook /// @notice The address to split key purchase fees to, set as a referrer on the lock address public immutable REFERRER; - /// @notice The percentage of key purchase fees that go to the referrer, in basis points (10000 = 100%) - uint256 public immutable REFERRER_FEE_PERCENTAGE; - /// @notice The Unlock Protocol factory contract /// @dev Used only for the implementation contract; for clones/instances, use {unlock} IUnlock public unlock_; @@ -63,6 +70,10 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook return PublicLockV14Eligibility(IMPLEMENTATION()).unlock_(); } + /// @notice The referrer fee percentage for this module instance and associated lock. It is set to the value of the + /// {implementationReferrerFeePercentage} during {setUp}, and cannot be updated. + uint256 public referrerFeePercentage; + /** * This contract is a clone with immutable args, which means that it is deployed with a set of * immutable storage variables (ie constants). Accessing these constants is cheaper than accessing @@ -88,6 +99,10 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook MUTABLE STATE //////////////////////////////////////////////////////////////*/ + /// @notice The referrer fee percentage that will be used for subsequent instances of this module. + /// Will be 0 for all instances; use {referrerFeePercentage} for the fee percentage for a given instance. + uint256 public implementationReferrerFeePercentage; + /// @notice The Unlock Protocol lock contract that is created along with this module and coupled to the hat IPublicLock public lock; @@ -98,18 +113,18 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook /// @notice Deploy the implementation contract and set its version /// @param _version The version of the implementation contract /// @param _referrer The referrer address, which will receive a portion of the fees - /// @param _referrerFeePercentage The percentage of fees to go to the referrer, in basis points (10000 = 100%) + /// @param __referrerFeePercentage The percentage of fees to go to the referrer, in basis points (10000 = 100%) /// @dev This is only used to deploy the implementation contract, and should not be used to deploy clones - constructor(string memory _version, IUnlock _unlock, address _referrer, uint256 _referrerFeePercentage) + constructor(string memory _version, IUnlock _unlock, address _referrer, uint256 __referrerFeePercentage) HatsModule(_version) { unlock_ = _unlock; REFERRER = _referrer; - REFERRER_FEE_PERCENTAGE = _referrerFeePercentage; + implementationReferrerFeePercentage = __referrerFeePercentage; } /*////////////////////////////////////////////////////////////// - INITIALIZOR + INITIALIZER //////////////////////////////////////////////////////////////*/ /// @inheritdoc HatsModule @@ -142,8 +157,12 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook _onKeyGrantHook: address(0) }); - // set referrer fee - lock.setReferrerFee(REFERRER, REFERRER_FEE_PERCENTAGE); + // set referrer fee percentage in this instance + uint256 fee = PublicLockV14Eligibility(IMPLEMENTATION()).implementationReferrerFeePercentage(); + referrerFeePercentage = fee; + + // set referrer and their fee percentage in the lock + lock.setReferrerFee(REFERRER, fee); // add lock manager role to the configured address lock.addLockManager(lockConfig.lockManager); @@ -180,7 +199,7 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook bytes calldata /* data */ ) external view returns (uint256 minKeyPrice) { // Check if referrer fee is correct. Fail minting if incorrect. - if (lock.referrerFees(REFERRER) != REFERRER_FEE_PERCENTAGE) { + if (lock.referrerFees(REFERRER) != referrerFeePercentage) { revert InvalidReferrerFee(); } @@ -246,6 +265,25 @@ contract PublicLockV14Eligibility is HatsEligibilityModule, ILockKeyPurchaseHook revert NotTransferable(); } + /*////////////////////////////////////////////////////////////// + ADMIN FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the referrer fee percentage on the implementation contract. This value will be used for subsequent + * instances of this module. It will not change the referrer fee percentage for existing instances. + * @dev This function can only be called by the referrer. + * @param _referrerFeePercentage The new referrer fee percentage + */ + function setImplementationReferrerFeePercentage(uint256 _referrerFeePercentage) external { + // caller must be the referrer + if (msg.sender != REFERRER) revert NotReferrer(); + + implementationReferrerFeePercentage = _referrerFeePercentage; + + emit ImplementationReferrerFeePercentageSet(_referrerFeePercentage); + } + /*////////////////////////////////////////////////////////////// VIEW FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/test/PublicLockEligibility.t.sol b/test/PublicLockEligibility.t.sol index 12a0f5d..5f3c3cd 100644 --- a/test/PublicLockEligibility.t.sol +++ b/test/PublicLockEligibility.t.sol @@ -196,8 +196,16 @@ contract Deployment is WithInstanceTest { assertEq(instance.hatId(), targetHat); } - function test_referrerFeePercentage() public view { - assertEq(instance.REFERRER_FEE_PERCENTAGE(), referrerFeePercentage); + function test_referrerFeePercentage_instance() public view { + assertEq(instance.referrerFeePercentage(), referrerFeePercentage, "wrong instance value"); + + assertEq(instance.implementationReferrerFeePercentage(), 0, "wrong implementation value"); + } + + function test_referrerFeePercentage_implementation() public view { + assertEq(implementation.implementationReferrerFeePercentage(), referrerFeePercentage); + + assertEq(implementation.referrerFeePercentage(), 0); } function test_referrer() public view { @@ -425,7 +433,6 @@ contract GetWearerStatus is WithInstanceTest { uint256 key = _purchaseSingleKey(lock, wearer); // the wearer should be eligible - (bool eligible, bool standing) = instance.getWearerStatus(wearer, targetHat); assertTrue(eligible); assertTrue(standing); @@ -526,3 +533,40 @@ contract MaxNumberOfKeys is WithInstanceTest { assertEq(instance.maxNumberOfKeys(), newMaxNumberOfKeys); } } + +contract SetImplementationReferrerFeePercentage is WithInstanceTest { + uint256 public oldFee; + uint256 public newFee; + + function setUp() public override { + super.setUp(); + + oldFee = referrerFeePercentage; + newFee = oldFee * 3; + } + + function test_referrerCanSet() public { + // set the referrer fee percentage + vm.expectEmit(true, true, true, true); + emit PublicLockV14Eligibility.ImplementationReferrerFeePercentageSet(newFee); + vm.prank(referrer); + implementation.setImplementationReferrerFeePercentage(newFee); + + // referrer fee percentage for existing instance should not change + assertEq(instance.referrerFeePercentage(), oldFee); + + // new instance should have the new referrer fee percentage + deployInstance.prepare(false, address(implementation), targetHat, saltNonce + 1, lockConfig); + PublicLockV14Eligibility newInstance = deployInstance.run(); + assertEq(newInstance.referrerFeePercentage(), newFee); + } + + function test_revert_nonReferrerCannotSet() public { + // try to set the referrer fee percentage, expecting a revert + vm.expectRevert(PublicLockV14Eligibility.NotReferrer.selector); + vm.prank(makeAddr("non-referrer")); + implementation.setImplementationReferrerFeePercentage(newFee); + + assertEq(implementation.implementationReferrerFeePercentage(), oldFee); + } +}