diff --git a/contracts/DefifaDelegate.sol b/contracts/DefifaDelegate.sol index b084726..62e45df 100644 --- a/contracts/DefifaDelegate.sol +++ b/contracts/DefifaDelegate.sol @@ -158,14 +158,72 @@ contract DefifaDelegate is IDefifaDelegate, JB721TieredGovernance { return ( PRBMath.mulDiv( _data.overflow + _amountRedeemed, - _redemptionWeightOf(_decodedTokenIds, _data), - _totalRedemptionWeight(_data) + redemptionWeightOf(_decodedTokenIds, _data), + totalRedemptionWeight(_data) ), _data.memo, delegateAllocations ); } + /** + @notice + The cumulative weight the given token IDs have in redemptions compared to the `_totalRedemptionWeight`. + + @param _tokenIds The IDs of the tokens to get the cumulative redemption weight of. + + @return cumulativeWeight The weight. + */ + function redemptionWeightOf(uint256[] memory _tokenIds, JBRedeemParamsData calldata) + public + view + virtual + override + returns (uint256 cumulativeWeight) + { + // If the game is over, set the weight based on the scorecard results. + // Keep a reference to the number of tokens being redeemed. + uint256 _tokenCount = _tokenIds.length; + + for (uint256 _i; _i < _tokenCount; ) { + // Keep a reference to the token's tier ID. + uint256 _tierId = store.tierIdOfToken(_tokenIds[_i]); + + // Keep a reference to the tier. + JB721Tier memory _tier = store.tier(address(this), _tierId); + + // Calculate what percentage of the tier redemption amount a single token counts for. + cumulativeWeight += + // Tier's are 1 indexed and are stored 0 indexed. + _tierRedemptionWeights[_tierId - 1] / + (_tier.initialQuantity - _tier.remainingQuantity + _redeemedFromTier[_tierId]); + + unchecked { + ++_i; + } + } + + // If there's nothing to claim, revert to prevent burning for nothing. + if (cumulativeWeight == 0) revert NOTHING_TO_CLAIM(); + } + + /** + @notice + The cumulative weight that all token IDs have in redemptions. + + @return The total weight. + */ + function totalRedemptionWeight(JBRedeemParamsData calldata) + public + view + virtual + override + returns (uint256) + { + // Set the total weight as the total scorecard weight. + return TOTAL_REDEMPTION_WEIGHT; + } + //*********************************************************************// // ---------------------- external transactions ---------------------- // //*********************************************************************// @@ -316,62 +374,4 @@ contract DefifaDelegate is IDefifaDelegate, JB721TieredGovernance { _transferTierVotingUnits(_from, _to, _tier.id, _tier.votingUnits); } } - - /** - @notice - The cumulative weight the given token IDs have in redemptions compared to the `_totalRedemptionWeight`. - - @param _tokenIds The IDs of the tokens to get the cumulative redemption weight of. - - @return cumulativeWeight The weight. - */ - function _redemptionWeightOf(uint256[] memory _tokenIds, JBRedeemParamsData calldata) - internal - view - virtual - override - returns (uint256 cumulativeWeight) - { - // If the game is over, set the weight based on the scorecard results. - // Keep a reference to the number of tokens being redeemed. - uint256 _tokenCount = _tokenIds.length; - - for (uint256 _i; _i < _tokenCount; ) { - // Keep a reference to the token's tier ID. - uint256 _tierId = store.tierIdOfToken(_tokenIds[_i]); - - // Keep a reference to the tier. - JB721Tier memory _tier = store.tier(address(this), _tierId); - - // Calculate what percentage of the tier redemption amount a single token counts for. - cumulativeWeight += - // Tier's are 1 indexed and are stored 0 indexed. - _tierRedemptionWeights[_tierId - 1] / - (_tier.initialQuantity - _tier.remainingQuantity + _redeemedFromTier[_tierId]); - - unchecked { - ++_i; - } - } - - // If there's nothing to claim, revert to prevent burning for nothing. - if (cumulativeWeight == 0) revert NOTHING_TO_CLAIM(); - } - - /** - @notice - The cumulative weight that all token IDs have in redemptions. - - @return The total weight. - */ - function _totalRedemptionWeight(JBRedeemParamsData calldata) - internal - view - virtual - override - returns (uint256) - { - // Set the total weight as the total scorecard weight. - return TOTAL_REDEMPTION_WEIGHT; - } } diff --git a/contracts/DefifaDeployer.sol b/contracts/DefifaDeployer.sol index 585772f..3c760fa 100644 --- a/contracts/DefifaDeployer.sol +++ b/contracts/DefifaDeployer.sol @@ -126,12 +126,12 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { return _timesFor[_gameId].mintDuration; } - function startOf(uint256 _gameId) external view override returns (uint256) { - return _timesFor[_gameId].start; + function refundPeriodDurationOf(uint256 _gameId) external view override returns (uint256) { + return _timesFor[_gameId].refundPeriodDuration; } - function tradeDeadlineOf(uint256 _gameId) external view override returns (uint256) { - return _timesFor[_gameId].tradeDeadline; + function startOf(uint256 _gameId) external view override returns (uint256) { + return _timesFor[_gameId].start; } function endOf(uint256 _gameId) external view override returns (uint256) { @@ -227,10 +227,8 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { ) external override returns (uint256 gameId) { // Make sure the provided gameplay timestamps are sequential. if ( - _launchProjectData.start - _launchProjectData.mintDuration < block.timestamp || - _launchProjectData.tradeDeadline < _launchProjectData.start || - _launchProjectData.end < _launchProjectData.start || - _launchProjectData.end < _launchProjectData.tradeDeadline + _launchProjectData.start - _launchProjectData.refundPeriodDuration - _launchProjectData.mintDuration < block.timestamp || + _launchProjectData.end < _launchProjectData.start ) revert INVALID_GAME_CONFIGURATION(); // Get the game ID, optimistically knowing it will be one greater than the current count. @@ -243,8 +241,8 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { // Store the timestamps that'll define the game phases. _timesFor[gameId] = DefifaTimeData({ mintDuration: _launchProjectData.mintDuration, + refundPeriodDuration: _launchProjectData.refundPeriodDuration, start: _launchProjectData.start, - tradeDeadline: _launchProjectData.tradeDeadline, end: _launchProjectData.end }); @@ -285,6 +283,7 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { _pricingParams, _delegateData.store, JBTiered721Flags({ + preventOverspending: true, lockReservedTokenChanges: false, lockVotingUnitChanges: false, lockManualMintingChanges: false @@ -413,11 +412,12 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { metadata: JBTiered721FundingCycleMetadataResolver.packFundingCycleGlobalMetadata( JBTiered721FundingCycleMetadata({ pauseTransfers: false, + // Reserved tokens can't be minted during this funding cycle. pauseMintingReserves: true }) ) }), - _launchProjectData.start - _launchProjectData.mintDuration, + _launchProjectData.start - _launchProjectData.mintDuration - _launchProjectData.refundPeriodDuration, new JBGroupedSplits[](0), new JBFundAccessConstraints[](0), _terminals, @@ -430,7 +430,7 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { Gets reconfiguration data for phase 2 of the game. @dev - Phase 2 freezes the treasury and activates the pre-programmed distribution limit to the specified splits. + Phase 2 freezes mints, but continues to allow refund redemptions. @param _gameId The ID of the project that's being reconfigured. @param _dataSource The data source to use. @@ -441,33 +441,6 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { internal returns (uint256 configuration) { - // Get a reference to the terminal being used by the project. - DefifaStoredOpsData memory _ops = _opsFor[_gameId]; - - // Set fund access constraints. - JBFundAccessConstraints[] memory fundAccessConstraints = new JBFundAccessConstraints[](1); - fundAccessConstraints[0] = JBFundAccessConstraints({ - terminal: _ops.terminal, - token: token, - distributionLimit: _ops.distributionLimit, - distributionLimitCurrency: _ops.terminal.currencyForToken(token), - overflowAllowance: 0, - overflowAllowanceCurrency: 0 - }); - - // Fetch splits. - JBSplit[] memory _splits = controller.splitsStore().splitsOf(SPLIT_PROJECT_ID, SPLIT_DOMAIN, _gameId); - - // Make a group split for ETH payouts. - JBGroupedSplits[] memory _groupedSplits; - - if (_splits.length != 0) { - _groupedSplits = new JBGroupedSplits[](1); - _groupedSplits[0] = JBGroupedSplits({group: JBSplitsGroups.ETH_PAYOUT, splits: _splits}); - } - else { - _groupedSplits = new JBGroupedSplits[](0); - } // Get a reference to the time data. DefifaTimeData memory _times = _timesFor[_gameId]; @@ -476,7 +449,7 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { controller.reconfigureFundingCyclesOf( _gameId, JBFundingCycleData ({ - duration: _times.tradeDeadline - _times.start, + duration: _times.refundPeriodDuration, // Don't mint project tokens. weight: 0, discountRate: 0, @@ -489,33 +462,36 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { pauseTransfers: false }), reservedRate: 0, - redemptionRate: 0, - ballotRedemptionRate: 0, + // Full refunds. + redemptionRate: JBConstants.MAX_REDEMPTION_RATE, + ballotRedemptionRate: JBConstants.MAX_REDEMPTION_RATE, // No more payments. pausePay: true, pauseDistributions: false, - // No redemptions. - pauseRedeem: true, + // Allow redemptions. + pauseRedeem: false, pauseBurn: false, allowMinting: false, allowTerminalMigration: false, allowControllerMigration: false, - holdFees: _ops.holdFees, + holdFees: false, preferClaimedTokenOverride: false, useTotalOverflowForRedemptions: false, useDataSourceForPay: true, useDataSourceForRedeem: true, dataSource: _dataSource, + // Set a metadata of 1 to impose token non-transferability. metadata: JBTiered721FundingCycleMetadataResolver.packFundingCycleGlobalMetadata( JBTiered721FundingCycleMetadata({ pauseTransfers: false, - pauseMintingReserves: false + // Reserved tokens can't be minted during this funding cycle. + pauseMintingReserves: true }) ) }), 0, // mustStartAtOrAfter should be ASAP - _groupedSplits, - fundAccessConstraints, + new JBGroupedSplits[](0), + new JBFundAccessConstraints[](0), 'Defifa game phase 2.' ); } @@ -525,14 +501,44 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { Gets reconfiguration data for phase 3 of the game. @dev - Phase 3 imposes a trade deadline. + Phase 3 freezes the treasury and activates the pre-programmed distribution limit to the specified splits. @param _gameId The ID of the project that's being reconfigured. @param _dataSource The data source to use. @return configuration The configuration of the funding cycle that was successfully reconfigured. */ - function _queuePhase3(uint256 _gameId, address _dataSource) internal returns (uint256 configuration) { + function _queuePhase3(uint256 _gameId, address _dataSource) + internal + returns (uint256 configuration) + { + // Get a reference to the terminal being used by the project. + DefifaStoredOpsData memory _ops = _opsFor[_gameId]; + + // Set fund access constraints. + JBFundAccessConstraints[] memory fundAccessConstraints = new JBFundAccessConstraints[](1); + fundAccessConstraints[0] = JBFundAccessConstraints({ + terminal: _ops.terminal, + token: token, + distributionLimit: _ops.distributionLimit, + distributionLimitCurrency: _ops.terminal.currencyForToken(token), + overflowAllowance: 0, + overflowAllowanceCurrency: 0 + }); + + // Fetch splits. + JBSplit[] memory _splits = controller.splitsStore().splitsOf(SPLIT_PROJECT_ID, SPLIT_DOMAIN, _gameId); + + // Make a group split for ETH payouts. + JBGroupedSplits[] memory _groupedSplits; + + if (_splits.length != 0) { + _groupedSplits = new JBGroupedSplits[](1); + _groupedSplits[0] = JBGroupedSplits({group: JBSplitsGroups.ETH_PAYOUT, splits: _splits}); + } + else { + _groupedSplits = new JBGroupedSplits[](0); + } // Get a reference to the time data. DefifaTimeData memory _times = _timesFor[_gameId]; @@ -541,7 +547,7 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { controller.reconfigureFundingCyclesOf( _gameId, JBFundingCycleData ({ - duration: _times.end - _times.tradeDeadline, + duration: _times.end - _times.start, // Don't mint project tokens. weight: 0, discountRate: 0, @@ -565,23 +571,22 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { allowMinting: false, allowTerminalMigration: false, allowControllerMigration: false, - holdFees: false, + holdFees: _ops.holdFees, preferClaimedTokenOverride: false, useTotalOverflowForRedemptions: false, useDataSourceForPay: true, useDataSourceForRedeem: true, dataSource: _dataSource, - // Set a metadata of 1 to impose token non-transferability. metadata: JBTiered721FundingCycleMetadataResolver.packFundingCycleGlobalMetadata( JBTiered721FundingCycleMetadata({ - pauseTransfers: true, + pauseTransfers: false, pauseMintingReserves: false }) ) }), 0, // mustStartAtOrAfter should be ASAP - new JBGroupedSplits[](0), - new JBFundAccessConstraints[](0), + _groupedSplits, + fundAccessConstraints, 'Defifa game phase 3.' ); } @@ -591,7 +596,7 @@ contract DefifaDeployer is IDefifaDeployer, IERC721Receiver { Gets reconfiguration data for phase 4 of the game. @dev - Phase 4 removes the trade deadline and open up redemptions. + Phase 4 removes the trade deadline and opens up redemptions. @param _gameId The ID of the project that's being reconfigured. @param _dataSource The data source to use. diff --git a/contracts/DefifaGovernor.sol b/contracts/DefifaGovernor.sol index 0af81bc..9156c3a 100644 --- a/contracts/DefifaGovernor.sol +++ b/contracts/DefifaGovernor.sol @@ -29,11 +29,11 @@ contract DefifaGovernor is Governor, GovernorCountingSimple, IDefifaGovernor { // --------------------------- custom errors ------------------------- // //*********************************************************************// error INCORRECT_TIER_ORDER(); + error DISABLED(); //*********************************************************************// // -------------------- private constant properties ------------------ // //*********************************************************************// - /** @notice The duration of one block. @@ -106,7 +106,29 @@ contract DefifaGovernor is Governor, GovernorCountingSimple, IDefifaGovernor { ) = _buildScorecardCalldata(_tierWeights); // Submit the proposal. - return propose(_targets, _values, _calldatas, ''); + return this.propose(_targets, _values, _calldatas, ''); + } + + /** + @notice + Attests to a scorecard. + + @param _scorecardId The scorecard ID. + */ + function attestToScorecard(uint256 _scorecardId) external override { + // Vote. + super._castVote(_scorecardId, msg.sender, 1, '', _defaultParams()); + } + + /** + @notice + Attests to a scorecard with the set of ordered tier id's. + + @param _scorecardId The scorecard ID. + */ + function attestToScorecardWithReasonAndParams(uint256 _scorecardId, bytes memory params) external override { + // Vote. + super._castVote(_scorecardId, msg.sender, 1, '', params); } /** @@ -272,11 +294,10 @@ contract DefifaGovernor is Governor, GovernorCountingSimple, IDefifaGovernor { @return The delay in number of blocks. */ function votingDelay() public view override(IGovernor) returns (uint256) { - if (votingStartTime > block.timestamp) - return (votingStartTime - block.timestamp) / _BLOCKTIME_SECONDS; - - // no voting delay once voting is active - return 0; + return + votingStartTime > block.timestamp + ? (votingStartTime - block.timestamp) / _BLOCKTIME_SECONDS + : 0; } /** @@ -284,7 +305,8 @@ contract DefifaGovernor is Governor, GovernorCountingSimple, IDefifaGovernor { The amount of time that must go by for voting on a proposal to no longer be allowed. */ function votingPeriod() public pure override(IGovernor) returns (uint256) { - return 45818; // one week + // blocks worth 2 weeks + return 100381; } /** @@ -308,6 +330,10 @@ contract DefifaGovernor is Governor, GovernorCountingSimple, IDefifaGovernor { bytes[] memory calldatas, string memory description ) public override(Governor) returns (uint256) { + // We don't allow submitting proposals other than scorecards + if (_msgSender() != address(this)) + revert DISABLED(); + return super.propose(targets, values, calldatas, description); } diff --git a/contracts/forge-test/DefifaGovernor.t.sol b/contracts/forge-test/DefifaGovernor.t.sol index 87b0b95..68f28fc 100644 --- a/contracts/forge-test/DefifaGovernor.t.sol +++ b/contracts/forge-test/DefifaGovernor.t.sol @@ -39,10 +39,15 @@ contract DefifaGovernorTest is TestBaseWorkflow { address _user = address(bytes20(keccak256('user'))); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + + // Phase 1: Mint + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); // User should have no voting power (yet) assertEq(_governor.getVotes(_user, block.number - 1), 0); @@ -58,8 +63,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -100,11 +103,16 @@ contract DefifaGovernorTest is TestBaseWorkflow { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, ) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + // Phase 1: Mint + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); + for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); @@ -120,8 +128,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -149,40 +155,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { _nft.setTierDelegates(tiered721SetDelegatesData); } - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Make sure this is actually Phase 2 - assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 2); - - for (uint256 i = 0; i < _users.length; i++) { - address _user = _users[i]; - - // Craft the metadata: redeem the tokenId - bytes memory redemptionMetadata; - { - uint256[] memory redemptionId = new uint256[](1); - redemptionId[0] = _generateTokenId(i + 1, 1); - redemptionMetadata = abi.encode(bytes32(0), type(IJB721Delegate).interfaceId, redemptionId); - } - - vm.expectRevert(abi.encodeWithSignature('FUNDING_CYCLE_REDEEM_PAUSED()')); - vm.prank(_user); - JBETHPaymentTerminal(address(_terminals[0])).redeemTokensOf({ - _holder: _user, - _projectId: _projectId, - _tokenCount: 0, - _token: address(0), - _minReturnedTokens: 0, - _beneficiary: payable(_user), - _memo: 'Refund plz', - _metadata: redemptionMetadata - }); - } - - // Phase 3 - vm.warp(block.timestamp + 1 weeks); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Make sure this is actually Phase 3 @@ -213,7 +191,10 @@ contract DefifaGovernorTest is TestBaseWorkflow { }); } - // Phase 4 + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); + // Forward the amount of blocks needed to reach the end (and round up) + vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1); vm.warp(block.timestamp + 1 weeks); assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 4); @@ -249,13 +230,18 @@ contract DefifaGovernorTest is TestBaseWorkflow { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); - (uint256 _projectId, DefifaDelegate _nft, ) = createDefifaProject( + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); + (uint256 _projectId,,) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 1: minting + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); + + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); // Make sure this is actually Phase 2 @@ -276,8 +262,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -296,8 +280,8 @@ contract DefifaGovernorTest is TestBaseWorkflow { ); } - // Phase 3 - vm.warp(block.timestamp + 1 weeks); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Make sure this is actually Phase 3 @@ -318,8 +302,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -338,8 +320,10 @@ contract DefifaGovernorTest is TestBaseWorkflow { ); } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); + // Forward the amount of blocks needed to reach the end (and round up) + vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1); // Make sure this is actually Phase 4 assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 4); @@ -358,8 +342,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -379,79 +361,89 @@ contract DefifaGovernorTest is TestBaseWorkflow { } } - function testTransfer_fails_afterTradeDeadline() external { + // Transfers are no longer disabled + // function testTransfer_fails_afterTradeDeadline() external { + // uint8 nTiers = 10; + // address[] memory _users = new address[](nTiers); + + // DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); + // (uint256 _projectId, DefifaDelegate _nft, ) = createDefifaProject( + // uint256(nTiers), + // getBasicDefifaLaunchData() + // ); + + // // Phase 1: minting + // vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + + // for (uint256 i = 0; i < nTiers; i++) { + // // Generate a new address for each tier + // _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); + + // // fund user + // vm.deal(_users[i], 1 ether); + + // // Build metadata to buy specific NFT + // uint16[] memory rawMetadata = new uint16[](1); + // rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed + // bytes memory metadata = abi.encode( + // bytes32(0), + // bytes32(0), + // type(IJB721Delegate).interfaceId, + // false, + // false, + // false, + // rawMetadata + // ); + + // // Pay to the project and mint an NFT + // vm.prank(_users[i]); + // _terminals[0].pay{value: 1 ether}( + // _projectId, + // 1 ether, + // address(0), + // _users[i], + // 0, + // true, + // '', + // metadata + // ); + // } + + // // Phase 2: Redeem + // vm.warp(block.timestamp + defifaData.mintDuration); + // deployer.queueNextPhaseOf(_projectId); + + // // Make sure this is actually Phase 2 + // assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 2); + + // // Phase 3: Start + // vm.warp(defifaData.start + 1); + // deployer.queueNextPhaseOf(_projectId); + + // // Make sure this is actually Phase 3 + // assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 3); + + // uint256 _tokenIdToTransfer = _generateTokenId(1, 1); + // vm.prank(_users[0]); + // // trasnfers not possible in phase 3 + // vm.expectRevert(abi.encodeWithSignature('TRANSFERS_PAUSED()')); + // _nft.transferFrom(_users[0], _users[1], _tokenIdToTransfer); + // } + + function testSetRedemptionRates_fails_unmetQuorum() external { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); - (uint256 _projectId, DefifaDelegate _nft, ) = createDefifaProject( + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); + (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); - for (uint256 i = 0; i < nTiers; i++) { - // Generate a new address for each tier - _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); - - // fund user - vm.deal(_users[i], 1 ether); - - // Build metadata to buy specific NFT - uint16[] memory rawMetadata = new uint16[](1); - rawMetadata[0] = uint16(i + 1); // reward tier, 1 indexed - bytes memory metadata = abi.encode( - bytes32(0), - bytes32(0), - type(IJB721Delegate).interfaceId, - false, - false, - false, - rawMetadata - ); - - // Pay to the project and mint an NFT - vm.prank(_users[i]); - _terminals[0].pay{value: 1 ether}( - _projectId, - 1 ether, - address(0), - _users[i], - 0, - true, - '', - metadata - ); - } - - // Phase 2 - vm.warp(block.timestamp + 1 days); - deployer.queueNextPhaseOf(_projectId); - - // Make sure this is actually Phase 2 - assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 2); - - // Phase 3 - vm.warp(block.timestamp + 1 weeks); + // Phase 1: minting + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); deployer.queueNextPhaseOf(_projectId); - // Make sure this is actually Phase 3 - assertEq(_jbFundingCycleStore.currentOf(_projectId).number, 3); - - uint256 _tokenIdToTransfer = _generateTokenId(1, 1); - vm.prank(_users[0]); - // trasnfers not possible in phase 3 - vm.expectRevert(abi.encodeWithSignature('TRANSFERS_PAUSED()')); - _nft.transferFrom(_users[0], _users[1], _tokenIdToTransfer); - } - - function testSetRedemptionRates_fails_unmetQuorum(bool _useHelper) external { - uint8 nTiers = 10; - address[] memory _users = new address[](nTiers); - - (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( - uint256(nTiers), - getBasicDefifaLaunchData() - ); - for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); @@ -467,8 +459,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -500,18 +490,14 @@ contract DefifaGovernorTest is TestBaseWorkflow { assertEq(_governor.MAX_VOTING_POWER_TIER(), _governor.getVotes(_users[i], block.number - 1)); } - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); - address[] memory targets = new address[](1); - uint256[] memory values = new uint256[](1); - bytes[] memory calldatas = new bytes[](1); - // Generate the scorecards DefifaTierRedemptionWeight[] memory scorecards = new DefifaTierRedemptionWeight[](nTiers); @@ -522,17 +508,7 @@ contract DefifaGovernorTest is TestBaseWorkflow { } // Forward time so proposals can be created - uint256 _proposalId; - if (_useHelper) { - _proposalId = _governor.submitScorecards(scorecards); - } else { - targets[0] = address(_nft); - calldatas[0] = abi.encodeCall(_nft.setTierRedemptionWeights, scorecards); - - // Create the proposal - _proposalId = _governor.propose(targets, values, calldatas, 'Governance!'); - } - + uint256 _proposalId = _governor.submitScorecards(scorecards); // Forward time so voting becomes active vm.roll(block.number + _governor.votingDelay() + 1); // '_governor.votingDelay()' internally uses the timestamp and not the block number, so we have to modify it for the next assert @@ -548,30 +524,31 @@ contract DefifaGovernorTest is TestBaseWorkflow { _governor.castVote(_proposalId, 0); } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); - vm.roll(deployer.endOf(_projectId)); - deployer.queueNextPhaseOf(_projectId); + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); + // Forward the amount of blocks needed to reach the end (and round up) + vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1); vm.warp(block.timestamp + 1 weeks); // Execute the proposal vm.expectRevert('Governor: proposal not successful'); - if (_useHelper) { - _governor.ratifyScorecard(scorecards); - } else { - _governor.execute(targets, values, calldatas, keccak256('Governance!')); - } + _governor.ratifyScorecard(scorecards); } - function testSetRedemptionRates_fails_declined(bool _useHelper) external { + function testSetRedemptionRates_fails_declined() external { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + // Phase 1: minting + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); + for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); @@ -587,8 +564,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -620,18 +595,14 @@ contract DefifaGovernorTest is TestBaseWorkflow { assertEq(_governor.MAX_VOTING_POWER_TIER(), _governor.getVotes(_users[i], block.number - 1)); } - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); - address[] memory targets = new address[](1); - uint256[] memory values = new uint256[](1); - bytes[] memory calldatas = new bytes[](1); - // Generate the scorecards DefifaTierRedemptionWeight[] memory scorecards = new DefifaTierRedemptionWeight[](nTiers); @@ -642,17 +613,7 @@ contract DefifaGovernorTest is TestBaseWorkflow { } // Forward time so proposals can be created - uint256 _proposalId; - if (_useHelper) { - _proposalId = _governor.submitScorecards(scorecards); - } else { - targets[0] = address(_nft); - calldatas[0] = abi.encodeCall(_nft.setTierRedemptionWeights, scorecards); - - // Create the proposal - _proposalId = _governor.propose(targets, values, calldatas, 'Governance!'); - } - + uint256 _proposalId = _governor.submitScorecards(scorecards); // Forward time so voting becomes active vm.roll(block.number + _governor.votingDelay() + 1); // '_governor.votingDelay()' internally uses the timestamp and not the block number, so we have to modify it for the next assert @@ -668,19 +629,14 @@ contract DefifaGovernorTest is TestBaseWorkflow { _governor.castVote(_proposalId, 0); } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); vm.roll(deployer.endOf(_projectId)); - deployer.queueNextPhaseOf(_projectId); vm.warp(block.timestamp + 1 weeks); // Execute the proposal vm.expectRevert('Governor: proposal not successful'); - if (_useHelper) { - _governor.ratifyScorecard(scorecards); - } else { - _governor.execute(targets, values, calldatas, keccak256('Governance!')); - } + _governor.ratifyScorecard(scorecards); } function testSetRedemptionRatesAndRedeem_multipleTiers( @@ -699,11 +655,16 @@ contract DefifaGovernorTest is TestBaseWorkflow { address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + // Phase 1: minting + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); + for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); @@ -719,8 +680,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -748,12 +707,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { // Have a user mint and refund the tier mintAndRefund(_nft, _projectId, 1); - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Generate the scorecards @@ -790,10 +749,10 @@ contract DefifaGovernorTest is TestBaseWorkflow { _governor.castVote(_proposalId, 1); } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); - vm.roll(deployer.endOf(_projectId)); - deployer.queueNextPhaseOf(_projectId); + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); + // Forward the amount of blocks needed to reach the end (and round up) + vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1); vm.warp(block.timestamp + 1 weeks); _governor.ratifyScorecard(scorecards); @@ -859,11 +818,15 @@ contract DefifaGovernorTest is TestBaseWorkflow { address[] memory _users = new address[](nOfOtherTiers + nUsersWithWinningTier); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nOfOtherTiers + 1), // All users will buying the same tier - getBasicDefifaLaunchData() + defifaData ); + // Phase 1: minting + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + for (uint256 i = 0; i < nOfOtherTiers + nUsersWithWinningTier; i++) { // Generate a new address for each tier _users[i] = address(bytes20(keccak256(abi.encode('user', Strings.toString(i))))); @@ -883,8 +846,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -916,8 +877,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -946,12 +905,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { // Have a user mint and refund the tier mintAndRefund(_nft, _projectId, 1); - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Generate the scorecards @@ -996,9 +955,10 @@ contract DefifaGovernorTest is TestBaseWorkflow { } } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); - vm.roll(deployer.endOf(_projectId)); + // Phase 4: End + // Forward the amount of blocks needed to reach the end (and round up) + vm.roll(deployer.endOf(_projectId) - block.timestamp / 12 + 1); + vm.warp(deployer.endOf(_projectId)); deployer.queueNextPhaseOf(_projectId); vm.warp(block.timestamp + 1 weeks); @@ -1020,7 +980,8 @@ contract DefifaGovernorTest is TestBaseWorkflow { ); redemptionMetadata = abi.encode(bytes32(0), type(IJB721Delegate).interfaceId, redemptionId); } - + uint256 _expectedTierRedemption; + { // Calculate how much weight his tier has uint256 _tierWeight = _tier == nOfOtherTiers + 1 ? uint256(baseRedemptionWeight) + uint256(winningTierExtraWeight) @@ -1042,8 +1003,9 @@ contract DefifaGovernorTest is TestBaseWorkflow { }); // We calculate the expected output based on the given distribution and how much is in the pot - uint256 _expectedTierRedemption = (uint256(_users.length) * 1 ether * _tierWeight) / + _expectedTierRedemption = (uint256(_users.length) * 1 ether * _tierWeight) / totalWeight; + } { // If this is the winning tier then the amount is divided among the nUsersWithWinningTier if (_tier == nOfOtherTiers + 1) @@ -1084,8 +1046,8 @@ contract DefifaGovernorTest is TestBaseWorkflow { DefifaLaunchProjectData memory _launchData = DefifaLaunchProjectData({ projectMetadata: JBProjectMetadata({content: '', domain: 0}), mintDuration: _mintDuration, - start: _launchProjectAt + uint48(_mintDuration), - tradeDeadline: _launchProjectAt + uint48(_mintDuration) + uint48(_inBetweenMintAndFifa), + start: _launchProjectAt + uint48(_mintDuration) + _inBetweenMintAndFifa, + refundPeriodDuration: _inBetweenMintAndFifa, end: _end, holdFees: false, splits: new JBSplit[](0), @@ -1140,10 +1102,15 @@ contract DefifaGovernorTest is TestBaseWorkflow { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + + // Phase 1: Mint + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier @@ -1160,8 +1127,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -1193,13 +1158,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { assertEq(_governor.MAX_VOTING_POWER_TIER(), _governor.getVotes(_users[i], block.number - 1)); } - // Phase 2 - vm.roll(block.number + 5); - vm.warp(block.timestamp + 600); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - vm.roll(block.number + 5); - vm.warp(block.timestamp + 600); + // Right at the end of Phase 2 + vm.warp(defifaData.start - 1); vm.expectRevert(abi.encodeWithSignature('PHASE_ALREADY_QUEUED()')); deployer.queueNextPhaseOf(_projectId); } @@ -1208,10 +1172,15 @@ contract DefifaGovernorTest is TestBaseWorkflow { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + + // Phase 1: Mint + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier @@ -1228,8 +1197,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -1261,12 +1228,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { assertEq(_governor.MAX_VOTING_POWER_TIER(), _governor.getVotes(_users[i], block.number - 1)); } - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Generate the scorecards @@ -1305,10 +1272,15 @@ contract DefifaGovernorTest is TestBaseWorkflow { uint8 nTiers = 10; address[] memory _users = new address[](nTiers); + DefifaLaunchProjectData memory defifaData = getBasicDefifaLaunchData(); (uint256 _projectId, DefifaDelegate _nft, DefifaGovernor _governor) = createDefifaProject( uint256(nTiers), - getBasicDefifaLaunchData() + defifaData ); + + // Phase 1: Mint + vm.warp(defifaData.start - defifaData.mintDuration - defifaData.refundPeriodDuration); + deployer.queueNextPhaseOf(_projectId); for (uint256 i = 0; i < nTiers; i++) { // Generate a new address for each tier @@ -1325,8 +1297,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -1358,12 +1328,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { assertEq(_governor.MAX_VOTING_POWER_TIER(), _governor.getVotes(_users[i], block.number - 1)); } - // Phase 2 - vm.warp(block.timestamp + 1 days); + // Phase 2: Redeem + vm.warp(block.timestamp + defifaData.mintDuration); deployer.queueNextPhaseOf(_projectId); - // Phase 3 - vm.warp(block.timestamp + 1 days); + // Phase 3: Start + vm.warp(defifaData.start + 1); deployer.queueNextPhaseOf(_projectId); // Generate the scorecards @@ -1396,10 +1366,9 @@ contract DefifaGovernorTest is TestBaseWorkflow { _governor.castVote(_proposalId, 1); } - // Phase 4 - vm.warp(block.timestamp + 1 weeks); + // Phase 4: End + vm.warp(deployer.endOf(_projectId)); vm.roll(deployer.endOf(_projectId)); - deployer.queueNextPhaseOf(_projectId); vm.warp(block.timestamp + 1 weeks); // Execute the proposal @@ -1412,9 +1381,10 @@ contract DefifaGovernorTest is TestBaseWorkflow { DefifaLaunchProjectData({ projectMetadata: JBProjectMetadata({content: '', domain: 0}), mintDuration: 1 days, - start: uint48(block.timestamp + 1 days), - tradeDeadline: uint48(block.timestamp + 1 days), - end: uint48(block.timestamp + 1 weeks), + start: uint48(block.timestamp + 3 days), + refundPeriodDuration: 1 days, + //tradeDeadline: uint48(block.timestamp + 1 days), + end: uint48(block.timestamp + 3 days + 1 weeks), holdFees: false, splits: new JBSplit[](0), distributionLimit: 0, @@ -1453,8 +1423,12 @@ contract DefifaGovernorTest is TestBaseWorkflow { // Get a reference to the latest configured funding cycle's data source, which should be the delegate that was deployed and attached to the project. JBFundingCycle memory _fc = _jbFundingCycleStore.currentOf(projectId); + if (_fc.dataSource() == address(0)) { + _fc = _jbFundingCycleStore.queuedOf(projectId); + } + // Deploy the governor - governor = new DefifaGovernor(DefifaDelegate(_fc.dataSource()), defifaLaunchData.tradeDeadline); + governor = new DefifaGovernor(DefifaDelegate(_fc.dataSource()), uint48(block.timestamp + 10 days + 1 weeks)); // making sure the addresses match assertEq(address(governor), _owner); @@ -1482,8 +1456,6 @@ contract DefifaGovernorTest is TestBaseWorkflow { bytes32(0), type(IJB721Delegate).interfaceId, false, - false, - false, rawMetadata ); @@ -1600,6 +1572,7 @@ contract DefifaGovernorTest is TestBaseWorkflow { reservedTokenBeneficiary: reserveBeneficiary, store: new JBTiered721DelegateStore(), flags: JBTiered721Flags({ + preventOverspending: true, lockReservedTokenChanges: false, lockVotingUnitChanges: false, lockManualMintingChanges: false diff --git a/contracts/interfaces/IDefifaDeployer.sol b/contracts/interfaces/IDefifaDeployer.sol index 4063978..7924442 100644 --- a/contracts/interfaces/IDefifaDeployer.sol +++ b/contracts/interfaces/IDefifaDeployer.sol @@ -24,7 +24,7 @@ interface IDefifaDeployer { function startOf(uint256 _gameId) external view returns (uint256); - function tradeDeadlineOf(uint256 _gameId) external view returns (uint256); + function refundPeriodDurationOf(uint256 _gameId) external view returns (uint256); function endOf(uint256 _gameId) external view returns (uint256); diff --git a/contracts/interfaces/IDefifaGovernor.sol b/contracts/interfaces/IDefifaGovernor.sol index 52f9c3e..965449e 100644 --- a/contracts/interfaces/IDefifaGovernor.sol +++ b/contracts/interfaces/IDefifaGovernor.sol @@ -15,6 +15,10 @@ interface IDefifaGovernor { external returns (uint256); + function attestToScorecard(uint256 _scorecardId) external; + + function attestToScorecardWithReasonAndParams(uint256 _scorecardId, bytes memory params) external; + function ratifyScorecard(DefifaTierRedemptionWeight[] calldata _tierWeights) external returns (uint256); diff --git a/contracts/interfaces/IGovernor.sol b/contracts/interfaces/IGovernor.sol new file mode 100644 index 0000000..6bd6009 --- /dev/null +++ b/contracts/interfaces/IGovernor.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.2) (governance/IGovernor.sol) + +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; + +/** + * @dev Interface of the {Governor} core. + * + * _Available since v4.3._ + */ +abstract contract IGovernor is IERC165 { + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + Executed + } + + /** + * @dev Emitted when a proposal is created. + */ + event ProposalCreated( + uint256 proposalId, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 startBlock, + uint256 endBlock, + string description + ); + + /** + * @dev Emitted when a proposal is canceled. + */ + event ProposalCanceled(uint256 proposalId); + + /** + * @dev Emitted when a proposal is executed. + */ + event ProposalExecuted(uint256 proposalId); + + /** + * @dev Emitted when a vote is cast without params. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + */ + event VoteCast( + address indexed voter, + uint256 proposalId, + uint8 support, + uint256 weight, + string reason + ); + + /** + * @dev Emitted when a vote is cast with params. + * + * Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + * `params` are additional encoded parameters. Their intepepretation also depends on the voting module used. + */ + event VoteCastWithParams( + address indexed voter, + uint256 proposalId, + uint8 support, + uint256 weight, + string reason, + bytes params + ); + + /** + * @notice module:core + * @dev Name of the governor instance (used in building the ERC712 domain separator). + */ + function name() public view virtual returns (string memory); + + /** + * @notice module:core + * @dev Version of the governor instance (used in building the ERC712 domain separator). Default: "1" + */ + function version() public view virtual returns (string memory); + + /** + * @notice module:voting + * @dev A description of the possible `support` values for {castVote} and the way these votes are counted, meant to + * be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of + * key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + * + * There are 2 standard keys: `support` and `quorum`. + * + * - `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. + * - `quorum=bravo` means that only For votes are counted towards quorum. + * - `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + * + * If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique + * name that describes the behavior. For example: + * + * - `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. + * - `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + * + * NOTE: The string can be decoded by the standard + * https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams[`URLSearchParams`] + * JavaScript class. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual returns (string memory); + + /** + * @notice module:core + * @dev Hashing function used to (re)build the proposal id from the proposal details.. + */ + function hashProposal( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public pure virtual returns (uint256); + + /** + * @notice module:core + * @dev Current state of a proposal, following Compound's convention + */ + function state(uint256 proposalId) public view virtual returns (ProposalState); + + /** + * @notice module:core + * @dev Block number used to retrieve user's votes and quorum. As per Compound's Comp and OpenZeppelin's + * ERC20Votes, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the + * beginning of the following block. + */ + function proposalSnapshot(uint256 proposalId) public view virtual returns (uint256); + + /** + * @notice module:core + * @dev Block number at which votes close. Votes close at the end of this block, so it is possible to cast a vote + * during this block. + */ + function proposalDeadline(uint256 proposalId) public view virtual returns (uint256); + + /** + * @notice module:user-config + * @dev Delay, in number of block, between the proposal is created and the vote starts. This can be increassed to + * leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + */ + function votingDelay() public view virtual returns (uint256); + + /** + * @notice module:user-config + * @dev Delay, in number of blocks, between the vote start and vote ends. + * + * NOTE: The {votingDelay} can delay the start of the vote. This must be considered when setting the voting + * duration compared to the voting delay. + */ + function votingPeriod() public view virtual returns (uint256); + + /** + * @notice module:user-config + * @dev Minimum number of cast voted required for a proposal to be successful. + * + * Note: The `blockNumber` parameter corresponds to the snapshot used for counting vote. This allows to scale the + * quorum depending on values such as the totalSupply of a token at this block (see {ERC20Votes}). + */ + function quorum(uint256 blockNumber) public view virtual returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `blockNumber`. + * + * Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or + * multiple), {ERC20Votes} tokens. + */ + function getVotes(address account, uint256 blockNumber) public view virtual returns (uint256); + + /** + * @notice module:reputation + * @dev Voting power of an `account` at a specific `blockNumber` given additional encoded parameters. + */ + function getVotesWithParams( + address account, + uint256 blockNumber, + bytes memory params + ) public view virtual returns (uint256); + + /** + * @notice module:voting + * @dev Returns weither `account` has cast a vote on `proposalId`. + */ + function hasVoted(uint256 proposalId, address account) public view virtual returns (bool); +} diff --git a/contracts/scripts/Deploy.s.sol b/contracts/scripts/Deploy.s.sol index 904ff4a..7069d9d 100644 --- a/contracts/scripts/Deploy.s.sol +++ b/contracts/scripts/Deploy.s.sol @@ -8,25 +8,32 @@ import '../DefifaGovernor.sol'; import 'forge-std/Script.sol'; contract DeployMainnet is Script { - function run() external { - vm.startBroadcast(); - - // V3 goerli controller. - IJBController controller = IJBController(0xFFdD70C318915879d5192e8a0dcbFcB0285b3C98); - // goerli 721 store. - IJBTiered721DelegateStore store = IJBTiered721DelegateStore( - 0xffB2Cd8519439A7ddcf2C933caedd938053067D2 - ); - // V3 goerli Payment terminal. - IJBPaymentTerminal terminal = IJBPaymentTerminal(0x594Cb208b5BB48db1bcbC9354d1694998864ec63); + // V3 mainnet controller. + IJBController controller = IJBController(0xFFdD70C318915879d5192e8a0dcbFcB0285b3C98); + // mainnet 721 store. + IJBTiered721DelegateStore store = + IJBTiered721DelegateStore(0xffB2Cd8519439A7ddcf2C933caedd938053067D2); + // V3 goerli Payment terminal. + IJBPaymentTerminal terminal = IJBPaymentTerminal(0x594Cb208b5BB48db1bcbC9354d1694998864ec63); + + address _defifaBallcats = 0x11834239698c7336EF232C00a2A9926d3375DF9D; + // Game params. + uint48 _start = 1673672400; // 12am EST, Jan 14. + uint48 _mintDuration = 432000; // 5 days. + uint48 _refundPeriodDuration = 86400; // 1 day. + uint48 _end = 1676178000; // 12am EST, Feb 12. + uint80 _price = 0.1 ether; + // We don't have to do this effenciently since this contract never gets deployed, its just used to build the broadcast txs + string _name = 'Defifa: NFL Playoffs 2023'; + string _symbol = 'DEFIFA NFL 2023'; + string _contractUri = 'QmaK1Hib3Umokija4bRwoPdxHgGY3unRreeeLoJss3vw4Y'; + string _projectMetadataUri = 'QmT7VFuF7cPMwMnqh3YruuYcRk2tKMb2xcXSbK2wV82Hdy'; + uint16 _reserved = 9; // 1 reserved NFT mintable to reserved beneficiary for every 9 NFTs minted outwardly. Inclusive, so 1 reserved can be minted as soon as the first token is minted outwardly. - address _defifaBallcats = 0x11834239698c7336EF232C00a2A9926d3375DF9D; - // Game params. - uint48 _start = 1669024800; // 2am PST, Nov 21. - uint48 _mintDuration = 1036800; // 12 days. - uint48 _tradeDeadline = 1670598000; // 7am, Dec 9. - uint48 _end = 1671375600 + 604800; // 7 days after the finals. Dec 25, 7am PST. + function run() external { + vm.startBroadcast(); + JB721TierParams[] memory _tiers = new JB721TierParams[](32); bytes32[] memory _teamEncodedIPFSUris = new bytes32[](32); @@ -44,32 +51,14 @@ contract DeployMainnet is Script { _teamEncodedIPFSUris[11] = 0x895646f615fc45358d894014f3d8c15ad020323cf57c6624464bd11a2815a480; _teamEncodedIPFSUris[12] = 0x6388190babc4fd7ad390b545dc0eaf01222355583ce0a5d5496c2bb6f118039b; _teamEncodedIPFSUris[13] = 0x33e06853378f58d42ceb8ab266d25656eded385f82a777ca563c5e38d5241a83; - _teamEncodedIPFSUris[14] = 0x2f4275c8dac582d4718d41315e8406e502f9edc24718508008c9be2120c29718; - _teamEncodedIPFSUris[15] = 0x6ff53e06711936e10bd3ca5786ca9c47cbd42f5b878289c350fa405513cbba39; - _teamEncodedIPFSUris[16] = 0xdaae0d745afe8943fffe0a733b7da605db462162bf0895d069daf8137ded8b0f; - _teamEncodedIPFSUris[17] = 0xba245717d6e4293217d7e94f19b5aef1a84f6902bced1332eb8607aaddd43db2; - _teamEncodedIPFSUris[18] = 0x42a65ae98f0d43856f8d75d645dbe964750f42415e038264813c95e1fd977839; - _teamEncodedIPFSUris[19] = 0x755651ad1de6c43ed4525d761a39c4521445f67c72010544cdbe156928954d2d; - _teamEncodedIPFSUris[20] = 0x6f880ca3d58e8f48a8709d097ead5dda0969f525efacadba9d707fd68578e45f; - _teamEncodedIPFSUris[21] = 0x9ca1e5354a134ff50d36121ccc511ac6a1c42797512aceea4d1e14811359c063; - _teamEncodedIPFSUris[22] = 0x91b2e30c3f239b25f75ea44ca36ddaa616738bd88ecedd67691f22bffe401727; - _teamEncodedIPFSUris[23] = 0xdfb13aa5ab8c607ca1772385ece7dc4213a06ae0e7a09058592556e19b4ef6eb; - _teamEncodedIPFSUris[24] = 0xb587a257c1a625c9b28f3f2c841fe99fdad97d111f94a3f238e1a8f224b48e37; - _teamEncodedIPFSUris[25] = 0xab1d428c4b2594b8232abff414ad259f6748dbe13302dd7d4758c12a79b9ffbf; - _teamEncodedIPFSUris[26] = 0x5756ead1507fa2210f66dd2bf9ed722b27929ccd90fade1a234447dc01cc8ef0; - _teamEncodedIPFSUris[27] = 0x032083c1a27f9e5fed3236e357dca8e7bc205681a9942465adfeca40efcc05a5; - _teamEncodedIPFSUris[28] = 0x60c52313561051352c1d98d9652815b555f95c0ee250b9afd1acddf95dec9578; - _teamEncodedIPFSUris[29] = 0x5b88630af3d5ad16f18c2c8528a41b925233c61e40c8934d7981e21881b062b5; - _teamEncodedIPFSUris[30] = 0x1ba892a008a164359a2b70ef1ab046d90fa3af4ce4815c00cd07a2a9237e9808; - _teamEncodedIPFSUris[31] = 0x193bb548bbf52cf76e00185e24c0eada8f758dfef1927cfaa202207e4beb6cec; for (uint256 _i; _i < 32; ) { _tiers[_i] = JB721TierParams({ - contributionFloor: 0.022 ether, + contributionFloor: _price, lockedUntil: 0, initialQuantity: 1_000_000_000 - 1, // max votingUnits: 1, - reservedRate: 9, + reservedRate: _reserved, reservedTokenBeneficiary: _defifaBallcats, encodedIPFSUri: _teamEncodedIPFSUris[_i], allowManualMint: false, @@ -83,10 +72,10 @@ contract DeployMainnet is Script { } DefifaDelegateData memory _delegateData = DefifaDelegateData({ - name: 'Defifa: FIFA World Cup 2022', - symbol: 'DEFIFA', + name: _name, + symbol: _symbol, baseUri: 'ipfs://', - contractUri: 'QmaK1Hib3Umokija4bRwoPdxHgGY3unRreeeLoJss3vw4Y', + contractUri: _contractUri, tiers: _tiers, store: store, // Set owner will be set to the Governor later on in this script. @@ -94,13 +83,10 @@ contract DeployMainnet is Script { }); DefifaLaunchProjectData memory _launchProjectData = DefifaLaunchProjectData({ - projectMetadata: JBProjectMetadata({ - content: 'QmT7VFuF7cPMwMnqh3YruuYcRk2tKMb2xcXSbK2wV82Hdy', - domain: 0 - }), + projectMetadata: JBProjectMetadata({content: _projectMetadataUri, domain: 0}), mintDuration: _mintDuration, start: _start, - tradeDeadline: _tradeDeadline, + refundPeriodDuration: _refundPeriodDuration, end: _end, holdFees: false, splits: new JBSplit[](0), @@ -149,9 +135,6 @@ contract DeployMainnet is Script { } contract DeployGoerli is Script { - function run() external { - vm.startBroadcast(); - // V3 goerli controller. IJBController controller = IJBController(0x7Cb86D43B665196BC719b6974D320bf674AFb395); // goerli 721 store. @@ -161,13 +144,23 @@ contract DeployGoerli is Script { // V3 goerli Payment terminal. IJBPaymentTerminal terminal = IJBPaymentTerminal(0x55d4dfb578daA4d60380995ffF7a706471d7c719); - address _defifaBallcats = 0x11834239698c7336EF232C00a2A9926d3375DF9D; - // Game params. - uint48 _start = 1669024800; // 2am PST, Nov 21. - uint48 _mintDuration = 1036800; // 12 days. - uint48 _tradeDeadline = 1670598000; // 7am, Dec 9. - uint48 _end = 1671375600 + 604800; // 7 days after the finals. Dec 25, 7am PST. + address _defifaBallcats = 0x11834239698c7336EF232C00a2A9926d3375DF9D; + // Game params. + uint48 _start = 1673672400; // 12am EST, Jan 14. + uint48 _mintDuration = 432000; // 5 days. + uint48 _refundPeriodDuration = 86400; // 1 day. + uint48 _end = 1676178000; // 12am EST, Feb 12. + uint80 _price = 0.1 ether; + // We don't have to do this effenciently since this contract never gets deployed, its just used to build the broadcast txs + string _name = 'Defifa: NFL Playoffs 2023'; + string _symbol = 'DEFIFA NFL 2023'; + string _contractUri = 'QmaK1Hib3Umokija4bRwoPdxHgGY3unRreeeLoJss3vw4Y'; + string _projectMetadataUri = 'QmT7VFuF7cPMwMnqh3YruuYcRk2tKMb2xcXSbK2wV82Hdy'; + uint16 _reserved = 9; // 1 reserved NFT mintable to reserved beneficiary for every 9 NFTs minted outwardly. Inclusive, so 1 reserved can be minted as soon as the first token is minted outwardly. + function run() external { + vm.startBroadcast(); + JB721TierParams[] memory _tiers = new JB721TierParams[](32); bytes32[] memory _teamEncodedIPFSUris = new bytes32[](32); @@ -185,32 +178,14 @@ contract DeployGoerli is Script { _teamEncodedIPFSUris[11] = 0x895646f615fc45358d894014f3d8c15ad020323cf57c6624464bd11a2815a480; _teamEncodedIPFSUris[12] = 0x6388190babc4fd7ad390b545dc0eaf01222355583ce0a5d5496c2bb6f118039b; _teamEncodedIPFSUris[13] = 0x33e06853378f58d42ceb8ab266d25656eded385f82a777ca563c5e38d5241a83; - _teamEncodedIPFSUris[14] = 0x2f4275c8dac582d4718d41315e8406e502f9edc24718508008c9be2120c29718; - _teamEncodedIPFSUris[15] = 0x6ff53e06711936e10bd3ca5786ca9c47cbd42f5b878289c350fa405513cbba39; - _teamEncodedIPFSUris[16] = 0xdaae0d745afe8943fffe0a733b7da605db462162bf0895d069daf8137ded8b0f; - _teamEncodedIPFSUris[17] = 0xba245717d6e4293217d7e94f19b5aef1a84f6902bced1332eb8607aaddd43db2; - _teamEncodedIPFSUris[18] = 0x42a65ae98f0d43856f8d75d645dbe964750f42415e038264813c95e1fd977839; - _teamEncodedIPFSUris[19] = 0x755651ad1de6c43ed4525d761a39c4521445f67c72010544cdbe156928954d2d; - _teamEncodedIPFSUris[20] = 0x6f880ca3d58e8f48a8709d097ead5dda0969f525efacadba9d707fd68578e45f; - _teamEncodedIPFSUris[21] = 0x9ca1e5354a134ff50d36121ccc511ac6a1c42797512aceea4d1e14811359c063; - _teamEncodedIPFSUris[22] = 0x91b2e30c3f239b25f75ea44ca36ddaa616738bd88ecedd67691f22bffe401727; - _teamEncodedIPFSUris[23] = 0xdfb13aa5ab8c607ca1772385ece7dc4213a06ae0e7a09058592556e19b4ef6eb; - _teamEncodedIPFSUris[24] = 0xb587a257c1a625c9b28f3f2c841fe99fdad97d111f94a3f238e1a8f224b48e37; - _teamEncodedIPFSUris[25] = 0xab1d428c4b2594b8232abff414ad259f6748dbe13302dd7d4758c12a79b9ffbf; - _teamEncodedIPFSUris[26] = 0x5756ead1507fa2210f66dd2bf9ed722b27929ccd90fade1a234447dc01cc8ef0; - _teamEncodedIPFSUris[27] = 0x032083c1a27f9e5fed3236e357dca8e7bc205681a9942465adfeca40efcc05a5; - _teamEncodedIPFSUris[28] = 0x60c52313561051352c1d98d9652815b555f95c0ee250b9afd1acddf95dec9578; - _teamEncodedIPFSUris[29] = 0x5b88630af3d5ad16f18c2c8528a41b925233c61e40c8934d7981e21881b062b5; - _teamEncodedIPFSUris[30] = 0x1ba892a008a164359a2b70ef1ab046d90fa3af4ce4815c00cd07a2a9237e9808; - _teamEncodedIPFSUris[31] = 0x193bb548bbf52cf76e00185e24c0eada8f758dfef1927cfaa202207e4beb6cec; for (uint256 _i; _i < 32; ) { _tiers[_i] = JB721TierParams({ - contributionFloor: 0.022 ether, + contributionFloor: _price, lockedUntil: 0, initialQuantity: 1_000_000_000 - 1, // max votingUnits: 1, - reservedRate: 9, + reservedRate: _reserved, reservedTokenBeneficiary: _defifaBallcats, encodedIPFSUri: _teamEncodedIPFSUris[_i], allowManualMint: false, @@ -224,10 +199,10 @@ contract DeployGoerli is Script { } DefifaDelegateData memory _delegateData = DefifaDelegateData({ - name: 'Defifa: FIFA World Cup 2022', - symbol: 'DEFIFA', + name: _name, + symbol: _symbol, baseUri: 'ipfs://', - contractUri: 'QmaK1Hib3Umokija4bRwoPdxHgGY3unRreeeLoJss3vw4Y', + contractUri: _contractUri, tiers: _tiers, store: store, // Set owner will be set to the Governor later on in this script. @@ -235,13 +210,10 @@ contract DeployGoerli is Script { }); DefifaLaunchProjectData memory _launchProjectData = DefifaLaunchProjectData({ - projectMetadata: JBProjectMetadata({ - content: 'QmT7VFuF7cPMwMnqh3YruuYcRk2tKMb2xcXSbK2wV82Hdy', - domain: 0 - }), + projectMetadata: JBProjectMetadata({content: _projectMetadataUri, domain: 0}), mintDuration: _mintDuration, start: _start, - tradeDeadline: _tradeDeadline, + refundPeriodDuration: _refundPeriodDuration, end: _end, holdFees: false, splits: new JBSplit[](0), diff --git a/contracts/structs/DefifaLaunchProjectData.sol b/contracts/structs/DefifaLaunchProjectData.sol index 0ce23dc..16574a6 100644 --- a/contracts/structs/DefifaLaunchProjectData.sol +++ b/contracts/structs/DefifaLaunchProjectData.sol @@ -9,8 +9,8 @@ import './DefifaTimeData.sol'; /** @member projectMetadata Metadata to associate with the project within a particular domain. This can be updated any time by the owner of the project. @member mintDuration The duration of the game's first phase. + @member refundPeriodDuration The time between the mint period and the start time where mint's are no longer open but refunds are still allowed. @member start The timestamp at which the game should start. - @member tradeDeadline The timestamp at which the game's trade deadline should begin. @member end The timestamp at which the game should end. @member holdFees A flag indicating if fees should be held when distributing funds during the second funding cycle. @member splits Splits to distribute funds between during the game's second phase. @@ -20,8 +20,8 @@ import './DefifaTimeData.sol'; struct DefifaLaunchProjectData { JBProjectMetadata projectMetadata; uint48 mintDuration; + uint48 refundPeriodDuration; uint48 start; - uint48 tradeDeadline; uint48 end; bool holdFees; JBSplit[] splits; diff --git a/contracts/structs/DefifaTimeData.sol b/contracts/structs/DefifaTimeData.sol index 40395b6..0c7ccd7 100644 --- a/contracts/structs/DefifaTimeData.sol +++ b/contracts/structs/DefifaTimeData.sol @@ -5,13 +5,13 @@ import '@jbx-protocol/juice-721-delegate/contracts/structs/JB721TierParams.sol'; /** @member mintDuration The amount of time mints will be open for before the game's start. + @member refundPeriodDuration The time between the mint period and the start time where mint's are no longer open but refunds are still allowed. @member start The timestamp at which the game should start. - @member tradeDeadline The timestamp at which the game's trade deadline should begin. @member end The timestamp at which the game should end. */ struct DefifaTimeData { uint48 mintDuration; + uint48 refundPeriodDuration; uint48 start; - uint48 tradeDeadline; uint48 end; } diff --git a/package.json b/package.json index 702462e..b982daf 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "version": "6.0.0", "license": "MIT", "dependencies": { - "@jbx-protocol/juice-721-delegate": "^3.0.2", + "@jbx-protocol/juice-721-delegate": "^3.0.3", "@openzeppelin/contracts": "^4.7.3", "@prb/math": "^2.5.0" }, diff --git a/yarn.lock b/yarn.lock index cca439e..5ed6d47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -51,10 +51,10 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@jbx-protocol/juice-721-delegate@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@jbx-protocol/juice-721-delegate/-/juice-721-delegate-3.0.2.tgz#da6de37cbb024cc3799f7221ec206e1a11e115e7" - integrity sha512-d0FoHLCQt5fHKuu7tUs1DelAQgYFAyA6FWPOie6kpQqoVNilAtU5njGv+/eATJSVhnIPriXI5cSmssekob1j4g== +"@jbx-protocol/juice-721-delegate@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@jbx-protocol/juice-721-delegate/-/juice-721-delegate-3.0.3.tgz#ede21a641267dd7c6c3338206221eac168edad56" + integrity sha512-0W/tJcjEmETCLJ6gSDl0ROJ1wp3fJjVtGoA7KfbLNUpKEC+m4I6BwlrYsuIBhWSqvBaYleTqL87lnd4jYEIbaA== dependencies: "@jbx-protocol/juice-contracts-v3" "^2.0.0" "@openzeppelin/contracts" "^4.6.0"