Transaction Hash:
Block:
17098804 at Apr-22-2023 02:07:35 AM +UTC
Transaction Fee:
0.001628454824151945 ETH
$3.13
Gas Used:
55,515 Gas / 29.333600363 Gwei
Emitted Events:
| 163 |
ERC1967Proxy.0x1edd36b28569cf1cf930c008c5bcb196ea19525977951413bec3118946951561( 0x1edd36b28569cf1cf930c008c5bcb196ea19525977951413bec3118946951561, 0000000000000000000000000000000000000000000000000000000000000008, 000000000000000000000000f9cfc8fb1ec248c4a896753d50b93109cfaaa53f, 000000000000000000000000000000000000000000000000040256e9bccc3000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000064434293 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x388C818C...7ccB19297
Miner
| (Lido: Execution Layer Rewards Vault) | 193.416164424730920966 Eth | 193.416169976230920966 Eth | 0.0000055515 | |
| 0x6E16d528...60733dD03 | 0.2588 Eth | 0.288888888 Eth | 0.030088888 | ||
| 0x94c49424...03A758d2F | 10.147343097329450411 Eth | 10.406143097329450411 Eth | 0.2588 | ||
| 0xf9cFc8Fb...9CfAaA53f |
0.886957616708138451 Eth
Nonce: 55
|
0.596440273883986506 Eth
Nonce: 56
| 0.290517342824151945 |
Execution Trace
ETH 0.288888888
ERC1967Proxy.659dd2b4( )
ETH 0.288888888
Auction.createBid( _tokenId=8 )- ETH 0.2588
0x94c49424ec92494c89f7a6f4bf7481c03a758d2f.CALL( )
- ETH 0.2588
createBid[Auction (ln:257)]
INVALID_TOKEN_ID[Auction (ln:260)]AUCTION_OVER[Auction (ln:264)]RESERVE_PRICE_NOT_MET[Auction (ln:292)]MINIMUM_BID_NOT_MET[Auction (ln:305)]MINIMUM_BID_NOT_MET[Auction (ln:309)]_handleOutgoingTransfer[Auction (ln:312)]INSOLVENT[Auction (ln:476)]deposit[Auction (ln:487)]transfer[Auction (ln:489)]FAILING_WETH_TRANSFER[Auction (ln:492)]
AuctionBid[Auction (ln:314)]
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.2.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";
import { TokenTypesV2 } from "./types/TokenTypesV2.sol";
/// @title IToken
/// @author Rohan Kulkarni
/// @notice The external Token events, errors and functions
interface IToken is IUUPS, IERC721Votes, TokenTypesV1, TokenTypesV2 {
/// ///
/// 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);
/// @notice Emitted when minters are updated
/// @param minter Address of added or removed minter
/// @param allowed Whether address is allowed to mint
event MinterUpdated(address minter, bool allowed);
/// ///
/// 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 the caller was not a minter
error ONLY_AUCTION_OR_MINTER();
/// @dev Reverts if the caller was not the token owner
error ONLY_TOKEN_OWNER();
/// @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 caller and handles founder vesting
function mint() external returns (uint256 tokenId);
/// @notice Mints tokens to the recipient and handles founder vesting
function mintTo(address recipient) external returns (uint256 tokenId);
/// @notice Mints the specified amount of tokens to the recipient and handles founder vesting
function mintBatchTo(uint256 amount, address recipient) external returns (uint256[] memory tokenIds);
/// @notice Burns a token owned by the caller
/// @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 Update minters
/// @param _minters Array of structs containing address status as a minter
function updateMinters(MinterParams[] calldata _minters) external;
/// @notice Check if an address is a minter
/// @param _minter Address to check
function isMinter(address _minter) external view returns (bool);
/// @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 { TokenStorageV2 } from "./storage/TokenStorageV2.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, TokenStorageV2 {
/// ///
/// IMMUTABLES ///
/// ///
/// @notice The contract upgrade manager
IManager private immutable manager;
/// ///
/// MODIFIERS ///
/// ///
/// @notice Reverts if caller is not an authorized minter
modifier onlyAuctionOrMinter() {
if (msg.sender != settings.auction && !minter[msg.sender]) {
revert ONLY_AUCTION_OR_MINTER();
}
_;
}
/// ///
/// 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 caller and handles founder vesting
function mint() external nonReentrant onlyAuctionOrMinter returns (uint256 tokenId) {
tokenId = _mintWithVesting(msg.sender);
}
/// @notice Mints tokens to the recipient and handles founder vesting
function mintTo(address recipient) external nonReentrant onlyAuctionOrMinter returns (uint256 tokenId) {
tokenId = _mintWithVesting(recipient);
}
/// @notice Mints the specified amount of tokens to the recipient and handles founder vesting
function mintBatchTo(uint256 amount, address recipient) external nonReentrant onlyAuctionOrMinter returns (uint256[] memory tokenIds) {
tokenIds = new uint256[](amount);
for (uint256 i = 0; i < amount; ) {
tokenIds[i] = _mintWithVesting(recipient);
unchecked {
++i;
}
}
}
function _mintWithVesting(address recipient) internal returns (uint256 tokenId) {
// 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 recipient for bidding
_mint(recipient, 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 owned by the caller
/// @param _tokenId The ERC-721 token id
function burn(uint256 _tokenId) external onlyAuctionOrMinter {
if (ownerOf(_tokenId) != msg.sender) {
revert ONLY_TOKEN_OWNER();
}
_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();
}
/// @notice Update minters
/// @param _minters Array of structs containing address status as a minter
function updateMinters(MinterParams[] calldata _minters) external onlyOwner {
// Update each minter
for (uint256 i; i < _minters.length; ++i) {
// Skip if the minter is already set to the correct value
if (minter[_minters[i].minter] == _minters[i].allowed) continue;
emit MinterUpdated(_minters[i].minter, _minters[i].allowed);
// Update the minter
minter[_minters[i].minter] = _minters[i].allowed;
}
}
/// @notice Check if an address is a minter
/// @param _minter Address to check
function isMinter(address _minter) external view returns (bool) {
return minter[_minter];
}
/// ///
/// 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 { TokenTypesV2 } from "../types/TokenTypesV2.sol";
/// @title TokenStorageV2
/// @author James Geary
/// @notice The Token storage contract
contract TokenStorageV2 is TokenTypesV2 {
/// @notice The minter status of an address
mapping(address => bool) public minter;
}
// 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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/// @title TokenTypesV2
/// @author James Geary
/// @notice The Token custom data types
interface TokenTypesV2 {
struct MinterParams {
address minter;
bool allowed;
}
}