From 34d47d5e43ca47e70dfadd40bbc17af96bb174f3 Mon Sep 17 00:00:00 2001 From: neokry Date: Thu, 27 Jul 2023 14:51:04 +0800 Subject: [PATCH] Add optional fees to Auction contract --- script/DeployContracts.s.sol | 5 ++- script/DeployVersion1_1.s.sol | 5 ++- src/auction/Auction.sol | 35 +++++++++++++++++--- src/fees/BuilderFeeManager.sol | 37 ++++++++++++++++++++++ src/fees/interfaces/IBuilderFeeManager.sol | 6 ++++ test/utils/NounsBuilderTest.sol | 5 ++- 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/fees/BuilderFeeManager.sol create mode 100644 src/fees/interfaces/IBuilderFeeManager.sol diff --git a/script/DeployContracts.s.sol b/script/DeployContracts.s.sol index 224501d..80ebd9f 100644 --- a/script/DeployContracts.s.sol +++ b/script/DeployContracts.s.sol @@ -5,6 +5,7 @@ import "forge-std/Script.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IManager, Manager } from "../src/manager/Manager.sol"; +import { BuilderFeeManager } from "../src/fees/BuilderFeeManager.sol"; import { IToken, Token } from "../src/token/Token.sol"; import { MetadataRenderer } from "../src/token/metadata/MetadataRenderer.sol"; import { IAuction, Auction } from "../src/auction/Auction.sol"; @@ -48,8 +49,10 @@ contract DeployContracts is Script { // Deploy metadata renderer implementation address metadataRendererImpl = address(new MetadataRenderer(address(manager))); + address feeManager = address(new BuilderFeeManager(0, address(0))); + // Deploy auction house implementation - address auctionImpl = address(new Auction(address(manager), weth)); + address auctionImpl = address(new Auction(address(manager), feeManager, weth)); // Deploy treasury implementation address treasuryImpl = address(new Treasury(address(manager))); diff --git a/script/DeployVersion1_1.s.sol b/script/DeployVersion1_1.s.sol index 115d19b..ea41974 100644 --- a/script/DeployVersion1_1.s.sol +++ b/script/DeployVersion1_1.s.sol @@ -6,6 +6,7 @@ import "forge-std/console2.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { IManager, Manager } from "../src/manager/Manager.sol"; +import { BuilderFeeManager } from "../src/fees/BuilderFeeManager.sol"; import { IToken, Token } from "../src/token/Token.sol"; import { MetadataRenderer } from "../src/token/metadata/MetadataRenderer.sol"; import { IAuction, Auction } from "../src/auction/Auction.sol"; @@ -49,8 +50,10 @@ contract DeployVersion1_1 is Script { // Get root manager implementation + proxy Manager manager = Manager(managerProxy); + address feeManager = address(new BuilderFeeManager(0, address(0))); + // Deploy auction upgrade implementation - address auctionUpgradeImpl = address(new Auction(managerProxy, weth)); + address auctionUpgradeImpl = address(new Auction(managerProxy, feeManager, weth)); // Deploy governor upgrade implementation address governorUpgradeImpl = address(new Governor(managerProxy)); // Deploy treasury upgrade implementation diff --git a/src/auction/Auction.sol b/src/auction/Auction.sol index 1dcf249..6af616e 100644 --- a/src/auction/Auction.sol +++ b/src/auction/Auction.sol @@ -10,6 +10,7 @@ import { SafeCast } from "../lib/utils/SafeCast.sol"; import { AuctionStorageV1 } from "./storage/AuctionStorageV1.sol"; import { Token } from "../token/Token.sol"; import { IManager } from "../manager/IManager.sol"; +import { IBuilderFeeManager } from "../fees/interfaces/IBuilderFeeManager.sol"; import { IAuction } from "./IAuction.sol"; import { IWETH } from "../lib/interfaces/IWETH.sol"; @@ -36,6 +37,9 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, /// @notice The address of WETH address private immutable WETH; + /// @notice The builder fee manager + address private immutable builderFeeManager; + /// @notice The contract upgrade manager IManager private immutable manager; @@ -43,10 +47,12 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, /// CONSTRUCTOR /// /// /// + /// @param _builderFeeManager The builder fee manager address /// @param _manager The contract upgrade manager address /// @param _weth The address of WETH - constructor(address _manager, address _weth) payable initializer { + constructor(address _manager, address _builderFeeManager, address _weth) payable initializer { manager = IManager(_manager); + builderFeeManager = _builderFeeManager; WETH = _weth; } @@ -196,10 +202,24 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, // If a bid was placed: if (_auction.highestBidder != address(0)) { // Cache the amount of the highest bid - uint256 highestBid = _auction.highestBid; + uint256 funds = _auction.highestBid; + + // Check if fee manager is set + if (builderFeeManager != address(0)) { + // Get the fee amount from the fee manager + (address recipent, uint256 fee) = _builderFeeForAmount(funds); + + // If there is a fee to be payed + if (fee != 0) { + _handleOutgoingTransfer(recipent, fee); + funds -= fee; + } + } - // If the highest bid included ETH: Transfer it to the DAO treasury - if (highestBid != 0) _handleOutgoingTransfer(settings.treasury, highestBid); + // If the highest bid included ETH: Transfer remaining funds to the DAO treasury + if (funds != 0) { + _handleOutgoingTransfer(settings.treasury, funds); + } // Transfer the token to the highest bidder token.transferFrom(address(this), _auction.highestBidder, _auction.tokenId); @@ -361,6 +381,13 @@ contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, emit MinBidIncrementPercentageUpdated(_percentage); } + /// @dev Gets the builder fee for amount of withdraw + /// @param amount amount of funds to get fee for + function _builderFeeForAmount(uint256 amount) private returns (address payable, uint256) { + (address payable recipient, uint256 bps) = IBuilderFeeManager(builderFeeManager).getBuilderFeesBPS(address(this)); + return (recipient, (amount * bps) / 10_000); + } + /// /// /// TRANSFER UTIL /// /// /// diff --git a/src/fees/BuilderFeeManager.sol b/src/fees/BuilderFeeManager.sol new file mode 100644 index 0000000..75e6e10 --- /dev/null +++ b/src/fees/BuilderFeeManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import { IBuilderFeeManager } from "./interfaces/IBuilderFeeManager.sol"; +import { Ownable } from "../lib/utils/Ownable.sol"; + +contract BuilderFeeManager is Ownable, IBuilderFeeManager { + mapping(address => uint256) private feeOverride; + uint256 private defaultFeeBPS; + + event FeeOverrideSet(address indexed, uint256 indexed); + event DefaultFeeSet(uint256 indexed); + + constructor(uint256 _defaultFeeBPS, address feeManagerAdmin) { + defaultFeeBPS = _defaultFeeBPS; + _transferOwnership(feeManagerAdmin); + } + + function setDefaultFee(uint256 amountBPS) external onlyOwner { + require(amountBPS < 2001, "Fee too high (not greater than 20%)"); + defaultFeeBPS = amountBPS; + emit DefaultFeeSet(amountBPS); + } + + function setFeeOverride(address tokenContract, uint256 amountBPS) external onlyOwner { + require(amountBPS < 2001, "Fee too high (not greater than 20%)"); + feeOverride[tokenContract] = amountBPS; + emit FeeOverrideSet(tokenContract, amountBPS); + } + + function getBuilderFeesBPS(address mediaContract) external view returns (address payable, uint256) { + if (feeOverride[mediaContract] > 0) { + return (payable(owner()), feeOverride[mediaContract]); + } + return (payable(owner()), defaultFeeBPS); + } +} diff --git a/src/fees/interfaces/IBuilderFeeManager.sol b/src/fees/interfaces/IBuilderFeeManager.sol new file mode 100644 index 0000000..9deb67f --- /dev/null +++ b/src/fees/interfaces/IBuilderFeeManager.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +interface IBuilderFeeManager { + function getBuilderFeesBPS(address sender) external returns (address payable, uint256); +} diff --git a/test/utils/NounsBuilderTest.sol b/test/utils/NounsBuilderTest.sol index 548c468..6301b8c 100644 --- a/test/utils/NounsBuilderTest.sol +++ b/test/utils/NounsBuilderTest.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.16; import { Test } from "forge-std/Test.sol"; import { IManager, Manager } from "../../src/manager/Manager.sol"; +import { BuilderFeeManager } from "../../src/fees/BuilderFeeManager.sol"; import { IToken, Token } from "../../src/token/Token.sol"; import { IBaseMetadata, MetadataRenderer } from "../../src/token/metadata/MetadataRenderer.sol"; import { IAuction, Auction } from "../../src/auction/Auction.sol"; @@ -25,6 +26,7 @@ contract NounsBuilderTest is Test { address internal managerImpl0; address internal managerImpl; + address internal feeManager; address internal tokenImpl; address internal metadataRendererImpl; address internal auctionImpl; @@ -60,10 +62,11 @@ contract NounsBuilderTest is Test { managerImpl0 = address(new Manager()); manager = Manager(address(new ERC1967Proxy(managerImpl0, abi.encodeWithSignature("initialize(address)", zoraDAO)))); + feeManager = address(new BuilderFeeManager(0, address(0))); tokenImpl = address(new Token(address(manager))); metadataRendererImpl = address(new MetadataRenderer(address(manager))); - auctionImpl = address(new Auction(address(manager), weth)); + auctionImpl = address(new Auction(address(manager), feeManager, weth)); treasuryImpl = address(new Treasury(address(manager))); governorImpl = address(new Governor(address(manager)));