From fc365c083f87bc65f984a4ec618826b1c98fcc72 Mon Sep 17 00:00:00 2001 From: neokry Date: Mon, 30 Oct 2023 14:20:59 +0700 Subject: [PATCH] Rework rewards to set at deploy time --- script/DeployV2Core.s.sol | 12 +-- src/auction/Auction.sol | 37 ++++--- src/auction/IAuction.sol | 5 +- src/manager/IManager.sol | 4 - src/manager/Manager.sol | 29 ++---- src/manager/storage/ManagerStorageV2.sol | 12 --- src/manager/types/ManagerTypesV2.sol | 17 ---- test/Auction.t.sol | 120 ++++++++++++++++++++++- test/utils/NounsBuilderTest.sol | 6 +- 9 files changed, 164 insertions(+), 78 deletions(-) delete mode 100644 src/manager/storage/ManagerStorageV2.sol delete mode 100644 src/manager/types/ManagerTypesV2.sol diff --git a/script/DeployV2Core.s.sol b/script/DeployV2Core.s.sol index b0e0e79..e159a1a 100644 --- a/script/DeployV2Core.s.sol +++ b/script/DeployV2Core.s.sol @@ -31,6 +31,9 @@ contract DeployContracts is Script { address deployerAddress = vm.addr(key); + uint16 BUILDER_REWARDS = chainID == 1 || chainID == 5 ? 0 : 250; + uint16 REFERRAL_REWARDS = chainID == 1 || chainID == 5 ? 0 : 250; + console2.log("~~~~~~~~~~ CHAIN ID ~~~~~~~~~~~"); console2.log(chainID); @@ -38,14 +41,11 @@ contract DeployContracts is Script { console2.log(deployerAddress); vm.startBroadcast(deployerAddress); - // Deploy root manager implementation + proxy - address managerImpl0 = address(new Manager(address(0), address(0), address(0), address(0), address(0))); + address managerImpl0 = address(new Manager(address(0), address(0), address(0), address(0), address(0), address(0))); Manager manager = Manager(address(new ERC1967Proxy(managerImpl0, abi.encodeWithSignature("initialize(address)", deployerAddress)))); - address rewards = _getKey("ProtocolRewards"); - // Deploy token implementation address tokenImpl = address(new Token(address(manager))); @@ -53,7 +53,7 @@ contract DeployContracts is Script { address metadataRendererImpl = address(new MetadataRenderer(address(manager))); // Deploy auction house implementation - address auctionImpl = address(new Auction(address(manager), address(rewards), weth)); + address auctionImpl = address(new Auction(address(manager), _getKey("ProtocolRewards"), weth, BUILDER_REWARDS, REFERRAL_REWARDS)); // Deploy treasury implementation address treasuryImpl = address(new Treasury(address(manager))); @@ -61,7 +61,7 @@ contract DeployContracts is Script { // Deploy governor implementation address governorImpl = address(new Governor(address(manager))); - address managerImpl = address(new Manager(tokenImpl, metadataRendererImpl, auctionImpl, treasuryImpl, governorImpl)); + address managerImpl = address(new Manager(tokenImpl, metadataRendererImpl, auctionImpl, treasuryImpl, governorImpl, _getKey("BuilderDAO"))); manager.upgradeTo(managerImpl); diff --git a/src/auction/Auction.sol b/src/auction/Auction.sol index 7a83125..6da44de 100644 --- a/src/auction/Auction.sol +++ b/src/auction/Auction.sol @@ -11,7 +11,6 @@ import { AuctionStorageV1 } from "./storage/AuctionStorageV1.sol"; import { Token } from "../token/Token.sol"; import { AuctionStorageV2 } from "./storage/AuctionStorageV2.sol"; import { Manager } from "../manager/Manager.sol"; -import { ManagerTypesV2 } from "../manager/types/ManagerTypesV2.sol"; import { IAuction } from "./IAuction.sol"; import { IWETH } from "../lib/interfaces/IWETH.sol"; import { IProtocolRewards } from "../lib/interfaces/IProtocolRewards.sol"; @@ -34,7 +33,7 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, uint256 private constant BPS_PER_100_PERCENT = 10_000; /// @notice The maximum rewards percentage - uint256 private constant MAX_FOUNDER_REWARDS_BPS = 3_000; + uint256 private constant MAX_FOUNDER_REWARD_BPS = 3_000; /// /// /// IMMUTABLES /// @@ -55,6 +54,12 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, /// @notice The rewards manager IProtocolRewards private immutable rewardsManager; + /// @notice The builder reward BPS as a percent of settled auction amount + uint16 private immutable builderRewardsBPS; + + /// @notice The referral reward BPS as a percent of settled auction amount + uint16 private immutable referralRewardsBPS; + /// /// /// CONSTRUCTOR /// /// /// @@ -65,11 +70,15 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, constructor( address _manager, address _rewardsManager, - address _weth + address _weth, + uint16 _builderRewardsBPS, + uint16 _referralRewardsBPS ) payable initializer { manager = Manager(_manager); rewardsManager = IProtocolRewards(_rewardsManager); WETH = _weth; + builderRewardsBPS = _builderRewardsBPS; + referralRewardsBPS = _referralRewardsBPS; } /// /// @@ -97,7 +106,10 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, if (msg.sender != address(manager)) revert ONLY_MANAGER(); // Ensure the founder reward is not more than max - if (_founderRewardBps > MAX_FOUNDER_REWARDS_BPS) revert INVALID_REWARDS_CONFIG(); + if (_founderRewardBps > MAX_FOUNDER_REWARD_BPS) revert INVALID_REWARDS_BPS(); + + // Ensure the recipient is set if the reward is greater than 0 + if (_founderRewardBps > 0 && _founderRewardRecipient == address(0)) revert INVALID_REWARDS_RECIPIENT(); // Initialize the reentrancy guard __ReentrancyGuard_init(); @@ -430,8 +442,11 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, /// @notice Updates the founder reward recipent address /// @param reward The new founder reward settings function setFounderReward(FounderReward calldata reward) external onlyOwner whenPaused { - // Ensure the reward is not more than max - if (reward.percentBps > MAX_FOUNDER_REWARDS_BPS) revert INVALID_REWARDS_CONFIG(); + // Ensure the founder reward is not more than max + if (reward.percentBps > MAX_FOUNDER_REWARD_BPS) revert INVALID_REWARDS_BPS(); + + // Ensure the recipient is set if the reward is greater than 0 + if (reward.percentBps > 0 && reward.recipient == address(0)) revert INVALID_REWARDS_RECIPIENT(); // Update the founder reward settings founderReward = reward; @@ -452,11 +467,11 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, uint256 _finalBidAmount, uint256 _founderRewardBps ) internal view returns (RewardSplits memory split) { - // Get global reward settings from manager - (address builderRecipient, uint256 referralBps, uint256 builderBps) = manager.rewards(); + // Get global builder recipient from manager + address builderRecipient = manager.builderRewardsRecipient(); // Calculate the total rewards percentage - uint256 totalBPS = _founderRewardBps + referralBps + builderBps; + uint256 totalBPS = _founderRewardBps + referralRewardsBPS + builderRewardsBPS; // Verify percentage is not more than 100 if (totalBPS >= BPS_PER_100_PERCENT) { @@ -475,8 +490,8 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, // Calculate reward splits split.amounts = new uint256[](3); split.amounts[0] = (_finalBidAmount * _founderRewardBps) / BPS_PER_100_PERCENT; - split.amounts[1] = (_finalBidAmount * referralBps) / BPS_PER_100_PERCENT; - split.amounts[2] = (_finalBidAmount * builderBps) / BPS_PER_100_PERCENT; + split.amounts[1] = (_finalBidAmount * referralRewardsBPS) / BPS_PER_100_PERCENT; + split.amounts[2] = (_finalBidAmount * builderRewardsBPS) / BPS_PER_100_PERCENT; // Leave reasons empty split.reasons = new bytes4[](3); diff --git a/src/auction/IAuction.sol b/src/auction/IAuction.sol index a0c540e..5dcf4c8 100644 --- a/src/auction/IAuction.sol +++ b/src/auction/IAuction.sol @@ -95,7 +95,10 @@ interface IAuction is IUUPS, IOwnable, IPausable { error AUCTION_CREATE_FAILED_TO_LAUNCH(); /// @dev Reverts if caller is not the token owner - error INVALID_REWARDS_CONFIG(); + error INVALID_REWARDS_BPS(); + + /// @dev Reverts if caller is not the token owner + error INVALID_REWARDS_RECIPIENT(); /// @dev Thrown if the rewards total is greater than 100% error INVALID_REWARD_TOTAL(); diff --git a/src/manager/IManager.sol b/src/manager/IManager.sol index 4a49d24..173bed2 100644 --- a/src/manager/IManager.sol +++ b/src/manager/IManager.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.16; import { IUUPS } from "../lib/interfaces/IUUPS.sol"; import { IOwnable } from "../lib/interfaces/IOwnable.sol"; -import { ManagerTypesV2 } from "./types/ManagerTypesV2.sol"; /// @title IManager /// @author Rohan Kulkarni @@ -46,9 +45,6 @@ interface IManager is IUUPS, IOwnable { /// @dev Reverts if caller is not the token owner error ONLY_TOKEN_OWNER(); - /// @dev Reverts if caller is not the token owner - error INVALID_REWARDS_CONFIG(); - /// /// /// STRUCTS /// /// /// diff --git a/src/manager/Manager.sol b/src/manager/Manager.sol index 92f46d7..5a1f243 100644 --- a/src/manager/Manager.sol +++ b/src/manager/Manager.sol @@ -6,7 +6,6 @@ import { Ownable } from "../lib/utils/Ownable.sol"; import { ERC1967Proxy } from "../lib/proxy/ERC1967Proxy.sol"; import { ManagerStorageV1 } from "./storage/ManagerStorageV1.sol"; -import { ManagerStorageV2 } from "./storage/ManagerStorageV2.sol"; import { IManager } from "./IManager.sol"; import { IToken } from "../token/IToken.sol"; import { IBaseMetadata } from "../token/metadata/interfaces/IBaseMetadata.sol"; @@ -22,14 +21,7 @@ import { IVersionedContract } from "../lib/interfaces/IVersionedContract.sol"; /// @author Neokry & Rohan Kulkarni /// @custom:repo github.com/ourzora/nouns-protocol /// @notice The DAO deployer and upgrade manager -contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1, ManagerStorageV2 { - /// /// - /// CONSTANTS /// - /// /// - - /// @notice The maximum combined BPS for referral and builder rewards - uint256 private constant MAX_REWARDS_BPS = 2_000; - +contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1 { /// /// /// IMMUTABLES /// /// /// @@ -49,6 +41,9 @@ contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1 /// @notice The governor implementation address address public immutable governorImpl; + /// @notice The address to send Builder DAO rewards to + address public immutable builderRewardsRecipient; + /// /// /// CONSTRUCTOR /// /// /// @@ -58,13 +53,15 @@ contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1 address _metadataImpl, address _auctionImpl, address _treasuryImpl, - address _governorImpl + address _governorImpl, + address _builderRewardsRecipient ) payable initializer { tokenImpl = _tokenImpl; metadataImpl = _metadataImpl; auctionImpl = _auctionImpl; treasuryImpl = _treasuryImpl; governorImpl = _governorImpl; + builderRewardsRecipient = _builderRewardsRecipient; } /// /// @@ -250,18 +247,6 @@ contract Manager is IManager, VersionedContract, UUPS, Ownable, ManagerStorageV1 emit UpgradeRemoved(_baseImpl, _upgradeImpl); } - /// @notice Set the global reward configuration - /// @param _rewards The reward to be paid to the referrer in BPS - function setRewardConfig(RewardConfig calldata _rewards) external onlyOwner { - // Ensure the rewards are valid - if (_rewards.referralBps + _rewards.builderBps > MAX_REWARDS_BPS) { - revert INVALID_REWARDS_CONFIG(); - } - - // Set the rewards - rewards = _rewards; - } - /// @notice Safely get the contract version of a target contract. /// @param target The ERC-721 token address /// @dev Assume `target` is a contract diff --git a/src/manager/storage/ManagerStorageV2.sol b/src/manager/storage/ManagerStorageV2.sol deleted file mode 100644 index 23a1af0..0000000 --- a/src/manager/storage/ManagerStorageV2.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import { ManagerTypesV2 } from "../types/ManagerTypesV2.sol"; - -/// @notice Manager Storage V2 -/// @author Neokry -/// @notice The Manager storage contract -contract ManagerStorageV2 is ManagerTypesV2 { - /// @notice The protocol rewards configuration - RewardConfig public rewards; -} diff --git a/src/manager/types/ManagerTypesV2.sol b/src/manager/types/ManagerTypesV2.sol deleted file mode 100644 index 8620f31..0000000 --- a/src/manager/types/ManagerTypesV2.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -/// @title ManagerTypesV2 -/// @author Neokry -/// @notice The external Base Metadata errors and functions -interface ManagerTypesV2 { - /// @notice Config for protocol rewards - struct RewardConfig { - //// @notice Address to send Builder DAO rewards to - address builderRecipient; - //// @notice Percentage of final bid amount in BPS claimable by the bid referral - uint16 referralBps; - //// @notice Percentage of final bid amount in BPS claimable by BuilderDAO - uint16 builderBps; - } -} diff --git a/test/Auction.t.sol b/test/Auction.t.sol index e92bddc..eed3763 100644 --- a/test/Auction.t.sol +++ b/test/Auction.t.sol @@ -5,14 +5,21 @@ import { NounsBuilderTest } from "./utils/NounsBuilderTest.sol"; import { MockERC721 } from "./utils/mocks/MockERC721.sol"; import { MockImpl } from "./utils/mocks/MockImpl.sol"; import { MockPartialTokenImpl } from "./utils/mocks/MockPartialTokenImpl.sol"; +import { MockProtocolRewards } from "./utils/mocks/MockProtocolRewards.sol"; +import { Auction } from "../src/auction/Auction.sol"; import { IAuction } from "../src/auction/IAuction.sol"; import { AuctionTypesV2 } from "../src/auction/types/AuctionTypesV2.sol"; contract AuctionTest is NounsBuilderTest { MockImpl internal mockImpl; + Auction internal rewardImpl; address internal bidder1; address internal bidder2; + address internal referral; + + uint16 internal builderRewardBPS = 300; + uint16 internal referralRewardBPS = 400; function setUp() public virtual override { super.setUp(); @@ -20,10 +27,13 @@ contract AuctionTest is NounsBuilderTest { bidder1 = vm.addr(0xB1); bidder2 = vm.addr(0xB2); + referral = vm.addr(0x41); + vm.deal(bidder1, 100 ether); vm.deal(bidder2, 100 ether); mockImpl = new MockImpl(); + rewardImpl = new Auction(address(manager), address(rewards), weth, builderRewardBPS, referralRewardBPS); } function deployAltMock(address founderRewardRecipent, uint16 founderRewardPercent) internal virtual { @@ -636,13 +646,119 @@ contract AuctionTest is NounsBuilderTest { AuctionTypesV2.FounderReward memory newRewards = AuctionTypesV2.FounderReward({ recipient: founder2, percentBps: 1000 }); - vm.startPrank(founder); + vm.prank(founder); auction.setFounderReward(newRewards); - vm.stopPrank(); (address newRecipient, uint256 newPercentBps) = auction.founderReward(); assertEq(newRecipient, founder2); assertEq(newPercentBps, 1000); } + + function testRevert_DeployInvalidBPS() public { + setMockFounderParams(); + + setMockTokenParams(); + + setAuctionParams(0.01 ether, 10 minutes, founder, 4_000); + + setMockGovParams(); + + vm.expectRevert(abi.encodeWithSignature("INVALID_REWARDS_BPS()")); + deploy(foundersArr, tokenParams, auctionParams, govParams); + } + + function testRevert_DeployInvalidRecipient() public { + setMockFounderParams(); + + setMockTokenParams(); + + setAuctionParams(0.01 ether, 10 minutes, address(0), 1_000); + + setMockGovParams(); + + vm.expectRevert(abi.encodeWithSignature("INVALID_REWARDS_RECIPIENT()")); + deploy(foundersArr, tokenParams, auctionParams, govParams); + } + + function testRevert_UpdateFounderRewardInvalidConfig() public { + // deploy with 5% founder fee + deployAltMock(founder, 500); + + (address recipient, uint256 percentBps) = auction.founderReward(); + + assertEq(recipient, founder); + assertEq(percentBps, 500); + + AuctionTypesV2.FounderReward memory newRewards = AuctionTypesV2.FounderReward({ recipient: founder2, percentBps: 4_000 }); + + vm.prank(founder); + vm.expectRevert(abi.encodeWithSignature("INVALID_REWARDS_BPS()")); + auction.setFounderReward(newRewards); + } + + function test_BuilderAndReferralReward() external { + // Setup + deployMock(); + + vm.prank(manager.owner()); + manager.registerUpgrade(auctionImpl, address(rewardImpl)); + + vm.prank(auction.owner()); + auction.upgradeTo(address(rewardImpl)); + + vm.prank(founder); + auction.unpause(); + + // Check reward values + + vm.prank(bidder1); + auction.createBidWithReferral{ value: 1 ether }(2, referral); + + vm.warp(10 minutes + 1 seconds); + + auction.settleCurrentAndCreateNewAuction(); + + assertEq(token.ownerOf(2), bidder1); + assertEq(token.getVotes(bidder1), 1); + + assertEq(address(treasury).balance, 0.93 ether); + assertEq(address(rewards).balance, 0.03 ether + 0.04 ether); + + assertEq(MockProtocolRewards(rewards).balanceOf(zoraDAO), 0.03 ether); + assertEq(MockProtocolRewards(rewards).balanceOf(referral), 0.04 ether); + } + + function test_FounderBuilderAndReferralReward() external { + // Setup + deployAltMock(founder, 500); + + vm.prank(manager.owner()); + manager.registerUpgrade(auctionImpl, address(rewardImpl)); + + vm.prank(auction.owner()); + auction.upgradeTo(address(rewardImpl)); + + vm.prank(founder); + auction.unpause(); + + // Check reward values + + vm.prank(bidder1); + auction.createBidWithReferral{ value: 1 ether }(2, referral); + + vm.warp(10 minutes + 1 seconds); + + auction.settleCurrentAndCreateNewAuction(); + + assertEq(token.ownerOf(2), bidder1); + assertEq(token.getVotes(bidder1), 1); + + assertEq(address(treasury).balance, 0.88 ether); + assertEq(address(rewards).balance, 0.03 ether + 0.04 ether + 0.05 ether); + + assertEq(MockProtocolRewards(rewards).balanceOf(zoraDAO), 0.03 ether); + assertEq(MockProtocolRewards(rewards).balanceOf(referral), 0.04 ether); + assertEq(MockProtocolRewards(rewards).balanceOf(founder), 0.05 ether); + } } diff --git a/test/utils/NounsBuilderTest.sol b/test/utils/NounsBuilderTest.sol index a2117de..8212977 100644 --- a/test/utils/NounsBuilderTest.sol +++ b/test/utils/NounsBuilderTest.sol @@ -60,17 +60,17 @@ contract NounsBuilderTest is Test { vm.label(founder, "FOUNDER"); vm.label(founder2, "FOUNDER_2"); - managerImpl0 = address(new Manager(address(0), address(0), address(0), address(0), address(0))); + managerImpl0 = address(new Manager(address(0), address(0), address(0), address(0), address(0), address(0))); manager = Manager(address(new ERC1967Proxy(managerImpl0, abi.encodeWithSignature("initialize(address)", zoraDAO)))); rewards = address(new MockProtocolRewards()); tokenImpl = address(new Token(address(manager))); metadataRendererImpl = address(new MetadataRenderer(address(manager))); - auctionImpl = address(new Auction(address(manager), address(rewards), weth)); + auctionImpl = address(new Auction(address(manager), address(rewards), weth, 0, 0)); treasuryImpl = address(new Treasury(address(manager))); governorImpl = address(new Governor(address(manager))); - managerImpl = address(new Manager(tokenImpl, metadataRendererImpl, auctionImpl, treasuryImpl, governorImpl)); + managerImpl = address(new Manager(tokenImpl, metadataRendererImpl, auctionImpl, treasuryImpl, governorImpl, zoraDAO)); vm.prank(zoraDAO); manager.upgradeTo(managerImpl);