Transaction Hash:
Block:
16449234 at Jan-20-2023 04:32:47 PM +UTC
Transaction Fee:
0.001783097235017058 ETH
$4.36
Gas Used:
55,137 Gas / 32.339395234 Gwei
Emitted Events:
289 |
ERC1967Proxy.0x1edd36b28569cf1cf930c008c5bcb196ea19525977951413bec3118946951561( 0x1edd36b28569cf1cf930c008c5bcb196ea19525977951413bec3118946951561, 0000000000000000000000000000000000000000000000000000000000000006, 000000000000000000000000c2efad9a2ee3f965ee773dfba7124c9aff8b9f4e, 000000000000000000000000000000000000000000000000002386f26fc10000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000063cadf75 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x25a939f3...D02a31575 | 0.0069 Eth | 0.01 Eth | 0.0031 | ||
0x690B9A9E...Db4FaC990
Miner
| (builder0x69) | 2.091383781686374252 Eth | 2.091466487186374252 Eth | 0.0000827055 | |
0x6a56f861...7f67aF0c2 | 0.510356749729360265 Eth | 0.517256749729360265 Eth | 0.0069 | ||
0xC2eFad9A...aFF8B9f4E |
0.0503056654407529 Eth
Nonce: 750
|
0.038522568205735842 Eth
Nonce: 751
| 0.011783097235017058 |
Execution Trace
ETH 0.01
ERC1967Proxy.659dd2b4( )
ETH 0.01
Auction.createBid( _tokenId=6 )
- ETH 0.0069
0x6a56f8613c494c95de6adccbf9747b37f67af0c2.CALL( )
- ETH 0.0069
File 1 of 2: ERC1967Proxy
File 2 of 2: Auction
// SPDX-License-Identifier: MIT pragma solidity 0.8.16; // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback() external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive() external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} } /// @title IERC1967Upgrade /// @author Rohan Kulkarni /// @notice The external ERC1967Upgrade events and errors interface IERC1967Upgrade { /// /// /// EVENTS /// /// /// /// @notice Emitted when the implementation is upgraded /// @param impl The address of the implementation event Upgraded(address impl); /// /// /// ERRORS /// /// /// /// @dev Reverts if an implementation is an invalid upgrade /// @param impl The address of the invalid implementation error INVALID_UPGRADE(address impl); /// @dev Reverts if an implementation upgrade is not stored at the storage slot of the original error UNSUPPORTED_UUID(); /// @dev Reverts if an implementation does not support ERC1822 proxiableUUID() error ONLY_UUPS(); } // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified * proxy whose upgrades are fully controlled by the current implementation. */ interface IERC1822Proxiable { /** * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation * address. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. */ function proxiableUUID() external view returns (bytes32); } // OpenZeppelin Contracts (last updated v4.7.0) (utils/StorageSlot.sol) /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } } /// @title EIP712 /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (utils/Address.sol) /// - Uses custom errors `INVALID_TARGET()` & `DELEGATE_CALL_FAILED()` /// - Adds util converting address to bytes32 library Address { /// /// /// ERRORS /// /// /// /// @dev Reverts if the target of a delegatecall is not a contract error INVALID_TARGET(); /// @dev Reverts if a delegatecall has failed error DELEGATE_CALL_FAILED(); /// /// /// FUNCTIONS /// /// /// /// @dev Utility to convert an address to bytes32 function toBytes32(address _account) internal pure returns (bytes32) { return bytes32(uint256(uint160(_account)) << 96); } /// @dev If an address is a contract function isContract(address _account) internal view returns (bool rv) { assembly { rv := gt(extcodesize(_account), 0) } } /// @dev Performs a delegatecall on an address function functionDelegateCall(address _target, bytes memory _data) internal returns (bytes memory) { if (!isContract(_target)) revert INVALID_TARGET(); (bool success, bytes memory returndata) = _target.delegatecall(_data); return verifyCallResult(success, returndata); } /// @dev Verifies a delegatecall was successful function verifyCallResult(bool _success, bytes memory _returndata) internal pure returns (bytes memory) { if (_success) { return _returndata; } else { if (_returndata.length > 0) { assembly { let returndata_size := mload(_returndata) revert(add(32, _returndata), returndata_size) } } else { revert DELEGATE_CALL_FAILED(); } } } } /// @title ERC1967Upgrade /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (proxy/ERC1967/ERC1967Upgrade.sol) /// - Uses custom errors declared in IERC1967Upgrade /// - Removes ERC1967 admin and beacon support abstract contract ERC1967Upgrade is IERC1967Upgrade { /// /// /// CONSTANTS /// /// /// /// @dev bytes32(uint256(keccak256('eip1967.proxy.rollback')) - 1) bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /// @dev bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /// /// /// FUNCTIONS /// /// /// /// @dev Upgrades to an implementation with security checks for UUPS proxies and an additional function call /// @param _newImpl The new implementation address /// @param _data The encoded function call function _upgradeToAndCallUUPS( address _newImpl, bytes memory _data, bool _forceCall ) internal { if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) { _setImplementation(_newImpl); } else { try IERC1822Proxiable(_newImpl).proxiableUUID() returns (bytes32 slot) { if (slot != _IMPLEMENTATION_SLOT) revert UNSUPPORTED_UUID(); } catch { revert ONLY_UUPS(); } _upgradeToAndCall(_newImpl, _data, _forceCall); } } /// @dev Upgrades to an implementation with an additional function call /// @param _newImpl The new implementation address /// @param _data The encoded function call function _upgradeToAndCall( address _newImpl, bytes memory _data, bool _forceCall ) internal { _upgradeTo(_newImpl); if (_data.length > 0 || _forceCall) { Address.functionDelegateCall(_newImpl, _data); } } /// @dev Performs an implementation upgrade /// @param _newImpl The new implementation address function _upgradeTo(address _newImpl) internal { _setImplementation(_newImpl); emit Upgraded(_newImpl); } /// @dev Stores the address of an implementation /// @param _impl The implementation address function _setImplementation(address _impl) private { if (!Address.isContract(_impl)) revert INVALID_UPGRADE(_impl); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = _impl; } /// @dev The address of the current implementation function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } } /// @title ERC1967Proxy /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (proxy/ERC1967/ERC1967Proxy.sol) /// - Inherits a modern, minimal ERC1967Upgrade contract ERC1967Proxy is IERC1967Upgrade, Proxy, ERC1967Upgrade { /// /// /// CONSTRUCTOR /// /// /// /// @dev Initializes the proxy with an implementation contract and encoded function call /// @param _logic The implementation address /// @param _data The encoded function call constructor(address _logic, bytes memory _data) payable { _upgradeToAndCall(_logic, _data, false); } /// /// /// FUNCTIONS /// /// /// /// @dev The address of the current implementation function _implementation() internal view virtual override returns (address) { return ERC1967Upgrade._getImplementation(); } }
File 2 of 2: Auction
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.0; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified * proxy whose upgrades are fully controlled by the current implementation. */ interface IERC1822Proxiable { /** * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation * address. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. */ function proxiableUUID() external view returns (bytes32); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 amount ) external returns (bool); } // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (utils/StorageSlot.sol) pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ``` * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; abstract contract VersionedContract { function contractVersion() external pure returns (string memory) { return "1.1.0"; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { UUPS } from "../lib/proxy/UUPS.sol"; import { Ownable } from "../lib/utils/Ownable.sol"; import { ReentrancyGuard } from "../lib/utils/ReentrancyGuard.sol"; import { Pausable } from "../lib/utils/Pausable.sol"; 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 { IAuction } from "./IAuction.sol"; import { IWETH } from "../lib/interfaces/IWETH.sol"; import { VersionedContract } from "../VersionedContract.sol"; /// @title Auction /// @author Rohan Kulkarni /// @notice A DAO's auction house /// @custom:repo github.com/ourzora/nouns-protocol /// Modified from: /// - NounsAuctionHouse.sol commit 2cbe6c7 - licensed under the BSD-3-Clause license. /// - Zora V3 ReserveAuctionCoreEth module commit 795aeca - licensed under the GPL-3.0 license. contract Auction is IAuction, VersionedContract, UUPS, Ownable, ReentrancyGuard, Pausable, AuctionStorageV1 { /// /// /// IMMUTABLES /// /// /// /// @notice Iniital time buffer for auction bids uint40 private immutable INITIAL_TIME_BUFFER = 5 minutes; /// @notice Min bid increment BPS uint8 private immutable INITIAL_MIN_BID_INCREMENT_PERCENT = 10; /// @notice The address of WETH address private immutable WETH; /// @notice The contract upgrade manager IManager private immutable manager; /// /// /// CONSTRUCTOR /// /// /// /// @param _manager The contract upgrade manager address /// @param _weth The address of WETH constructor(address _manager, address _weth) payable initializer { manager = IManager(_manager); WETH = _weth; } /// /// /// INITIALIZER /// /// /// /// @notice Initializes a DAO's auction contract /// @param _token The ERC-721 token address /// @param _founder The founder responsible for starting the first auction /// @param _treasury The treasury address where ETH will be sent /// @param _duration The duration of each auction /// @param _reservePrice The reserve price of each auction function initialize( address _token, address _founder, address _treasury, uint256 _duration, uint256 _reservePrice ) external initializer { // Ensure the caller is the contract manager if (msg.sender != address(manager)) revert ONLY_MANAGER(); // Initialize the reentrancy guard __ReentrancyGuard_init(); // Grant initial ownership to a founder __Ownable_init(_founder); // Pause the contract until the first auction __Pausable_init(true); // Store DAO's ERC-721 token token = Token(_token); // Store the auction house settings settings.duration = SafeCast.toUint40(_duration); settings.reservePrice = _reservePrice; settings.treasury = _treasury; settings.timeBuffer = INITIAL_TIME_BUFFER; settings.minBidIncrement = INITIAL_MIN_BID_INCREMENT_PERCENT; } /// /// /// CREATE BID /// /// /// /// @notice Creates a bid for the current token /// @param _tokenId The ERC-721 token id function createBid(uint256 _tokenId) external payable nonReentrant { // Ensure the bid is for the current token if (auction.tokenId != _tokenId) { revert INVALID_TOKEN_ID(); } // Ensure the auction is still active if (block.timestamp >= auction.endTime) { revert AUCTION_OVER(); } // Cache the amount of ETH attached uint256 msgValue = msg.value; // Cache the address of the highest bidder address lastHighestBidder = auction.highestBidder; // Cache the last highest bid uint256 lastHighestBid = auction.highestBid; // Store the new highest bid auction.highestBid = msgValue; // Store the new highest bidder auction.highestBidder = msg.sender; // Used to store whether to extend the auction bool extend; // Cannot underflow as `_auction.endTime` is ensured to be greater than the current time above unchecked { // Compute whether the time remaining is less than the buffer extend = (auction.endTime - block.timestamp) < settings.timeBuffer; // If the auction should be extended if (extend) { // Update the end time with the additional time buffer auction.endTime = uint40(block.timestamp + settings.timeBuffer); } } // If this is the first bid: if (lastHighestBidder == address(0)) { // Ensure the bid meets the reserve price if (msgValue < settings.reservePrice) { revert RESERVE_PRICE_NOT_MET(); } // Else this is a subsequent bid: } else { // Used to store the minimum bid required uint256 minBid; // Cannot realistically overflow unchecked { // Compute the minimum bid minBid = lastHighestBid + ((lastHighestBid * settings.minBidIncrement) / 100); } // Ensure the incoming bid meets the minimum if (msgValue < minBid) { revert MINIMUM_BID_NOT_MET(); } // Ensure that the second bid is not also zero if (minBid == 0 && msgValue == 0 && lastHighestBidder != address(0)) { revert MINIMUM_BID_NOT_MET(); } // Refund the previous bidder _handleOutgoingTransfer(lastHighestBidder, lastHighestBid); } emit AuctionBid(_tokenId, msg.sender, msgValue, extend, auction.endTime); } /// /// /// SETTLE & CREATE AUCTION /// /// /// /// @notice Settles the current auction and creates the next one function settleCurrentAndCreateNewAuction() external nonReentrant whenNotPaused { _settleAuction(); _createAuction(); } /// @dev Settles the current auction function _settleAuction() private { // Get a copy of the current auction Auction memory _auction = auction; // Ensure the auction wasn't already settled if (auction.settled) revert AUCTION_SETTLED(); // Ensure the auction had started if (_auction.startTime == 0) revert AUCTION_NOT_STARTED(); // Ensure the auction is over if (block.timestamp < _auction.endTime) revert AUCTION_ACTIVE(); // Mark the auction as settled auction.settled = true; // If a bid was placed: if (_auction.highestBidder != address(0)) { // Cache the amount of the highest bid uint256 highestBid = _auction.highestBid; // If the highest bid included ETH: Transfer it to the DAO treasury if (highestBid != 0) _handleOutgoingTransfer(settings.treasury, highestBid); // Transfer the token to the highest bidder token.transferFrom(address(this), _auction.highestBidder, _auction.tokenId); // Else no bid was placed: } else { // Burn the token token.burn(_auction.tokenId); } emit AuctionSettled(_auction.tokenId, _auction.highestBidder, _auction.highestBid); } /// @dev Creates an auction for the next token function _createAuction() private returns (bool) { // Get the next token available for bidding try token.mint() returns (uint256 tokenId) { // Store the token id auction.tokenId = tokenId; // Cache the current timestamp uint256 startTime = block.timestamp; // Used to store the auction end time uint256 endTime; // Cannot realistically overflow unchecked { // Compute the auction end time endTime = startTime + settings.duration; } // Store the auction start and end time auction.startTime = uint40(startTime); auction.endTime = uint40(endTime); // Reset data from the previous auction auction.highestBid = 0; auction.highestBidder = address(0); auction.settled = false; emit AuctionCreated(tokenId, startTime, endTime); return true; } catch { // Pause the contract if token minting failed _pause(); return false; } } /// /// /// PAUSE /// /// /// /// @notice Unpauses the auction house function unpause() external onlyOwner { _unpause(); // If this is the first auction: if (!settings.launched) { // Mark the DAO as launched settings.launched = true; // Transfer ownership of the auction contract to the DAO transferOwnership(settings.treasury); // Transfer ownership of the token contract to the DAO token.onFirstAuctionStarted(); // Start the first auction if (!_createAuction()) { // In cause of failure, revert. revert AUCTION_CREATE_FAILED_TO_LAUNCH(); } } // Else if the contract was paused and the previous auction was settled: else if (auction.settled) { // Start the next auction _createAuction(); } } /// @notice Pauses the auction house function pause() external onlyOwner { _pause(); } /// @notice Settles the latest auction when the contract is paused function settleAuction() external nonReentrant whenPaused { _settleAuction(); } /// /// /// AUCTION SETTINGS /// /// /// /// @notice The DAO treasury function treasury() external view returns (address) { return settings.treasury; } /// @notice The time duration of each auction function duration() external view returns (uint256) { return settings.duration; } /// @notice The reserve price of each auction function reservePrice() external view returns (uint256) { return settings.reservePrice; } /// @notice The minimum amount of time to place a bid during an active auction function timeBuffer() external view returns (uint256) { return settings.timeBuffer; } /// @notice The minimum percentage an incoming bid must raise the highest bid function minBidIncrement() external view returns (uint256) { return settings.minBidIncrement; } /// /// /// UPDATE SETTINGS /// /// /// /// @notice Updates the time duration of each auction /// @param _duration The new time duration function setDuration(uint256 _duration) external onlyOwner whenPaused { settings.duration = SafeCast.toUint40(_duration); emit DurationUpdated(_duration); } /// @notice Updates the reserve price of each auction /// @param _reservePrice The new reserve price function setReservePrice(uint256 _reservePrice) external onlyOwner whenPaused { settings.reservePrice = _reservePrice; emit ReservePriceUpdated(_reservePrice); } /// @notice Updates the time buffer of each auction /// @param _timeBuffer The new time buffer function setTimeBuffer(uint256 _timeBuffer) external onlyOwner whenPaused { settings.timeBuffer = SafeCast.toUint40(_timeBuffer); emit TimeBufferUpdated(_timeBuffer); } /// @notice Updates the minimum bid increment of each subsequent bid /// @param _percentage The new percentage function setMinimumBidIncrement(uint256 _percentage) external onlyOwner whenPaused { if (_percentage == 0) { revert MIN_BID_INCREMENT_1_PERCENT(); } settings.minBidIncrement = SafeCast.toUint8(_percentage); emit MinBidIncrementPercentageUpdated(_percentage); } /// /// /// TRANSFER UTIL /// /// /// /// @notice Transfer ETH/WETH from the contract /// @param _to The recipient address /// @param _amount The amount transferring function _handleOutgoingTransfer(address _to, uint256 _amount) private { // Ensure the contract has enough ETH to transfer if (address(this).balance < _amount) revert INSOLVENT(); // Used to store if the transfer succeeded bool success; assembly { // Transfer ETH to the recipient // Limit the call to 50,000 gas success := call(50000, _to, _amount, 0, 0, 0, 0) } // If the transfer failed: if (!success) { // Wrap as WETH IWETH(WETH).deposit{ value: _amount }(); // Transfer WETH instead bool wethSuccess = IWETH(WETH).transfer(_to, _amount); // Ensure successful transfer if (!wethSuccess) { revert FAILING_WETH_TRANSFER(); } } } /// /// /// AUCTION UPGRADE /// /// /// /// @notice Ensures the caller is authorized to upgrade the contract and the new implementation is valid /// @dev This function is called in `upgradeTo` & `upgradeToAndCall` /// @param _newImpl The new implementation address function _authorizeUpgrade(address _newImpl) internal view override onlyOwner whenPaused { // Ensure the new implementation is registered by the Builder DAO if (!manager.isRegisteredUpgrade(_getImplementation(), _newImpl)) revert INVALID_UPGRADE(_newImpl); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IUUPS } from "../lib/interfaces/IUUPS.sol"; import { IOwnable } from "../lib/interfaces/IOwnable.sol"; import { IPausable } from "../lib/interfaces/IPausable.sol"; /// @title IAuction /// @author Rohan Kulkarni /// @notice The external Auction events, errors, and functions interface IAuction is IUUPS, IOwnable, IPausable { /// /// /// EVENTS /// /// /// /// @notice Emitted when a bid is placed /// @param tokenId The ERC-721 token id /// @param bidder The address of the bidder /// @param amount The amount of ETH /// @param extended If the bid extended the auction /// @param endTime The end time of the auction event AuctionBid(uint256 tokenId, address bidder, uint256 amount, bool extended, uint256 endTime); /// @notice Emitted when an auction is settled /// @param tokenId The ERC-721 token id of the settled auction /// @param winner The address of the winning bidder /// @param amount The amount of ETH raised from the winning bid event AuctionSettled(uint256 tokenId, address winner, uint256 amount); /// @notice Emitted when an auction is created /// @param tokenId The ERC-721 token id of the created auction /// @param startTime The start time of the created auction /// @param endTime The end time of the created auction event AuctionCreated(uint256 tokenId, uint256 startTime, uint256 endTime); /// @notice Emitted when the auction duration is updated /// @param duration The new auction duration event DurationUpdated(uint256 duration); /// @notice Emitted when the reserve price is updated /// @param reservePrice The new reserve price event ReservePriceUpdated(uint256 reservePrice); /// @notice Emitted when the min bid increment percentage is updated /// @param minBidIncrementPercentage The new min bid increment percentage event MinBidIncrementPercentageUpdated(uint256 minBidIncrementPercentage); /// @notice Emitted when the time buffer is updated /// @param timeBuffer The new time buffer event TimeBufferUpdated(uint256 timeBuffer); /// /// /// ERRORS /// /// /// /// @dev Reverts if a bid is placed for the wrong token error INVALID_TOKEN_ID(); /// @dev Reverts if a bid is placed for an auction thats over error AUCTION_OVER(); /// @dev Reverts if a bid is placed for an auction that hasn't started error AUCTION_NOT_STARTED(); /// @dev Reverts if attempting to settle an active auction error AUCTION_ACTIVE(); /// @dev Reverts if attempting to settle an auction that was already settled error AUCTION_SETTLED(); /// @dev Reverts if a bid does not meet the reserve price error RESERVE_PRICE_NOT_MET(); /// @dev Reverts if a bid does not meet the minimum bid error MINIMUM_BID_NOT_MET(); /// @dev Error for when the bid increment is set to 0. error MIN_BID_INCREMENT_1_PERCENT(); /// @dev Reverts if the contract does not have enough ETH error INSOLVENT(); /// @dev Reverts if the caller was not the contract manager error ONLY_MANAGER(); /// @dev Thrown if the WETH contract throws a failure on transfer error FAILING_WETH_TRANSFER(); /// @dev Thrown if the auction creation failed error AUCTION_CREATE_FAILED_TO_LAUNCH(); /// /// /// FUNCTIONS /// /// /// /// @notice Initializes a DAO's auction house /// @param token The ERC-721 token address /// @param founder The founder responsible for starting the first auction /// @param treasury The treasury address where ETH will be sent /// @param duration The duration of each auction /// @param reservePrice The reserve price of each auction function initialize( address token, address founder, address treasury, uint256 duration, uint256 reservePrice ) external; /// @notice Creates a bid for the current token /// @param tokenId The ERC-721 token id function createBid(uint256 tokenId) external payable; /// @notice Settles the current auction and creates the next one function settleCurrentAndCreateNewAuction() external; /// @notice Settles the latest auction when the contract is paused function settleAuction() external; /// @notice Pauses the auction house function pause() external; /// @notice Unpauses the auction house function unpause() external; /// @notice The time duration of each auction function duration() external view returns (uint256); /// @notice The reserve price of each auction function reservePrice() external view returns (uint256); /// @notice The minimum amount of time to place a bid during an active auction function timeBuffer() external view returns (uint256); /// @notice The minimum percentage an incoming bid must raise the highest bid function minBidIncrement() external view returns (uint256); /// @notice Updates the time duration of each auction /// @param duration The new time duration function setDuration(uint256 duration) external; /// @notice Updates the reserve price of each auction /// @param reservePrice The new reserve price function setReservePrice(uint256 reservePrice) external; /// @notice Updates the time buffer of each auction /// @param timeBuffer The new time buffer function setTimeBuffer(uint256 timeBuffer) external; /// @notice Updates the minimum bid increment of each subsequent bid /// @param percentage The new percentage function setMinimumBidIncrement(uint256 percentage) external; /// @notice Get the address of the treasury function treasury() external returns (address); } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { Token } from "../../token/Token.sol"; import { AuctionTypesV1 } from "../types/AuctionTypesV1.sol"; /// @title AuctionStorageV1 /// @author Rohan Kulkarni /// @notice The Auction storage contract contract AuctionStorageV1 is AuctionTypesV1 { /// @notice The auction settings Settings internal settings; /// @notice The ERC-721 token Token public token; /// @notice The state of the current auction Auction public auction; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title AuctionTypesV1 /// @author Rohan Kulkarni /// @notice The Auction custom data types contract AuctionTypesV1 { /// @notice The settings type /// @param treasury The DAO treasury /// @param duration The time duration of each auction /// @param timeBuffer The minimum time to place a bid /// @param minBidIncrement The minimum percentage an incoming bid must raise the highest bid /// @param launched If the first auction has been kicked off /// @param reservePrice The reserve price of each auction struct Settings { address treasury; uint40 duration; uint40 timeBuffer; uint8 minBidIncrement; bool launched; uint256 reservePrice; } /// @notice The auction type /// @param tokenId The ERC-721 token id /// @param highestBid The highest amount of ETH raised /// @param highestBidder The leading bidder /// @param startTime The timestamp the auction starts /// @param endTime The timestamp the auction ends /// @param settled If the auction has been settled struct Auction { uint256 tokenId; uint256 highestBid; address highestBidder; uint40 startTime; uint40 endTime; bool settled; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IEIP712 /// @author Rohan Kulkarni /// @notice The external EIP712 errors and functions interface IEIP712 { /// /// /// ERRORS /// /// /// /// @dev Reverts if the deadline has passed to submit a signature error EXPIRED_SIGNATURE(); /// @dev Reverts if the recovered signature is invalid error INVALID_SIGNATURE(); /// /// /// FUNCTIONS /// /// /// /// @notice The sig nonce for an account /// @param account The account address function nonce(address account) external view returns (uint256); /// @notice The EIP-712 domain separator function DOMAIN_SEPARATOR() external view returns (bytes32); } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IERC1967Upgrade /// @author Rohan Kulkarni /// @notice The external ERC1967Upgrade events and errors interface IERC1967Upgrade { /// /// /// EVENTS /// /// /// /// @notice Emitted when the implementation is upgraded /// @param impl The address of the implementation event Upgraded(address impl); /// /// /// ERRORS /// /// /// /// @dev Reverts if an implementation is an invalid upgrade /// @param impl The address of the invalid implementation error INVALID_UPGRADE(address impl); /// @dev Reverts if an implementation upgrade is not stored at the storage slot of the original error UNSUPPORTED_UUID(); /// @dev Reverts if an implementation does not support ERC1822 proxiableUUID() error ONLY_UUPS(); } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IERC721 /// @author Rohan Kulkarni /// @notice The external ERC721 events, errors, and functions interface IERC721 { /// /// /// EVENTS /// /// /// /// @notice Emitted when a token is transferred from sender to recipient /// @param from The sender address /// @param to The recipient address /// @param tokenId The ERC-721 token id event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /// @notice Emitted when an owner approves an account to manage a token /// @param owner The owner address /// @param approved The account address /// @param tokenId The ERC-721 token id event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /// @notice Emitted when an owner sets an approval for a spender to manage all tokens /// @param owner The owner address /// @param operator The spender address /// @param approved If the approval is being set or removed event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /// /// /// ERRORS /// /// /// /// @dev Reverts if a caller is not authorized to approve or transfer a token error INVALID_APPROVAL(); /// @dev Reverts if a transfer is called with the incorrect token owner error INVALID_OWNER(); /// @dev Reverts if a transfer is attempted to address(0) error INVALID_RECIPIENT(); /// @dev Reverts if an existing token is called to be minted error ALREADY_MINTED(); /// @dev Reverts if a non-existent token is called to be burned error NOT_MINTED(); /// /// /// FUNCTIONS /// /// /// /// @notice The number of tokens owned /// @param owner The owner address function balanceOf(address owner) external view returns (uint256); /// @notice The owner of a token /// @param tokenId The ERC-721 token id function ownerOf(uint256 tokenId) external view returns (address); /// @notice The account approved to manage a token /// @param tokenId The ERC-721 token id function getApproved(uint256 tokenId) external view returns (address); /// @notice If an operator is authorized to manage all of an owner's tokens /// @param owner The owner address /// @param operator The operator address function isApprovedForAll(address owner, address operator) external view returns (bool); /// @notice Authorizes an account to manage a token /// @param to The account address /// @param tokenId The ERC-721 token id function approve(address to, uint256 tokenId) external; /// @notice Authorizes an account to manage all tokens /// @param operator The account address /// @param approved If permission is being given or removed function setApprovalForAll(address operator, bool approved) external; /// @notice Safe transfers a token from sender to recipient with additional data /// @param from The sender address /// @param to The recipient address /// @param tokenId The ERC-721 token id /// @param data The additional data sent in the call to the recipient function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; /// @notice Safe transfers a token from sender to recipient /// @param from The sender address /// @param to The recipient address /// @param tokenId The ERC-721 token id function safeTransferFrom( address from, address to, uint256 tokenId ) external; /// @notice Transfers a token from sender to recipient /// @param from The sender address /// @param to The recipient address /// @param tokenId The ERC-721 token id function transferFrom( address from, address to, uint256 tokenId ) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IERC721 } from "./IERC721.sol"; import { IEIP712 } from "./IEIP712.sol"; /// @title IERC721Votes /// @author Rohan Kulkarni /// @notice The external ERC721Votes events, errors, and functions interface IERC721Votes is IERC721, IEIP712 { /// /// /// EVENTS /// /// /// /// @notice Emitted when an account changes their delegate event DelegateChanged(address indexed delegator, address indexed from, address indexed to); /// @notice Emitted when a delegate's number of votes is updated event DelegateVotesChanged(address indexed delegate, uint256 prevTotalVotes, uint256 newTotalVotes); /// /// /// ERRORS /// /// /// /// @dev Reverts if the timestamp provided isn't in the past error INVALID_TIMESTAMP(); /// /// /// STRUCTS /// /// /// /// @notice The checkpoint data type /// @param timestamp The recorded timestamp /// @param votes The voting weight struct Checkpoint { uint64 timestamp; uint192 votes; } /// /// /// FUNCTIONS /// /// /// /// @notice The current number of votes for an account /// @param account The account address function getVotes(address account) external view returns (uint256); /// @notice The number of votes for an account at a past timestamp /// @param account The account address /// @param timestamp The past timestamp function getPastVotes(address account, uint256 timestamp) external view returns (uint256); /// @notice The delegate for an account /// @param account The account address function delegates(address account) external view returns (address); /// @notice Delegates votes to an account /// @param to The address delegating votes to function delegate(address to) external; /// @notice Delegates votes from a signer to an account /// @param from The address delegating votes from /// @param to The address delegating votes to /// @param deadline The signature deadline /// @param v The 129th byte and chain id of the signature /// @param r The first 64 bytes of the signature /// @param s Bytes 64-128 of the signature function delegateBySig( address from, address to, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IInitializable /// @author Rohan Kulkarni /// @notice The external Initializable events and errors interface IInitializable { /// /// /// EVENTS /// /// /// /// @notice Emitted when the contract has been initialized or reinitialized event Initialized(uint256 version); /// /// /// ERRORS /// /// /// /// @dev Reverts if incorrectly initialized with address(0) error ADDRESS_ZERO(); /// @dev Reverts if disabling initializers during initialization error INITIALIZING(); /// @dev Reverts if calling an initialization function outside of initialization error NOT_INITIALIZING(); /// @dev Reverts if reinitializing incorrectly error ALREADY_INITIALIZED(); } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IOwnable /// @author Rohan Kulkarni /// @notice The external Ownable events, errors, and functions interface IOwnable { /// /// /// EVENTS /// /// /// /// @notice Emitted when ownership has been updated /// @param prevOwner The previous owner address /// @param newOwner The new owner address event OwnerUpdated(address indexed prevOwner, address indexed newOwner); /// @notice Emitted when an ownership transfer is pending /// @param owner The current owner address /// @param pendingOwner The pending new owner address event OwnerPending(address indexed owner, address indexed pendingOwner); /// @notice Emitted when a pending ownership transfer has been canceled /// @param owner The current owner address /// @param canceledOwner The canceled owner address event OwnerCanceled(address indexed owner, address indexed canceledOwner); /// /// /// ERRORS /// /// /// /// @dev Reverts if an unauthorized user calls an owner function error ONLY_OWNER(); /// @dev Reverts if an unauthorized user calls a pending owner function error ONLY_PENDING_OWNER(); /// /// /// FUNCTIONS /// /// /// /// @notice The address of the owner function owner() external view returns (address); /// @notice The address of the pending owner function pendingOwner() external view returns (address); /// @notice Forces an ownership transfer /// @param newOwner The new owner address function transferOwnership(address newOwner) external; /// @notice Initiates a two-step ownership transfer /// @param newOwner The new owner address function safeTransferOwnership(address newOwner) external; /// @notice Accepts an ownership transfer function acceptOwnership() external; /// @notice Cancels a pending ownership transfer function cancelOwnershipTransfer() external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title IPausable /// @author Rohan Kulkarni /// @notice The external Pausable events, errors, and functions interface IPausable { /// /// /// EVENTS /// /// /// /// @notice Emitted when the contract is paused /// @param user The address that paused the contract event Paused(address user); /// @notice Emitted when the contract is unpaused /// @param user The address that unpaused the contract event Unpaused(address user); /// /// /// ERRORS /// /// /// /// @dev Reverts if called when the contract is paused error PAUSED(); /// @dev Reverts if called when the contract is unpaused error UNPAUSED(); /// /// /// FUNCTIONS /// /// /// /// @notice If the contract is paused function paused() external view returns (bool); } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import { IERC1822Proxiable } from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; import { IERC1967Upgrade } from "./IERC1967Upgrade.sol"; /// @title IUUPS /// @author Rohan Kulkarni /// @notice The external UUPS errors and functions interface IUUPS is IERC1967Upgrade, IERC1822Proxiable { /// /// /// ERRORS /// /// /// /// @dev Reverts if not called directly error ONLY_CALL(); /// @dev Reverts if not called via delegatecall error ONLY_DELEGATECALL(); /// @dev Reverts if not called via proxy error ONLY_PROXY(); /// /// /// FUNCTIONS /// /// /// /// @notice Upgrades to an implementation /// @param newImpl The new implementation address function upgradeTo(address newImpl) external; /// @notice Upgrades to an implementation with an additional function call /// @param newImpl The new implementation address /// @param data The encoded function call function upgradeToAndCall(address newImpl, bytes memory data) external payable; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.16; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title IWETH interface IWETH is IERC20 { function deposit() external payable; function withdraw(uint256 wad) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IERC1822Proxiable } from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; import { StorageSlot } from "@openzeppelin/contracts/utils/StorageSlot.sol"; import { IERC1967Upgrade } from "../interfaces/IERC1967Upgrade.sol"; import { Address } from "../utils/Address.sol"; /// @title ERC1967Upgrade /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (proxy/ERC1967/ERC1967Upgrade.sol) /// - Uses custom errors declared in IERC1967Upgrade /// - Removes ERC1967 admin and beacon support abstract contract ERC1967Upgrade is IERC1967Upgrade { /// /// /// CONSTANTS /// /// /// /// @dev bytes32(uint256(keccak256('eip1967.proxy.rollback')) - 1) bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /// @dev bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /// /// /// FUNCTIONS /// /// /// /// @dev Upgrades to an implementation with security checks for UUPS proxies and an additional function call /// @param _newImpl The new implementation address /// @param _data The encoded function call function _upgradeToAndCallUUPS( address _newImpl, bytes memory _data, bool _forceCall ) internal { if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) { _setImplementation(_newImpl); } else { try IERC1822Proxiable(_newImpl).proxiableUUID() returns (bytes32 slot) { if (slot != _IMPLEMENTATION_SLOT) revert UNSUPPORTED_UUID(); } catch { revert ONLY_UUPS(); } _upgradeToAndCall(_newImpl, _data, _forceCall); } } /// @dev Upgrades to an implementation with an additional function call /// @param _newImpl The new implementation address /// @param _data The encoded function call function _upgradeToAndCall( address _newImpl, bytes memory _data, bool _forceCall ) internal { _upgradeTo(_newImpl); if (_data.length > 0 || _forceCall) { Address.functionDelegateCall(_newImpl, _data); } } /// @dev Performs an implementation upgrade /// @param _newImpl The new implementation address function _upgradeTo(address _newImpl) internal { _setImplementation(_newImpl); emit Upgraded(_newImpl); } /// @dev Stores the address of an implementation /// @param _impl The implementation address function _setImplementation(address _impl) private { if (!Address.isContract(_impl)) revert INVALID_UPGRADE(_impl); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = _impl; } /// @dev The address of the current implementation function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IUUPS } from "../interfaces/IUUPS.sol"; import { ERC1967Upgrade } from "./ERC1967Upgrade.sol"; /// @title UUPS /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (proxy/utils/UUPSUpgradeable.sol) /// - Uses custom errors declared in IUUPS /// - Inherits a modern, minimal ERC1967Upgrade abstract contract UUPS is IUUPS, ERC1967Upgrade { /// /// /// IMMUTABLES /// /// /// /// @dev The address of the implementation address private immutable __self = address(this); /// /// /// MODIFIERS /// /// /// /// @dev Ensures that execution is via proxy delegatecall with the correct implementation modifier onlyProxy() { if (address(this) == __self) revert ONLY_DELEGATECALL(); if (_getImplementation() != __self) revert ONLY_PROXY(); _; } /// @dev Ensures that execution is via direct call modifier notDelegated() { if (address(this) != __self) revert ONLY_CALL(); _; } /// /// /// FUNCTIONS /// /// /// /// @dev Hook to authorize an implementation upgrade /// @param _newImpl The new implementation address function _authorizeUpgrade(address _newImpl) internal virtual; /// @notice Upgrades to an implementation /// @param _newImpl The new implementation address function upgradeTo(address _newImpl) external onlyProxy { _authorizeUpgrade(_newImpl); _upgradeToAndCallUUPS(_newImpl, "", false); } /// @notice Upgrades to an implementation with an additional function call /// @param _newImpl The new implementation address /// @param _data The encoded function call function upgradeToAndCall(address _newImpl, bytes memory _data) external payable onlyProxy { _authorizeUpgrade(_newImpl); _upgradeToAndCallUUPS(_newImpl, _data, true); } /// @notice The storage slot of the implementation address function proxiableUUID() external view notDelegated returns (bytes32) { return _IMPLEMENTATION_SLOT; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IERC721 } from "../interfaces/IERC721.sol"; import { Initializable } from "../utils/Initializable.sol"; import { ERC721TokenReceiver } from "../utils/TokenReceiver.sol"; import { Address } from "../utils/Address.sol"; /// @title ERC721 /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC721/ERC721Upgradeable.sol) /// - Uses custom errors declared in IERC721 abstract contract ERC721 is IERC721, Initializable { /// /// /// STORAGE /// /// /// /// @notice The token name string public name; /// @notice The token symbol string public symbol; /// @notice The token owners /// @dev ERC-721 token id => Owner mapping(uint256 => address) internal owners; /// @notice The owner balances /// @dev Owner => Balance mapping(address => uint256) internal balances; /// @notice The token approvals /// @dev ERC-721 token id => Manager mapping(uint256 => address) internal tokenApprovals; /// @notice The balance approvals /// @dev Owner => Operator => Approved mapping(address => mapping(address => bool)) internal operatorApprovals; /// /// /// FUNCTIONS /// /// /// /// @dev Initializes an ERC-721 token /// @param _name The ERC-721 token name /// @param _symbol The ERC-721 token symbol function __ERC721_init(string memory _name, string memory _symbol) internal onlyInitializing { name = _name; symbol = _symbol; } /// @notice The token URI /// @param _tokenId The ERC-721 token id function tokenURI(uint256 _tokenId) public view virtual returns (string memory) {} /// @notice The contract URI function contractURI() public view virtual returns (string memory) {} /// @notice If the contract implements an interface /// @param _interfaceId The interface id function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { return _interfaceId == 0x01ffc9a7 || // ERC165 Interface ID _interfaceId == 0x80ac58cd || // ERC721 Interface ID _interfaceId == 0x5b5e139f; // ERC721Metadata Interface ID } /// @notice The account approved to manage a token /// @param _tokenId The ERC-721 token id function getApproved(uint256 _tokenId) external view returns (address) { return tokenApprovals[_tokenId]; } /// @notice If an operator is authorized to manage all of an owner's tokens /// @param _owner The owner address /// @param _operator The operator address function isApprovedForAll(address _owner, address _operator) external view returns (bool) { return operatorApprovals[_owner][_operator]; } /// @notice The number of tokens owned /// @param _owner The owner address function balanceOf(address _owner) public view returns (uint256) { if (_owner == address(0)) revert ADDRESS_ZERO(); return balances[_owner]; } /// @notice The owner of a token /// @param _tokenId The ERC-721 token id function ownerOf(uint256 _tokenId) public view returns (address) { address owner = owners[_tokenId]; if (owner == address(0)) revert INVALID_OWNER(); return owner; } /// @notice Authorizes an account to manage a token /// @param _to The account address /// @param _tokenId The ERC-721 token id function approve(address _to, uint256 _tokenId) external { address owner = owners[_tokenId]; if (msg.sender != owner && !operatorApprovals[owner][msg.sender]) revert INVALID_APPROVAL(); tokenApprovals[_tokenId] = _to; emit Approval(owner, _to, _tokenId); } /// @notice Authorizes an account to manage all tokens /// @param _operator The account address /// @param _approved If permission is being given or removed function setApprovalForAll(address _operator, bool _approved) external { operatorApprovals[msg.sender][_operator] = _approved; emit ApprovalForAll(msg.sender, _operator, _approved); } /// @notice Transfers a token from sender to recipient /// @param _from The sender address /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function transferFrom( address _from, address _to, uint256 _tokenId ) public { if (_from != owners[_tokenId]) revert INVALID_OWNER(); if (_to == address(0)) revert ADDRESS_ZERO(); if (msg.sender != _from && !operatorApprovals[_from][msg.sender] && msg.sender != tokenApprovals[_tokenId]) revert INVALID_APPROVAL(); _beforeTokenTransfer(_from, _to, _tokenId); unchecked { --balances[_from]; ++balances[_to]; } owners[_tokenId] = _to; delete tokenApprovals[_tokenId]; emit Transfer(_from, _to, _tokenId); _afterTokenTransfer(_from, _to, _tokenId); } /// @notice Safe transfers a token from sender to recipient /// @param _from The sender address /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function safeTransferFrom( address _from, address _to, uint256 _tokenId ) external { transferFrom(_from, _to, _tokenId); if ( Address.isContract(_to) && ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, "") != ERC721TokenReceiver.onERC721Received.selector ) revert INVALID_RECIPIENT(); } /// @notice Safe transfers a token from sender to recipient with additional data /// @param _from The sender address /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes calldata _data ) external { transferFrom(_from, _to, _tokenId); if ( Address.isContract(_to) && ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) != ERC721TokenReceiver.onERC721Received.selector ) revert INVALID_RECIPIENT(); } /// @dev Mints a token to a recipient /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function _mint(address _to, uint256 _tokenId) internal virtual { if (_to == address(0)) revert ADDRESS_ZERO(); if (owners[_tokenId] != address(0)) revert ALREADY_MINTED(); _beforeTokenTransfer(address(0), _to, _tokenId); unchecked { ++balances[_to]; } owners[_tokenId] = _to; emit Transfer(address(0), _to, _tokenId); _afterTokenTransfer(address(0), _to, _tokenId); } /// @dev Burns a token to a recipient /// @param _tokenId The ERC-721 token id function _burn(uint256 _tokenId) internal virtual { address owner = owners[_tokenId]; if (owner == address(0)) revert NOT_MINTED(); _beforeTokenTransfer(owner, address(0), _tokenId); unchecked { --balances[owner]; } delete owners[_tokenId]; delete tokenApprovals[_tokenId]; emit Transfer(owner, address(0), _tokenId); _afterTokenTransfer(owner, address(0), _tokenId); } /// @dev Hook called before a token transfer /// @param _from The sender address /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function _beforeTokenTransfer( address _from, address _to, uint256 _tokenId ) internal virtual {} /// @dev Hook called after a token transfer /// @param _from The sender address /// @param _to The recipient address /// @param _tokenId The ERC-721 token id function _afterTokenTransfer( address _from, address _to, uint256 _tokenId ) internal virtual {} } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IERC721Votes } from "../interfaces/IERC721Votes.sol"; import { ERC721 } from "../token/ERC721.sol"; import { EIP712 } from "../utils/EIP712.sol"; /// @title ERC721Votes /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC721/extensions/draft-ERC721Votes.sol) & Nouns DAO ERC721Checkpointable.sol commit 2cbe6c7 - licensed under the BSD-3-Clause license. /// - Uses custom errors defined in IERC721Votes /// - Checkpoints are based on timestamps instead of block numbers /// - Tokens are self-delegated by default /// - The total number of votes is the token supply itself abstract contract ERC721Votes is IERC721Votes, EIP712, ERC721 { /// /// /// CONSTANTS /// /// /// /// @dev The EIP-712 typehash to delegate with a signature bytes32 internal constant DELEGATION_TYPEHASH = keccak256("Delegation(address from,address to,uint256 nonce,uint256 deadline)"); /// /// /// STORAGE /// /// /// /// @notice The delegate for an account /// @notice Account => Delegate mapping(address => address) internal delegation; /// @notice The number of checkpoints for an account /// @dev Account => Num Checkpoints mapping(address => uint256) internal numCheckpoints; /// @notice The checkpoint for an account /// @dev Account => Checkpoint Id => Checkpoint mapping(address => mapping(uint256 => Checkpoint)) internal checkpoints; /// /// /// VOTING WEIGHT /// /// /// /// @notice The current number of votes for an account /// @param _account The account address function getVotes(address _account) public view returns (uint256) { // Get the account's number of checkpoints uint256 nCheckpoints = numCheckpoints[_account]; // Cannot underflow as `nCheckpoints` is ensured to be greater than 0 if reached unchecked { // Return the number of votes at the latest checkpoint if applicable return nCheckpoints != 0 ? checkpoints[_account][nCheckpoints - 1].votes : 0; } } /// @notice The number of votes for an account at a past timestamp /// @param _account The account address /// @param _timestamp The past timestamp function getPastVotes(address _account, uint256 _timestamp) public view returns (uint256) { // Ensure the given timestamp is in the past if (_timestamp >= block.timestamp) revert INVALID_TIMESTAMP(); // Get the account's number of checkpoints uint256 nCheckpoints = numCheckpoints[_account]; // If there are none return 0 if (nCheckpoints == 0) return 0; // Get the account's checkpoints mapping(uint256 => Checkpoint) storage accountCheckpoints = checkpoints[_account]; unchecked { // Get the latest checkpoint id // Cannot underflow as `nCheckpoints` is ensured to be greater than 0 uint256 lastCheckpoint = nCheckpoints - 1; // If the latest checkpoint has a valid timestamp, return its number of votes if (accountCheckpoints[lastCheckpoint].timestamp <= _timestamp) return accountCheckpoints[lastCheckpoint].votes; // If the first checkpoint doesn't have a valid timestamp, return 0 if (accountCheckpoints[0].timestamp > _timestamp) return 0; // Otherwise, find a checkpoint with a valid timestamp // Use the latest id as the initial upper bound uint256 high = lastCheckpoint; uint256 low; uint256 middle; // Used to temporarily hold a checkpoint Checkpoint memory cp; // While a valid checkpoint is to be found: while (high > low) { // Find the id of the middle checkpoint middle = high - (high - low) / 2; // Get the middle checkpoint cp = accountCheckpoints[middle]; // If the timestamp is a match: if (cp.timestamp == _timestamp) { // Return the voting weight return cp.votes; // Else if the timestamp is before the one looking for: } else if (cp.timestamp < _timestamp) { // Update the lower bound low = middle; // Else update the upper bound } else { high = middle - 1; } } return accountCheckpoints[low].votes; } } /// /// /// DELEGATION /// /// /// /// @notice The delegate for an account /// @param _account The account address function delegates(address _account) public view returns (address) { address current = delegation[_account]; return current == address(0) ? _account : current; } /// @notice Delegates votes to an account /// @param _to The address delegating votes to function delegate(address _to) external { _delegate(msg.sender, _to); } /// @notice Delegates votes from a signer to an account /// @param _from The address delegating votes from /// @param _to The address delegating votes to /// @param _deadline The signature deadline /// @param _v The 129th byte and chain id of the signature /// @param _r The first 64 bytes of the signature /// @param _s Bytes 64-128 of the signature function delegateBySig( address _from, address _to, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s ) external { // Ensure the signature has not expired if (block.timestamp > _deadline) revert EXPIRED_SIGNATURE(); // Used to store the digest bytes32 digest; // Cannot realistically overflow unchecked { // Compute the hash of the domain seperator with the typed delegation data digest = keccak256( abi.encodePacked("\\x19\\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(DELEGATION_TYPEHASH, _from, _to, nonces[_from]++, _deadline))) ); } // Recover the message signer address recoveredAddress = ecrecover(digest, _v, _r, _s); // Ensure the recovered signer is the voter if (recoveredAddress == address(0) || recoveredAddress != _from) revert INVALID_SIGNATURE(); // Update the delegate _delegate(_from, _to); } /// @dev Updates delegate addresses /// @param _from The address delegating votes from /// @param _to The address delegating votes to function _delegate(address _from, address _to) internal { // If address(0) is being delegated to, update the op as a self-delegate if (_to == address(0)) _to = _from; // Get the previous delegate address prevDelegate = delegates(_from); // Store the new delegate delegation[_from] = _to; emit DelegateChanged(_from, prevDelegate, _to); // Transfer voting weight from the previous delegate to the new delegate _moveDelegateVotes(prevDelegate, _to, balanceOf(_from)); } /// @dev Transfers voting weight /// @param _from The address delegating votes from /// @param _to The address delegating votes to /// @param _amount The number of votes delegating function _moveDelegateVotes( address _from, address _to, uint256 _amount ) internal { unchecked { // If voting weight is being transferred: if (_from != _to && _amount > 0) { // If this isn't a token mint: if (_from != address(0)) { // Get the sender's number of checkpoints uint256 newCheckpointId = numCheckpoints[_from]; // Used to store their previous checkpoint id uint256 prevCheckpointId; // Used to store their previous checkpoint's voting weight uint256 prevTotalVotes; // Used to store their previous checkpoint's timestamp uint256 prevTimestamp; // If this isn't the sender's first checkpoint: if (newCheckpointId != 0) { // Get their previous checkpoint's id prevCheckpointId = newCheckpointId - 1; // Get their previous checkpoint's voting weight prevTotalVotes = checkpoints[_from][prevCheckpointId].votes; // Get their previous checkpoint's timestamp prevTimestamp = checkpoints[_from][prevCheckpointId].timestamp; } // Update their voting weight _writeCheckpoint(_from, newCheckpointId, prevCheckpointId, prevTimestamp, prevTotalVotes, prevTotalVotes - _amount); } // If this isn't a token burn: if (_to != address(0)) { // Get the recipients's number of checkpoints uint256 nCheckpoints = numCheckpoints[_to]; // Used to store their previous checkpoint id uint256 prevCheckpointId; // Used to store their previous checkpoint's voting weight uint256 prevTotalVotes; // Used to store their previous checkpoint's timestamp uint256 prevTimestamp; // If this isn't the recipient's first checkpoint: if (nCheckpoints != 0) { // Get their previous checkpoint's id prevCheckpointId = nCheckpoints - 1; // Get their previous checkpoint's voting weight prevTotalVotes = checkpoints[_to][prevCheckpointId].votes; // Get their previous checkpoint's timestamp prevTimestamp = checkpoints[_to][prevCheckpointId].timestamp; } // Update their voting weight _writeCheckpoint(_to, nCheckpoints, prevCheckpointId, prevTimestamp, prevTotalVotes, prevTotalVotes + _amount); } } } } /// @dev Records a checkpoint /// @param _account The account address /// @param _newId The new checkpoint id /// @param _prevId The previous checkpoint id /// @param _prevTimestamp The previous checkpoint timestamp /// @param _prevTotalVotes The previous checkpoint voting weight /// @param _newTotalVotes The new checkpoint voting weight function _writeCheckpoint( address _account, uint256 _newId, uint256 _prevId, uint256 _prevTimestamp, uint256 _prevTotalVotes, uint256 _newTotalVotes ) private { unchecked { // If the new checkpoint is not the user's first AND has the timestamp of the previous checkpoint: if (_newId > 0 && _prevTimestamp == block.timestamp) { // Just update the previous checkpoint's votes checkpoints[_account][_prevId].votes = uint192(_newTotalVotes); // Else write a new checkpoint: } else { // Get the pointer to store the checkpoint Checkpoint storage checkpoint = checkpoints[_account][_newId]; // Store the new voting weight and the current time checkpoint.votes = uint192(_newTotalVotes); checkpoint.timestamp = uint64(block.timestamp); // Increment the account's number of checkpoints ++numCheckpoints[_account]; } emit DelegateVotesChanged(_account, _prevTotalVotes, _newTotalVotes); } } /// @dev Enables each NFT to equal 1 vote /// @param _from The token sender /// @param _to The token recipient /// @param _tokenId The ERC-721 token id function _afterTokenTransfer( address _from, address _to, uint256 _tokenId ) internal override { // Transfer 1 vote from the sender to the recipient _moveDelegateVotes(delegates(_from), delegates(_to), 1); super._afterTokenTransfer(_from, _to, _tokenId); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @title EIP712 /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (utils/Address.sol) /// - Uses custom errors `INVALID_TARGET()` & `DELEGATE_CALL_FAILED()` /// - Adds util converting address to bytes32 library Address { /// /// /// ERRORS /// /// /// /// @dev Reverts if the target of a delegatecall is not a contract error INVALID_TARGET(); /// @dev Reverts if a delegatecall has failed error DELEGATE_CALL_FAILED(); /// /// /// FUNCTIONS /// /// /// /// @dev Utility to convert an address to bytes32 function toBytes32(address _account) internal pure returns (bytes32) { return bytes32(uint256(uint160(_account)) << 96); } /// @dev If an address is a contract function isContract(address _account) internal view returns (bool rv) { assembly { rv := gt(extcodesize(_account), 0) } } /// @dev Performs a delegatecall on an address function functionDelegateCall(address _target, bytes memory _data) internal returns (bytes memory) { if (!isContract(_target)) revert INVALID_TARGET(); (bool success, bytes memory returndata) = _target.delegatecall(_data); return verifyCallResult(success, returndata); } /// @dev Verifies a delegatecall was successful function verifyCallResult(bool _success, bytes memory _returndata) internal pure returns (bytes memory) { if (_success) { return _returndata; } else { if (_returndata.length > 0) { assembly { let returndata_size := mload(_returndata) revert(add(32, _returndata), returndata_size) } } else { revert DELEGATE_CALL_FAILED(); } } } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IEIP712 } from "../interfaces/IEIP712.sol"; import { Initializable } from "../utils/Initializable.sol"; /// @title EIP712 /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (utils/cryptography/draft-EIP712Upgradeable.sol) /// - Uses custom errors declared in IEIP712 /// - Caches `INITIAL_CHAIN_ID` and `INITIAL_DOMAIN_SEPARATOR` upon initialization /// - Adds mapping for account nonces abstract contract EIP712 is IEIP712, Initializable { /// /// /// CONSTANTS /// /// /// /// @dev The EIP-712 domain typehash bytes32 internal constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); /// /// /// STORAGE /// /// /// /// @notice The hash of the EIP-712 domain name bytes32 internal HASHED_NAME; /// @notice The hash of the EIP-712 domain version bytes32 internal HASHED_VERSION; /// @notice The domain separator computed upon initialization bytes32 internal INITIAL_DOMAIN_SEPARATOR; /// @notice The chain id upon initialization uint256 internal INITIAL_CHAIN_ID; /// @notice The account nonces /// @dev Account => Nonce mapping(address => uint256) internal nonces; /// /// /// FUNCTIONS /// /// /// /// @dev Initializes EIP-712 support /// @param _name The EIP-712 domain name /// @param _version The EIP-712 domain version function __EIP712_init(string memory _name, string memory _version) internal onlyInitializing { HASHED_NAME = keccak256(bytes(_name)); HASHED_VERSION = keccak256(bytes(_version)); INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); } /// @notice The current nonce for an account /// @param _account The account address function nonce(address _account) external view returns (uint256) { return nonces[_account]; } /// @notice The EIP-712 domain separator function DOMAIN_SEPARATOR() public view returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); } /// @dev Computes the EIP-712 domain separator function _computeDomainSeparator() private view returns (bytes32) { return keccak256(abi.encode(DOMAIN_TYPEHASH, HASHED_NAME, HASHED_VERSION, block.chainid, address(this))); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IInitializable } from "../interfaces/IInitializable.sol"; import { Address } from "../utils/Address.sol"; /// @title Initializable /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (proxy/utils/Initializable.sol) /// - Uses custom errors declared in IInitializable abstract contract Initializable is IInitializable { /// /// /// STORAGE /// /// /// /// @dev Indicates the contract has been initialized uint8 internal _initialized; /// @dev Indicates the contract is being initialized bool internal _initializing; /// /// /// MODIFIERS /// /// /// /// @dev Ensures an initialization function is only called within an `initializer` or `reinitializer` function modifier onlyInitializing() { if (!_initializing) revert NOT_INITIALIZING(); _; } /// @dev Enables initializing upgradeable contracts modifier initializer() { bool isTopLevelCall = !_initializing; if ((!isTopLevelCall || _initialized != 0) && (Address.isContract(address(this)) || _initialized != 1)) revert ALREADY_INITIALIZED(); _initialized = 1; if (isTopLevelCall) { _initializing = true; } _; if (isTopLevelCall) { _initializing = false; emit Initialized(1); } } /// @dev Enables initializer versioning /// @param _version The version to set modifier reinitializer(uint8 _version) { if (_initializing || _initialized >= _version) revert ALREADY_INITIALIZED(); _initialized = _version; _initializing = true; _; _initializing = false; emit Initialized(_version); } /// /// /// FUNCTIONS /// /// /// /// @dev Prevents future initialization function _disableInitializers() internal virtual { if (_initializing) revert INITIALIZING(); if (_initialized < type(uint8).max) { _initialized = type(uint8).max; emit Initialized(type(uint8).max); } } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IOwnable } from "../interfaces/IOwnable.sol"; import { Initializable } from "../utils/Initializable.sol"; /// @title Ownable /// @author Rohan Kulkarni /// @notice Modified from OpenZeppelin Contracts v4.7.3 (access/OwnableUpgradeable.sol) /// - Uses custom errors declared in IOwnable /// - Adds optional two-step ownership transfer (`safeTransferOwnership` + `acceptOwnership`) abstract contract Ownable is IOwnable, Initializable { /// /// /// STORAGE /// /// /// /// @dev The address of the owner address internal _owner; /// @dev The address of the pending owner address internal _pendingOwner; /// /// /// MODIFIERS /// /// /// /// @dev Ensures the caller is the owner modifier onlyOwner() { if (msg.sender != _owner) revert ONLY_OWNER(); _; } /// @dev Ensures the caller is the pending owner modifier onlyPendingOwner() { if (msg.sender != _pendingOwner) revert ONLY_PENDING_OWNER(); _; } /// /// /// FUNCTIONS /// /// /// /// @dev Initializes contract ownership /// @param _initialOwner The initial owner address function __Ownable_init(address _initialOwner) internal onlyInitializing { _owner = _initialOwner; emit OwnerUpdated(address(0), _initialOwner); } /// @notice The address of the owner function owner() public virtual view returns (address) { return _owner; } /// @notice The address of the pending owner function pendingOwner() public view returns (address) { return _pendingOwner; } /// @notice Forces an ownership transfer from the last owner /// @param _newOwner The new owner address function transferOwnership(address _newOwner) public onlyOwner { _transferOwnership(_newOwner); } /// @notice Forces an ownership transfer from any sender /// @param _newOwner New owner to transfer contract to /// @dev Ensure is called only from trusted internal code, no access control checks. function _transferOwnership(address _newOwner) internal { emit OwnerUpdated(_owner, _newOwner); _owner = _newOwner; if (_pendingOwner != address(0)) delete _pendingOwner; } /// @notice Initiates a two-step ownership transfer /// @param _newOwner The new owner address function safeTransferOwnership(address _newOwner) public onlyOwner { _pendingOwner = _newOwner; emit OwnerPending(_owner, _newOwner); } /// @notice Accepts an ownership transfer function acceptOwnership() public onlyPendingOwner { emit OwnerUpdated(_owner, msg.sender); _owner = _pendingOwner; delete _pendingOwner; } /// @notice Cancels a pending ownership transfer function cancelOwnershipTransfer() public onlyOwner { emit OwnerCanceled(_owner, _pendingOwner); delete _pendingOwner; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IPausable } from "../interfaces/IPausable.sol"; import { Initializable } from "../utils/Initializable.sol"; /// @notice Modified from OpenZeppelin Contracts v4.7.3 (security/PausableUpgradeable.sol) /// - Uses custom errors declared in IPausable abstract contract Pausable is IPausable, Initializable { /// /// /// STORAGE /// /// /// /// @dev If the contract is paused bool internal _paused; /// /// /// MODIFIERS /// /// /// /// @dev Ensures the contract is paused modifier whenPaused() { if (!_paused) revert UNPAUSED(); _; } /// @dev Ensures the contract isn't paused modifier whenNotPaused() { if (_paused) revert PAUSED(); _; } /// /// /// FUNCTIONS /// /// /// /// @dev Sets whether the initial state /// @param _initPause If the contract should pause upon initialization function __Pausable_init(bool _initPause) internal onlyInitializing { _paused = _initPause; } /// @notice If the contract is paused function paused() external view returns (bool) { return _paused; } /// @dev Pauses the contract function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(msg.sender); } /// @dev Unpauses the contract function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(msg.sender); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { Initializable } from "../utils/Initializable.sol"; /// @notice Modified from OpenZeppelin Contracts v4.7.3 (security/ReentrancyGuardUpgradeable.sol) /// - Uses custom error `REENTRANCY()` abstract contract ReentrancyGuard is Initializable { /// /// /// STORAGE /// /// /// /// @dev Indicates a function has not been entered uint256 internal constant _NOT_ENTERED = 1; /// @dev Indicates a function has been entered uint256 internal constant _ENTERED = 2; /// @notice The reentrancy status of a function uint256 internal _status; /// /// /// ERRORS /// /// /// /// @dev Reverts if attempted reentrancy error REENTRANCY(); /// /// /// FUNCTIONS /// /// /// /// @dev Initializes the reentrancy guard function __ReentrancyGuard_init() internal onlyInitializing { _status = _NOT_ENTERED; } /// @dev Ensures a function cannot be reentered modifier nonReentrant() { if (_status == _ENTERED) revert REENTRANCY(); _status = _ENTERED; _; _status = _NOT_ENTERED; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; /// @notice Modified from OpenZeppelin Contracts v4.7.3 (utils/math/SafeCast.sol) /// - Uses custom error `UNSAFE_CAST()` library SafeCast { error UNSAFE_CAST(); function toUint128(uint256 x) internal pure returns (uint128) { if (x > type(uint128).max) revert UNSAFE_CAST(); return uint128(x); } function toUint64(uint256 x) internal pure returns (uint64) { if (x > type(uint64).max) revert UNSAFE_CAST(); return uint64(x); } function toUint48(uint256 x) internal pure returns (uint48) { if (x > type(uint48).max) revert UNSAFE_CAST(); return uint48(x); } function toUint40(uint256 x) internal pure returns (uint40) { if (x > type(uint40).max) revert UNSAFE_CAST(); return uint40(x); } function toUint32(uint256 x) internal pure returns (uint32) { if (x > type(uint32).max) revert UNSAFE_CAST(); return uint32(x); } function toUint16(uint256 x) internal pure returns (uint16) { if (x > type(uint16).max) revert UNSAFE_CAST(); return uint16(x); } function toUint8(uint256 x) internal pure returns (uint8) { if (x > type(uint8).max) revert UNSAFE_CAST(); return uint8(x); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; /// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC721/utils/ERC721Holder.sol) abstract contract ERC721TokenReceiver { function onERC721Received( address, address, uint256, bytes calldata ) external virtual returns (bytes4) { return this.onERC721Received.selector; } } /// @notice Modified from OpenZeppelin Contracts v4.7.3 (token/ERC1155/utils/ERC1155Holder.sol) abstract contract ERC1155TokenReceiver { function onERC1155Received( address, address, uint256, uint256, bytes calldata ) external virtual returns (bytes4) { return this.onERC1155Received.selector; } function onERC1155BatchReceived( address, address, uint256[] calldata, uint256[] calldata, bytes calldata ) external virtual returns (bytes4) { return this.onERC1155BatchReceived.selector; } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IUUPS } from "../lib/interfaces/IUUPS.sol"; import { IOwnable } from "../lib/interfaces/IOwnable.sol"; /// @title IManager /// @author Rohan Kulkarni /// @notice The external Manager events, errors, structs and functions interface IManager is IUUPS, IOwnable { /// /// /// EVENTS /// /// /// /// @notice Emitted when a DAO is deployed /// @param token The ERC-721 token address /// @param metadata The metadata renderer address /// @param auction The auction address /// @param treasury The treasury address /// @param governor The governor address event DAODeployed(address token, address metadata, address auction, address treasury, address governor); /// @notice Emitted when an upgrade is registered by the Builder DAO /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address event UpgradeRegistered(address baseImpl, address upgradeImpl); /// @notice Emitted when an upgrade is unregistered by the Builder DAO /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address event UpgradeRemoved(address baseImpl, address upgradeImpl); /// /// /// ERRORS /// /// /// /// @dev Reverts if at least one founder is not provided upon deploy error FOUNDER_REQUIRED(); /// /// /// STRUCTS /// /// /// /// @notice The founder parameters /// @param wallet The wallet address /// @param ownershipPct The percent ownership of the token /// @param vestExpiry The timestamp that vesting expires struct FounderParams { address wallet; uint256 ownershipPct; uint256 vestExpiry; } /// @notice DAO Version Information information struct struct DAOVersionInfo { string token; string metadata; string auction; string treasury; string governor; } /// @notice The ERC-721 token parameters /// @param initStrings The encoded token name, symbol, collection description, collection image uri, renderer base uri struct TokenParams { bytes initStrings; } /// @notice The auction parameters /// @param reservePrice The reserve price of each auction /// @param duration The duration of each auction struct AuctionParams { uint256 reservePrice; uint256 duration; } /// @notice The governance parameters /// @param timelockDelay The time delay to execute a queued transaction /// @param votingDelay The time delay to vote on a created proposal /// @param votingPeriod The time period to vote on a proposal /// @param proposalThresholdBps The basis points of the token supply required to create a proposal /// @param quorumThresholdBps The basis points of the token supply required to reach quorum /// @param vetoer The address authorized to veto proposals (address(0) if none desired) struct GovParams { uint256 timelockDelay; uint256 votingDelay; uint256 votingPeriod; uint256 proposalThresholdBps; uint256 quorumThresholdBps; address vetoer; } /// /// /// FUNCTIONS /// /// /// /// @notice The token implementation address function tokenImpl() external view returns (address); /// @notice The metadata renderer implementation address function metadataImpl() external view returns (address); /// @notice The auction house implementation address function auctionImpl() external view returns (address); /// @notice The treasury implementation address function treasuryImpl() external view returns (address); /// @notice The governor implementation address function governorImpl() external view returns (address); /// @notice Deploys a DAO with custom token, auction, and governance settings /// @param founderParams The DAO founder(s) /// @param tokenParams The ERC-721 token settings /// @param auctionParams The auction settings /// @param govParams The governance settings function deploy( FounderParams[] calldata founderParams, TokenParams calldata tokenParams, AuctionParams calldata auctionParams, GovParams calldata govParams ) external returns ( address token, address metadataRenderer, address auction, address treasury, address governor ); /// @notice A DAO's remaining contract addresses from its token address /// @param token The ERC-721 token address function getAddresses(address token) external returns ( address metadataRenderer, address auction, address treasury, address governor ); /// @notice If an implementation is registered by the Builder DAO as an optional upgrade /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address function isRegisteredUpgrade(address baseImpl, address upgradeImpl) external view returns (bool); /// @notice Called by the Builder DAO to offer opt-in implementation upgrades for all other DAOs /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address function registerUpgrade(address baseImpl, address upgradeImpl) external; /// @notice Called by the Builder DAO to remove an upgrade /// @param baseImpl The base implementation address /// @param upgradeImpl The upgrade implementation address function removeUpgrade(address baseImpl, address upgradeImpl) external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IUUPS } from "../lib/interfaces/IUUPS.sol"; import { IERC721Votes } from "../lib/interfaces/IERC721Votes.sol"; import { IManager } from "../manager/IManager.sol"; import { TokenTypesV1 } from "./types/TokenTypesV1.sol"; /// @title IToken /// @author Rohan Kulkarni /// @notice The external Token events, errors and functions interface IToken is IUUPS, IERC721Votes, TokenTypesV1 { /// /// /// EVENTS /// /// /// /// @notice Emitted when a token is scheduled to be allocated /// @param baseTokenId The /// @param founderId The founder's id /// @param founder The founder's vesting details event MintScheduled(uint256 baseTokenId, uint256 founderId, Founder founder); /// @notice Emitted when a token allocation is unscheduled (removed) /// @param baseTokenId The token ID % 100 /// @param founderId The founder's id /// @param founder The founder's vesting details event MintUnscheduled(uint256 baseTokenId, uint256 founderId, Founder founder); /// @notice Emitted when a tokens founders are deleted from storage /// @param newFounders the list of founders event FounderAllocationsCleared(IManager.FounderParams[] newFounders); /// /// /// ERRORS /// /// /// /// @dev Reverts if the founder ownership exceeds 100 percent error INVALID_FOUNDER_OWNERSHIP(); /// @dev Reverts if the caller was not the auction contract error ONLY_AUCTION(); /// @dev Reverts if no metadata was generated upon mint error NO_METADATA_GENERATED(); /// @dev Reverts if the caller was not the contract manager error ONLY_MANAGER(); /// /// /// FUNCTIONS /// /// /// /// @notice Initializes a DAO's ERC-721 token /// @param founders The founding members to receive vesting allocations /// @param initStrings The encoded token and metadata initialization strings /// @param metadataRenderer The token's metadata renderer /// @param auction The token's auction house function initialize( IManager.FounderParams[] calldata founders, bytes calldata initStrings, address metadataRenderer, address auction, address initialOwner ) external; /// @notice Mints tokens to the auction house for bidding and handles founder vesting function mint() external returns (uint256 tokenId); /// @notice Burns a token that did not see any bids /// @param tokenId The ERC-721 token id function burn(uint256 tokenId) external; /// @notice The URI for a token /// @param tokenId The ERC-721 token id function tokenURI(uint256 tokenId) external view returns (string memory); /// @notice The URI for the contract function contractURI() external view returns (string memory); /// @notice The number of founders function totalFounders() external view returns (uint256); /// @notice The founders total percent ownership function totalFounderOwnership() external view returns (uint256); /// @notice The vesting details of a founder /// @param founderId The founder id function getFounder(uint256 founderId) external view returns (Founder memory); /// @notice The vesting details of all founders function getFounders() external view returns (Founder[] memory); /// @notice Update the list of allocation owners /// @param newFounders the full list of FounderParam structs function updateFounders(IManager.FounderParams[] calldata newFounders) external; /// @notice The founder scheduled to receive the given token id /// NOTE: If a founder is returned, there's no guarantee they'll receive the token as vesting expiration is not considered /// @param tokenId The ERC-721 token id function getScheduledRecipient(uint256 tokenId) external view returns (Founder memory); /// @notice The total supply of tokens function totalSupply() external view returns (uint256); /// @notice The token's auction house function auction() external view returns (address); /// @notice The token's metadata renderer function metadataRenderer() external view returns (address); /// @notice The owner of the token and metadata renderer function owner() external view returns (address); /// @notice Callback called by auction on first auction started to transfer ownership to treasury from founder function onFirstAuctionStarted() external; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { UUPS } from "../lib/proxy/UUPS.sol"; import { ReentrancyGuard } from "../lib/utils/ReentrancyGuard.sol"; import { ERC721Votes } from "../lib/token/ERC721Votes.sol"; import { ERC721 } from "../lib/token/ERC721.sol"; import { Ownable } from "../lib/utils/Ownable.sol"; import { TokenStorageV1 } from "./storage/TokenStorageV1.sol"; import { IBaseMetadata } from "./metadata/interfaces/IBaseMetadata.sol"; import { IManager } from "../manager/IManager.sol"; import { IAuction } from "../auction/IAuction.sol"; import { IToken } from "./IToken.sol"; import { VersionedContract } from "../VersionedContract.sol"; /// @title Token /// @author Rohan Kulkarni /// @custom:repo github.com/ourzora/nouns-protocol /// @notice A DAO's ERC-721 governance token contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC721Votes, TokenStorageV1 { /// /// /// IMMUTABLES /// /// /// /// @notice The contract upgrade manager IManager private immutable manager; /// /// /// CONSTRUCTOR /// /// /// /// @param _manager The contract upgrade manager address constructor(address _manager) payable initializer { manager = IManager(_manager); } /// /// /// INITIALIZER /// /// /// /// @notice Initializes a DAO's ERC-721 token contract /// @param _founders The DAO founders /// @param _initStrings The encoded token and metadata initialization strings /// @param _metadataRenderer The token's metadata renderer /// @param _auction The token's auction house /// @param _initialOwner The initial owner of the token function initialize( IManager.FounderParams[] calldata _founders, bytes calldata _initStrings, address _metadataRenderer, address _auction, address _initialOwner ) external initializer { // Ensure the caller is the contract manager if (msg.sender != address(manager)) { revert ONLY_MANAGER(); } // Initialize the reentrancy guard __ReentrancyGuard_init(); // Setup ownable __Ownable_init(_initialOwner); // Store the founders and compute their allocations _addFounders(_founders); // Decode the token name and symbol (string memory _name, string memory _symbol, , , , ) = abi.decode(_initStrings, (string, string, string, string, string, string)); // Initialize the ERC-721 token __ERC721_init(_name, _symbol); // Store the metadata renderer and auction house settings.metadataRenderer = IBaseMetadata(_metadataRenderer); settings.auction = _auction; } /// @notice Called by the auction upon the first unpause / token mint to transfer ownership from founder to treasury /// @dev Only callable by the auction contract function onFirstAuctionStarted() external override { if (msg.sender != settings.auction) { revert ONLY_AUCTION(); } // Force transfer ownership to the treasury _transferOwnership(IAuction(settings.auction).treasury()); } /// @notice Called upon initialization to add founders and compute their vesting allocations /// @dev We do this by reserving an mapping of [0-100] token indices, such that if a new token mint ID % 100 is reserved, it's sent to the appropriate founder. /// @param _founders The list of DAO founders function _addFounders(IManager.FounderParams[] calldata _founders) internal { // Used to store the total percent ownership among the founders uint256 totalOwnership; uint8 numFoundersAdded = 0; unchecked { // For each founder: for (uint256 i; i < _founders.length; ++i) { // Cache the percent ownership uint256 founderPct = _founders[i].ownershipPct; // Continue if no ownership is specified if (founderPct == 0) { continue; } // Update the total ownership and ensure it's valid totalOwnership += founderPct; // Check that founders own less than 100% of tokens if (totalOwnership > 99) { revert INVALID_FOUNDER_OWNERSHIP(); } // Compute the founder's id uint256 founderId = numFoundersAdded++; // Get the pointer to store the founder Founder storage newFounder = founder[founderId]; // Store the founder's vesting details newFounder.wallet = _founders[i].wallet; newFounder.vestExpiry = uint32(_founders[i].vestExpiry); // Total ownership cannot be above 100 so this fits safely in uint8 newFounder.ownershipPct = uint8(founderPct); // Compute the vesting schedule uint256 schedule = 100 / founderPct; // Used to store the base token id the founder will recieve uint256 baseTokenId; // For each token to vest: for (uint256 j; j < founderPct; ++j) { // Get the available token id baseTokenId = _getNextTokenId(baseTokenId); // Store the founder as the recipient tokenRecipient[baseTokenId] = newFounder; emit MintScheduled(baseTokenId, founderId, newFounder); // Update the base token id baseTokenId = (baseTokenId + schedule) % 100; } } // Store the founders' details settings.totalOwnership = uint8(totalOwnership); settings.numFounders = numFoundersAdded; } } /// @dev Finds the next available base token id for a founder /// @param _tokenId The ERC-721 token id function _getNextTokenId(uint256 _tokenId) internal view returns (uint256) { unchecked { while (tokenRecipient[_tokenId].wallet != address(0)) { _tokenId = (++_tokenId) % 100; } return _tokenId; } } /// /// /// MINT /// /// /// /// @notice Mints tokens to the auction house for bidding and handles founder vesting function mint() external nonReentrant returns (uint256 tokenId) { // Cache the auction address address minter = settings.auction; // Ensure the caller is the auction if (msg.sender != minter) { revert ONLY_AUCTION(); } // Cannot realistically overflow unchecked { do { // Get the next token to mint tokenId = settings.mintCount++; // Lookup whether the token is for a founder, and mint accordingly if so } while (_isForFounder(tokenId)); } // Mint the next available token to the auction house for bidding _mint(minter, tokenId); } /// @dev Overrides _mint to include attribute generation /// @param _to The token recipient /// @param _tokenId The ERC-721 token id function _mint(address _to, uint256 _tokenId) internal override { // Mint the token super._mint(_to, _tokenId); // Increment the total supply unchecked { ++settings.totalSupply; } // Generate the token attributes if (!settings.metadataRenderer.onMinted(_tokenId)) revert NO_METADATA_GENERATED(); } /// @dev Checks if a given token is for a founder and mints accordingly /// @param _tokenId The ERC-721 token id function _isForFounder(uint256 _tokenId) private returns (bool) { // Get the base token id uint256 baseTokenId = _tokenId % 100; // If there is no scheduled recipient: if (tokenRecipient[baseTokenId].wallet == address(0)) { return false; // Else if the founder is still vesting: } else if (block.timestamp < tokenRecipient[baseTokenId].vestExpiry) { // Mint the token to the founder _mint(tokenRecipient[baseTokenId].wallet, _tokenId); return true; // Else the founder has finished vesting: } else { // Remove them from future lookups delete tokenRecipient[baseTokenId]; return false; } } /// /// /// BURN /// /// /// /// @notice Burns a token that did not see any bids /// @param _tokenId The ERC-721 token id function burn(uint256 _tokenId) external { // Ensure the caller is the auction house if (msg.sender != settings.auction) { revert ONLY_AUCTION(); } // Burn the token _burn(_tokenId); } function _burn(uint256 _tokenId) internal override { super._burn(_tokenId); unchecked { --settings.totalSupply; } } /// /// /// METADATA /// /// /// /// @notice The URI for a token /// @param _tokenId The ERC-721 token id function tokenURI(uint256 _tokenId) public view override(IToken, ERC721) returns (string memory) { return settings.metadataRenderer.tokenURI(_tokenId); } /// @notice The URI for the contract function contractURI() public view override(IToken, ERC721) returns (string memory) { return settings.metadataRenderer.contractURI(); } /// /// /// FOUNDERS /// /// /// /// @notice The number of founders function totalFounders() external view returns (uint256) { return settings.numFounders; } /// @notice The founders total percent ownership function totalFounderOwnership() external view returns (uint256) { return settings.totalOwnership; } /// @notice The vesting details of a founder /// @param _founderId The founder id function getFounder(uint256 _founderId) external view returns (Founder memory) { return founder[_founderId]; } /// @notice The vesting details of all founders function getFounders() external view returns (Founder[] memory) { // Cache the number of founders uint256 numFounders = settings.numFounders; // Get a temporary array to hold all founders Founder[] memory founders = new Founder[](numFounders); // Cannot realistically overflow unchecked { // Add each founder to the array for (uint256 i; i < numFounders; ++i) { founders[i] = founder[i]; } } return founders; } /// @notice The founder scheduled to receive the given token id /// NOTE: If a founder is returned, there's no guarantee they'll receive the token as vesting expiration is not considered /// @param _tokenId The ERC-721 token id function getScheduledRecipient(uint256 _tokenId) external view returns (Founder memory) { return tokenRecipient[_tokenId % 100]; } /// @notice Update the list of allocation owners /// @param newFounders the full list of founders function updateFounders(IManager.FounderParams[] calldata newFounders) external onlyOwner { // Cache the number of founders uint256 numFounders = settings.numFounders; // Get a temporary array to hold all founders Founder[] memory cachedFounders = new Founder[](numFounders); // Cannot realistically overflow unchecked { // Add each founder to the array for (uint256 i; i < numFounders; ++i) { cachedFounders[i] = founder[i]; } } // Keep a mapping of all the reserved token IDs we're set to clear. bool[] memory clearedTokenIds = new bool[](100); unchecked { // for each existing founder: for (uint256 i; i < cachedFounders.length; ++i) { // copy the founder into memory Founder memory cachedFounder = cachedFounders[i]; // Delete the founder from the stored mapping delete founder[i]; // Some DAOs were initialized with 0 percentage ownership. // This skips them to avoid a division by zero error. if (cachedFounder.ownershipPct == 0) { continue; } // using the ownership percentage, get reserved token percentages uint256 schedule = 100 / cachedFounder.ownershipPct; // Used to reverse engineer the indices the founder has reserved tokens in. uint256 baseTokenId; for (uint256 j; j < cachedFounder.ownershipPct; ++j) { // Get the next index that hasn't already been cleared while (clearedTokenIds[baseTokenId] != false) { baseTokenId = (++baseTokenId) % 100; } delete tokenRecipient[baseTokenId]; clearedTokenIds[baseTokenId] = true; emit MintUnscheduled(baseTokenId, i, cachedFounder); // Update the base token id baseTokenId = (baseTokenId + schedule) % 100; } } } settings.numFounders = 0; settings.totalOwnership = 0; emit FounderAllocationsCleared(newFounders); _addFounders(newFounders); } /// /// /// SETTINGS /// /// /// /// @notice The total supply of tokens function totalSupply() external view returns (uint256) { return settings.totalSupply; } /// @notice The address of the auction house function auction() external view returns (address) { return settings.auction; } /// @notice The address of the metadata renderer function metadataRenderer() external view returns (address) { return address(settings.metadataRenderer); } function owner() public view override(IToken, Ownable) returns (address) { return super.owner(); } /// /// /// TOKEN UPGRADE /// /// /// /// @notice Ensures the caller is authorized to upgrade the contract and that the new implementation is valid /// @dev This function is called in `upgradeTo` & `upgradeToAndCall` /// @param _newImpl The new implementation address function _authorizeUpgrade(address _newImpl) internal view override { // Ensure the caller is the shared owner of the token and metadata renderer if (msg.sender != owner()) revert ONLY_OWNER(); // Ensure the implementation is valid if (!manager.isRegisteredUpgrade(_getImplementation(), _newImpl)) revert INVALID_UPGRADE(_newImpl); } } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IUUPS } from "../../../lib/interfaces/IUUPS.sol"; /// @title IBaseMetadata /// @author Rohan Kulkarni /// @notice The external Base Metadata errors and functions interface IBaseMetadata is IUUPS { /// /// /// ERRORS /// /// /// /// @dev Reverts if the caller was not the contract manager error ONLY_MANAGER(); /// /// /// FUNCTIONS /// /// /// /// @notice Initializes a DAO's token metadata renderer /// @param initStrings The encoded token and metadata initialization strings /// @param token The associated ERC-721 token address function initialize( bytes calldata initStrings, address token ) external; /// @notice Generates attributes for a token upon mint /// @param tokenId The ERC-721 token id function onMinted(uint256 tokenId) external returns (bool); /// @notice The token URI /// @param tokenId The ERC-721 token id function tokenURI(uint256 tokenId) external view returns (string memory); /// @notice The contract URI function contractURI() external view returns (string memory); /// @notice The associated ERC-721 token function token() external view returns (address); /// @notice Get metadata owner address function owner() external view returns (address); } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { TokenTypesV1 } from "../types/TokenTypesV1.sol"; /// @title TokenStorageV1 /// @author Rohan Kulkarni /// @notice The Token storage contract contract TokenStorageV1 is TokenTypesV1 { /// @notice The token settings Settings internal settings; /// @notice The vesting details of a founder /// @dev Founder id => Founder mapping(uint256 => Founder) internal founder; /// @notice The recipient of a token /// @dev ERC-721 token id => Founder mapping(uint256 => Founder) internal tokenRecipient; } // SPDX-License-Identifier: MIT pragma solidity 0.8.16; import { IBaseMetadata } from "../metadata/interfaces/IBaseMetadata.sol"; /// @title TokenTypesV1 /// @author Rohan Kulkarni /// @notice The Token custom data types interface TokenTypesV1 { /// @notice The settings type /// @param auction The DAO auction house /// @param totalSupply The number of active tokens /// @param numFounders The number of vesting recipients /// @param metadatarenderer The token metadata renderer /// @param mintCount The number of minted tokens /// @param totalPercentage The total percentage owned by founders struct Settings { address auction; uint88 totalSupply; uint8 numFounders; IBaseMetadata metadataRenderer; uint88 mintCount; uint8 totalOwnership; } /// @notice The founder type /// @param wallet The address where tokens are sent /// @param ownershipPct The percentage of token ownership /// @param vestExpiry The timestamp when vesting ends struct Founder { address wallet; uint8 ownershipPct; uint32 vestExpiry; } }