diff --git a/script/deploy/DeployNFTMultiplier.s.sol b/script/deploy/DeployNFTMultiplier.s.sol new file mode 100644 index 0000000..08c597c --- /dev/null +++ b/script/deploy/DeployNFTMultiplier.s.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Script} from "forge-std/Script.sol"; +import {NFTMultiplier} from "src/multipliers/NFTMultiplier.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {console} from "forge-std/console.sol"; + +contract DeployNFTMultiplier is Script { + function run() external { + uint256 deployerPrivateKey; + address nftContractAddress; + uint256 initialMultiplyingFactor; + uint256 validUntilBlock; + + string memory configPath = "deploy_config.json"; + string memory jsonData; + // Try to read the JSON file, if it doesn't exist or can't be read, catch the error + try vm.readFile(configPath) returns (string memory data) { + jsonData = data; + } catch { + console.log("Config file not found or couldn't be read. Falling back to environment variables."); + jsonData = ""; + } + + if (bytes(jsonData).length > 0) { + // Read from JSON if file exists + deployerPrivateKey = vm.parseJsonUint(jsonData, ".deployerPrivateKey"); + nftContractAddress = vm.parseJsonAddress(jsonData, ".nftContractAddress"); + initialMultiplyingFactor = vm.parseJsonUint(jsonData, ".initialMultiplyingFactor"); + validUntilBlock = vm.parseJsonUint(jsonData, ".validUntilBlock"); + } else { + // Fall back to environment variables + deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + nftContractAddress = vm.envAddress("NFT_CONTRACT_ADDRESS"); + initialMultiplyingFactor = vm.envUint("INITIAL_MULTIPLYING_FACTOR"); + validUntilBlock = vm.envUint("VALID_UNTIL_BLOCK"); + } + + // Check if all required variables are set + require(deployerPrivateKey != 0, "Deployer private key not set"); + require(nftContractAddress != address(0), "NFT contract address not set"); + require(initialMultiplyingFactor != 0, "Initial multiplying factor not set"); + require(validUntilBlock != 0, "Valid until block not set"); + + vm.startBroadcast(deployerPrivateKey); + + NFTMultiplier implementation = new NFTMultiplier(); + + bytes memory initData = abi.encodeWithSelector( + NFTMultiplier.initialize.selector, IERC721(nftContractAddress), initialMultiplyingFactor, validUntilBlock + ); + + TransparentUpgradeableProxy proxy = + new TransparentUpgradeableProxy(address(implementation), vm.addr(deployerPrivateKey), initData); + + NFTMultiplier nftMultiplier = NFTMultiplier(address(proxy)); + + vm.stopBroadcast(); + + console.log("NFTMultiplier deployed at:", address(nftMultiplier)); + } +} diff --git a/script/deploy/config/deployNFTMul.json b/script/deploy/config/deployNFTMul.json new file mode 100644 index 0000000..bb6699f --- /dev/null +++ b/script/deploy/config/deployNFTMul.json @@ -0,0 +1,6 @@ +{ + "deployerPrivateKey": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "nftContractAddress": "0x1234567890123456789012345678901234567890", + "initialMultiplyingFactor": 100, + "validUntilBlock": 1000000 +} \ No newline at end of file diff --git a/src/multipliers/NFTMultiplier.sol b/src/multipliers/NFTMultiplier.sol new file mode 100644 index 0000000..fda20f2 --- /dev/null +++ b/src/multipliers/NFTMultiplier.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {INFTMultiplier} from "src/interfaces/multipliers/INFTMultiplier.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/// @title NFT Multiplier +/// @notice Implementation of INFTMultiplier interface +/// @dev Provides multiplying factors based on NFT ownership +contract NFTMultiplier is INFTMultiplier, Initializable, OwnableUpgradeable { + IERC721 public nftContract; + uint256 public multiplyingFactor; + uint256 public validUntilBlock; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Initializer function to set the NFT contract address and initial multiplying factor + /// @param _nftContract Address of the NFT contract + /// @param _initialMultiplyingFactor Initial multiplying factor (in basis points, e.g., 15000 for 1.5x) + /// @param _validUntilBlock Block number until which the multiplier is valid + function initialize(IERC721 _nftContract, uint256 _initialMultiplyingFactor, uint256 _validUntilBlock) + public + initializer + { + __Ownable_init(msg.sender); + + nftContract = _nftContract; + multiplyingFactor = _initialMultiplyingFactor; + validUntilBlock = _validUntilBlock; + } + + /// @notice Get the address of the NFT contract + /// @return The address of the NFT contract used for checking ownership + function NFTAddress() external view override returns (IERC721) { + return nftContract; + } + + /// @notice Check if a user owns an NFT + /// @param user The address of the user to check + /// @return True if the user owns at least one NFT, false otherwise + function hasNFT(address user) public view override returns (bool) { + return nftContract.balanceOf(user) > 0; + } + + /// @notice Get the multiplying factor for a given user + /// @param user The address of the user + /// @return The multiplying factor if the user owns an NFT, 0 otherwise + function getMultiplyingFactor(address user) external view override returns (uint256) { + return hasNFT(user) ? multiplyingFactor : 0; + } + + /// @notice Get the block number until which the multiplier is valid + /// @return The block number until which the multiplier is valid + function validUntil(address /* user */ ) external view override returns (uint256) { + return validUntilBlock; + } + + /// @notice Update the multiplying factor + /// @param _newMultiplyingFactor New multiplying factor (in basis points) + function updateMultiplyingFactor(uint256 _newMultiplyingFactor) external onlyOwner { + multiplyingFactor = _newMultiplyingFactor; + } + + /// @notice Update the valid until block + /// @param _newValidUntilBlock New block number until which the multiplier is valid + function updateValidUntilBlock(uint256 _newValidUntilBlock) external onlyOwner { + validUntilBlock = _newValidUntilBlock; + } + + /// @notice Update the NFT contract address + /// @param _newNFTContract New NFT contract address + function updateNFTContract(IERC721 _newNFTContract) external onlyOwner { + nftContract = _newNFTContract; + } +}