diff --git a/package-lock.json b/package-lock.json index 812d1513..6e2f5eba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@bananapus/address-registry": "^0.0.3", "@bananapus/core": "github:Bananapus/nana-core#feat/sphinx", "@bananapus/ownable": "^0.0.3", + "@bananapus/permission-ids": "^0.0.2", "@openzeppelin/contracts": "^5.0.1", "@prb/math": "^4.0.2", @@ -139,9 +140,9 @@ } }, "node_modules/@bananapus/ownable": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@bananapus/ownable/-/ownable-0.0.3.tgz", - "integrity": "sha512-KMnfzMRwxepn69mgVPpPg1huiwVRMaWJ/kjBpZG619ON6uErhDKMVxbMy09uMQ92TFvwWfbvdHAa65kfhVxDvw==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@bananapus/ownable/-/ownable-0.0.4.tgz", + "integrity": "sha512-rKN3cIffC5yDuQdI6M4HhBbM5yWYAMvm26chZyHL56wMtMgGBZQAnQf9pXciVERAgjZRhwoEsUEIcG89ZmIzQw==", "dependencies": { "@bananapus/core": "^0.0.6", "@openzeppelin/contracts": "^5.0.1" diff --git a/package.json b/package.json index ad2a13ab..852d2c3f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@bananapus/address-registry": "^0.0.3", "@bananapus/core": "github:Bananapus/nana-core#feat/sphinx", - "@bananapus/ownable": "^0.0.3", + "@bananapus/ownable": "^0.0.4", "@bananapus/permission-ids": "^0.0.2", "@openzeppelin/contracts": "^5.0.1", "@prb/math": "^4.0.2", diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index c57c662a..a7a0ce10 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -58,7 +58,6 @@ contract DeployScript is Script, Sphinx { type(JBAddressRegistry).creationCode, "" ); - // Deploy it if it has not been deployed yet. registry = !_registryIsDeployed ? new JBAddressRegistry{salt: ADDRESS_REGISTRY_SALT}() : diff --git a/src/JB721TiersHook.sol b/src/JB721TiersHook.sol index 4defa313..59e47a92 100644 --- a/src/JB721TiersHook.sol +++ b/src/JB721TiersHook.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; import {mulDiv} from "@prb/math/src/Common.sol"; import {JBOwnable} from "@bananapus/ownable/src/JBOwnable.sol"; import {JBOwnableOverrides} from "@bananapus/ownable/src/JBOwnableOverrides.sol"; @@ -32,7 +34,7 @@ import {JB721TiersMintReservesConfig} from "./structs/JB721TiersMintReservesConf /// the project is paid, the hook may mint NFTs to the payer, depending on the hook's setup, the amount paid, and /// information specified by the payer. The project's owner can enable NFT redemptions through this hook, allowing /// holders to burn their NFTs to reclaim funds from the project (in proportion to the NFT's price). -contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { +contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook { //*********************************************************************// // --------------------------- custom errors ------------------------- // //*********************************************************************// @@ -199,12 +201,15 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { /// @param directory A directory of terminals and controllers for projects. /// @param permissions A contract storing permissions. + /// @param trustedForwarder The trusted forwarder for the ERC2771Context. constructor( IJBDirectory directory, - IJBPermissions permissions + IJBPermissions permissions, + address trustedForwarder ) JBOwnable(directory.PROJECTS(), permissions) JB721Hook(directory) + ERC2771Context(trustedForwarder) { CODE_ORIGIN = address(this); } @@ -277,7 +282,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { ) store.recordFlags(flags); // Transfer ownership to the initializer. - _transferOwnership(msg.sender); + _transferOwnership(_msgSender()); } //*********************************************************************// @@ -319,7 +324,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Mint the NFT. _mint(beneficiary, tokenId); - emit Mint(tokenId, tierIds[i], beneficiary, 0, msg.sender); + emit Mint(tokenId, tierIds[i], beneficiary, 0, _msgSender()); } } @@ -365,7 +370,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Emit events for each removed tier. for (uint256 i; i < numberOfTiersToRemove; i++) { - emit RemoveTier(tierIdsToRemove[i], msg.sender); + emit RemoveTier(tierIdsToRemove[i], _msgSender()); } } @@ -376,7 +381,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Emit events for each added tier. for (uint256 i; i < numberOfTiersToAdd; i++) { - emit AddTier(tierIdsAdded[i], tiersToAdd[i], msg.sender); + emit AddTier(tierIdsAdded[i], tiersToAdd[i], _msgSender()); } } } @@ -408,12 +413,12 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { if (bytes(baseUri).length != 0) { // Store the new base URI. baseURI = baseUri; - emit SetBaseUri(baseUri, msg.sender); + emit SetBaseUri(baseUri, _msgSender()); } if (bytes(contractUri).length != 0) { // Store the new contract URI. contractURI = contractUri; - emit SetContractUri(contractUri, msg.sender); + emit SetContractUri(contractUri, _msgSender()); } // Keep a reference to the store. @@ -423,13 +428,13 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Store the new URI resolver. store.recordSetTokenUriResolver(tokenUriResolver); - emit SetTokenUriResolver(tokenUriResolver, msg.sender); + emit SetTokenUriResolver(tokenUriResolver, _msgSender()); } if (encodedIPFSTUriTierId != 0 && encodedIPFSUri != bytes32(0)) { // Store the new encoded IPFS URI. store.recordSetEncodedIPFSUriOf(encodedIPFSTUriTierId, encodedIPFSUri); - emit SetEncodedIPFSUri(encodedIPFSTUriTierId, encodedIPFSUri, msg.sender); + emit SetEncodedIPFSUri(encodedIPFSTUriTierId, encodedIPFSUri, _msgSender()); } } @@ -470,7 +475,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Mint the NFT. _mint(reserveBeneficiary, tokenId); - emit MintReservedNft(tokenId, tierId, reserveBeneficiary, msg.sender); + emit MintReservedNft(tokenId, tierId, reserveBeneficiary, _msgSender()); } } @@ -572,9 +577,9 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Emit the change in NFT credits. if (newPayCredits > payCredits) { - emit AddPayCredits(newPayCredits - payCredits, newPayCredits, context.beneficiary, msg.sender); + emit AddPayCredits(newPayCredits - payCredits, newPayCredits, context.beneficiary, _msgSender()); } else if (payCredits > newPayCredits) { - emit UsePayCredits(payCredits - newPayCredits, newPayCredits, context.beneficiary, msg.sender); + emit UsePayCredits(payCredits - newPayCredits, newPayCredits, context.beneficiary, _msgSender()); } // Store the new NFT credits for the beneficiary. @@ -583,7 +588,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Otherwise, reset their NFT credits. } else if (payCredits != unusedPayCredits) { // Emit the change in NFT credits. - emit UsePayCredits(payCredits - unusedPayCredits, unusedPayCredits, context.beneficiary, msg.sender); + emit UsePayCredits(payCredits - unusedPayCredits, unusedPayCredits, context.beneficiary, _msgSender()); // Store the new NFT credits. payCreditsOf[context.beneficiary] = unusedPayCredits; @@ -636,7 +641,7 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Mint the NFT. _mint(beneficiary, tokenId); - emit Mint(tokenId, mintTierIds[i], beneficiary, amount, msg.sender); + emit Mint(tokenId, mintTierIds[i], beneficiary, amount, _msgSender()); } } @@ -674,4 +679,21 @@ contract JB721TiersHook is JBOwnable, JB721Hook, IJB721TiersHook { // Record the transfer. store.recordTransferForTier(tier.id, from, to); } + + /// @notice Returns the sender, prefered to use over `msg.sender` + /// @return sender the sender address of this call. + function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) { + return ERC2771Context._msgSender(); + } + + /// @notice Returns the calldata, prefered to use over `msg.data` + /// @return calldata the `msg.data` of this call + function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) { + return ERC2771Context._msgData(); + } + + /// @dev ERC-2771 specifies the context as being a single address (20 bytes). + function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) { + return super._contextSuffixLength(); + } } diff --git a/src/JB721TiersHookDeployer.sol b/src/JB721TiersHookDeployer.sol index e7eb2f0d..a20a9120 100644 --- a/src/JB721TiersHookDeployer.sol +++ b/src/JB721TiersHookDeployer.sol @@ -9,11 +9,12 @@ import {IJB721TiersHookDeployer} from "./interfaces/IJB721TiersHookDeployer.sol" import {IJB721TiersHook} from "./interfaces/IJB721TiersHook.sol"; import {JBDeploy721TiersHookConfig} from "./structs/JBDeploy721TiersHookConfig.sol"; import {IJB721TiersHookStore} from "./interfaces/IJB721TiersHookStore.sol"; +import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; import {JB721TiersHook} from "./JB721TiersHook.sol"; /// @title JB721TiersHookDeployer /// @notice Deploys a `JB721TiersHook`. -contract JB721TiersHookDeployer is IJB721TiersHookDeployer { +contract JB721TiersHookDeployer is ERC2771Context, IJB721TiersHookDeployer { //*********************************************************************// // ----------------------- internal properties ----------------------- // //*********************************************************************// @@ -40,7 +41,14 @@ contract JB721TiersHookDeployer is IJB721TiersHookDeployer { /// @param hook Reference copy of a hook. /// @param addressRegistry A registry which stores references to contracts and their deployers. - constructor(JB721TiersHook hook, IJB721TiersHookStore store, IJBAddressRegistry addressRegistry) { + constructor( + JB721TiersHook hook, + IJB721TiersHookStore store, + IJBAddressRegistry addressRegistry, + address trustedForwarder + ) + ERC2771Context(trustedForwarder) + { HOOK = hook; STORE = store; ADDRESS_REGISTRY = addressRegistry; @@ -79,7 +87,7 @@ contract JB721TiersHookDeployer is IJB721TiersHookDeployer { }); // Transfer the hook's ownership to the address that called this function. - JBOwnable(address(newHook)).transferOwnership(msg.sender); + JBOwnable(address(newHook)).transferOwnership(_msgSender()); // Add the hook to the address registry. This contract's nonce starts at 1. ADDRESS_REGISTRY.registerAddress(address(this), ++_nonce); diff --git a/test/E2E/Pay_Mint_Redeem_E2E.t.sol b/test/E2E/Pay_Mint_Redeem_E2E.t.sol index 8af6903c..185be4de 100644 --- a/test/E2E/Pay_Mint_Redeem_E2E.t.sol +++ b/test/E2E/Pay_Mint_Redeem_E2E.t.sol @@ -17,6 +17,7 @@ contract Test_TiersHook_E2E is TestBaseWorkflow { using JBRulesetMetadataResolver for JBRuleset; address reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary"))); + address trustedForwarder = address(123_456); JB721TiersHook hook; @@ -55,10 +56,9 @@ contract Test_TiersHook_E2E is TestBaseWorkflow { function setUp() public override { super.setUp(); - hook = new JB721TiersHook(jbDirectory, jbPermissions); + hook = new JB721TiersHook(jbDirectory, jbPermissions, trustedForwarder); addressRegistry = new JBAddressRegistry(); - store = new JB721TiersHookStore(); - JB721TiersHookDeployer hookDeployer = new JB721TiersHookDeployer(hook, store, addressRegistry); + JB721TiersHookDeployer hookDeployer = new JB721TiersHookDeployer(hook, store, addressRegistry, trustedForwarder); deployer = new JB721TiersHookProjectDeployer(IJBDirectory(jbDirectory), IJBPermissions(jbPermissions), hookDeployer); diff --git a/test/utils/ForTest_JB721TiersHook.sol b/test/utils/ForTest_JB721TiersHook.sol index ee8e9420..fb342f22 100644 --- a/test/utils/ForTest_JB721TiersHook.sol +++ b/test/utils/ForTest_JB721TiersHook.sol @@ -39,6 +39,7 @@ contract ForTest_JB721TiersHook is JB721TiersHook { uint256 constant SURPLUS = 10e18; uint256 constant REDEMPTION_RATE = JBConstants.MAX_RESERVED_RATE; // 40% + address _trustedForwarder = address(123_456); constructor( uint256 projectId, @@ -54,7 +55,7 @@ contract ForTest_JB721TiersHook is JB721TiersHook { JB721TiersHookFlags memory flags ) // The directory is also `IJBPermissioned`. - JB721TiersHook(directory, IJBPermissioned(address(directory)).PERMISSIONS()) + JB721TiersHook(directory, IJBPermissioned(address(directory)).PERMISSIONS(), _trustedForwarder) { // Disable the safety check to not allow initializing the original contract CODE_ORIGIN = address(0); diff --git a/test/utils/UnitTestSetup.sol b/test/utils/UnitTestSetup.sol index f51105d5..91dc1da9 100644 --- a/test/utils/UnitTestSetup.sol +++ b/test/utils/UnitTestSetup.sol @@ -51,6 +51,8 @@ contract UnitTestSetup is Test { uint256 constant REDEMPTION_RATE = 4000; // 40% + address constant trustedForwarder = address(123_456); + JB721TierConfig defaultTierConfig; // To generate: @@ -205,9 +207,11 @@ contract UnitTestSetup is Test { mockJBDirectory, abi.encodeWithSelector(IJBPermissioned.PERMISSIONS.selector), abi.encode(mockJBPermissions) ); - hookOrigin = new JB721TiersHook(IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions)); + hookOrigin = + new JB721TiersHook(IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions), trustedForwarder); addressRegistry = new JBAddressRegistry(); store = new JB721TiersHookStore(); + jbHookDeployer = new JB721TiersHookDeployer(hookOrigin, store, addressRegistry, trustedForwarder); jbHookDeployer = new JB721TiersHookDeployer(hookOrigin, store, addressRegistry); JBDeploy721TiersHookConfig memory hookConfig = JBDeploy721TiersHookConfig( name,