pragma solidity ^0.4.24; import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "@evolutionland/common/contracts/interfaces/ISettingsRegistry.sol"; import "@evolutionland/common/contracts/PausableDSAuth.sol"; import "./ApostleSettingIds.sol"; import "@evolutionland/common/contracts/interfaces/ITokenUse.sol"; import "./interfaces/IApostleBase.sol"; /// @title Auction Core /// @dev Contains models, variables, and internal methods for the auction. contract SiringAuctionBase is ApostleSettingIds, PausableDSAuth { using SafeMath for *; event AuctionCreated( uint256 tokenId, address seller, uint256 startingPriceInToken, uint256 endingPriceInToken, uint256 duration, address token, uint256 startedAt ); event AuctionSuccessful(uint256 tokenId, uint256 totalPrice, address winner); event AuctionCancelled(uint256 tokenId); // Represents an auction on an NFT struct Auction { // Current owner of NFT address seller; // Price (in ring(wei)) at beginning of auction uint128 startingPriceInToken; // Price (in ring(wei)) at end of auction uint128 endingPriceInToken; // Duration (in seconds) of auction uint48 duration; // Time when auction started // NOTE: 0 if this auction has been concluded uint48 startedAt; // bid with which token address token; } ISettingsRegistry public registry; // Map from token ID to their corresponding auction. mapping (uint256 => Auction) public tokenIdToAuction; /// @dev DON'T give me your money. function() external {} // Modifiers to check that inputs can be safely stored with a certain // number of bits. We use constants and multiple modifiers to save gas. modifier canBeStoredWith48Bits(uint256 _value) { require(_value <= 281474976710655); _; } modifier canBeStoredWith128Bits(uint256 _value) { require(_value < 340282366920938463463374607431768211455); _; } /// @dev Creates and begins a new auction. /// @param _tokenId - ID of token to auction, sender must be owner. // NOTE: change _startingPrice and _endingPrice in from wei to ring for user-friendly reason /// @param _startingPriceInToken - Price of item (in token) at beginning of auction. /// @param _endingPriceInToken - Price of item (in token) at end of auction. /// @param _duration - Length of time to move between starting /// price and ending price (in seconds). /// @param _seller - Seller, if not the message sender function _createAuction( address _from, uint256 _tokenId, uint256 _startingPriceInToken, uint256 _endingPriceInToken, uint256 _duration, uint256 _startAt, address _seller, address _token ) internal canBeStoredWith128Bits(_startingPriceInToken) canBeStoredWith128Bits(_endingPriceInToken) canBeStoredWith48Bits(_duration) canBeStoredWith48Bits(_startAt) whenNotPaused { // Require that all auctions have a duration of // at least one minute. (Keeps our math from getting hairy!) require(_duration >= 1 minutes, "duration must be at least 1 minutes"); require(_duration <= 1000 days); require(IApostleBase(registry.addressOf(ApostleSettingIds.CONTRACT_APOSTLE_BASE)).isReadyToBreed(_tokenId), "it is still in use or have a baby to give birth."); // escrow ERC721(registry.addressOf(SettingIds.CONTRACT_OBJECT_OWNERSHIP)).safeTransferFrom(_from, address(this), _tokenId); tokenIdToAuction[_tokenId] = Auction({ seller: _seller, startedAt: uint48(_startAt), duration: uint48(_duration), startingPriceInToken: uint128(_startingPriceInToken), endingPriceInToken: uint128(_endingPriceInToken), token: _token }); emit AuctionCreated(_tokenId, _seller, _startingPriceInToken, _endingPriceInToken, _duration, _token, _startAt); } /// @dev Cancels an auction unconditionally. function _cancelAuction(uint256 _tokenId, address _seller) internal { _removeAuction(_tokenId); ERC721(registry.addressOf(SettingIds.CONTRACT_OBJECT_OWNERSHIP)).transferFrom(address(this), _seller, _tokenId); emit AuctionCancelled(_tokenId); } /// @dev Removes an auction from the list of open auctions. /// @param _tokenId - ID of NFT on auction. function _removeAuction(uint256 _tokenId) internal { delete tokenIdToAuction[_tokenId]; } /// @dev Returns true if the NFT is on auction. /// @param _auction - Auction to check. function _isOnAuction(Auction storage _auction) internal view returns (bool) { return (_auction.startedAt > 0); } /// @dev Returns current price of an NFT on auction. Broken into two /// functions (this one, that computes the duration from the auction /// structure, and the other that does the price computation) so we /// can easily test that the price computation works correctly. function _currentPrice(Auction storage _auction) internal view returns (uint256) { uint256 secondsPassed = 0; // A bit of insurance against negative values (or wraparound). // Probably not necessary (since Ethereum guarnatees that the // now variable doesn't ever go backwards). if (now > _auction.startedAt) { secondsPassed = now - _auction.startedAt; } return _computeCurrentPrice( _auction.startingPriceInToken, _auction.endingPriceInToken, _auction.duration, secondsPassed ); } /// @dev Computes the current price of an auction. Factored out /// from _currentPrice so we can run extensive unit tests. /// When testing, make this function public and turn on /// `Current price computation` test suite. function _computeCurrentPrice( uint256 _startingPrice, uint256 _endingPrice, uint256 _duration, uint256 _secondsPassed ) internal pure returns (uint256) { // NOTE: We don't use SafeMath (or similar) in this function because // all of our public functions carefully cap the maximum values for // time (at 64-bits) and currency (at 128-bits). _duration is // also known to be non-zero (see the require() statement in // _addAuction()) if (_secondsPassed >= _duration) { // We've reached the end of the dynamic pricing portion // of the auction, just return the end price. return _endingPrice; } else { // Starting price can be higher than ending price (and often is!), so // this delta can be negative. int256 totalPriceChange = int256(_endingPrice) - int256(_startingPrice); // This multiplication can't overflow, _secondsPassed will easily fit within // 64-bits, and totalPriceChange will easily fit within 128-bits, their product // will always fit within 256-bits. int256 currentPriceChange = totalPriceChange * int256(_secondsPassed) / int256(_duration); // currentPriceChange can be negative, but if so, will have a magnitude // less that _startingPrice. Thus, this result will always end up positive. int256 currentPrice = int256(_startingPrice) + currentPriceChange; return uint256(currentPrice); } } /// @dev Computes owner's cut of a sale. /// @param _price - Sale price of NFT. function _computeCut(uint256 _price) internal view returns (uint256) { // NOTE: We don't use SafeMath (or similar) in this function because // all of our entry functions carefully cap the maximum values for // currency (at 128-bits), and ownerCut <= 10000 (see the require() // statement in the ClockAuction constructor). The result of this // function is always guaranteed to be <= _price. uint ownerCut = registry.uintOf(UINT_AUCTION_CUT); return _price * ownerCut / 10000; } }