ERC721の取引所サービスであるOpen seaのコントラクトを調べてみたのでまとめてみます。
state of the dapps にcontractのアドレスが載っていたので、とりあえずrinkebyで公開されているものについて調べてみました。
mainnetの方が3つに分かれているぽいですが、調べたらまた追記します。
ソースコード
500行くらいですね。短めです。
pragma solidity ^0.4.18;
/**
* Interface for required functionality in the ERC721 standard
* for non-fungible tokens.
*
* Author: Nadav Hollander (nadav at dharma.io)
*/
contract ERC721 {
// Function
function totalSupply() public view returns (uint256 _totalSupply);
function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint _tokenId) public view returns (address _owner);
function approve(address _to, uint _tokenId) public;
function transferFrom(address _from, address _to, uint _tokenId) public;
function transfer(address _to, uint _tokenId) public;
function implementsERC721() public view returns (bool _implementsERC721);
// Events
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
}
/// @title Auction Core
/// @dev Contains models, variables, and internal methods for the auction.
contract ClockAuctionBase {
// Represents an auction on an NFT
struct Auction {
// Address of the NFT
address nftAddress;
// Current owner of NFT
address seller;
// Price (in wei) at beginning of auction
uint128 startingPrice;
// Price (in wei) at end of auction
uint128 endingPrice;
// Duration (in seconds) of auction
uint64 duration;
// Time when auction started
// NOTE: 0 if this auction has been concluded
uint64 startedAt;
}
// Cut owner takes on each auction, measured in basis points (1/100 of a percent).
// Values 0-10,000 map to 0%-100%
uint256 public ownerCut;
// Map from token ID to their corresponding auction.
mapping (address => mapping(uint256 => Auction)) nftToTokenIdToAuction;
event AuctionCreated(address nftAddress, uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration);
event AuctionSuccessful(address nftAddress, uint256 tokenId, uint256 totalPrice, address winner);
event AuctionCancelled(address nftAddress, uint256 tokenId);
/// @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 canBeStoredWith64Bits(uint256 _value) {
require(_value <= 18446744073709551615);
_;
}
modifier canBeStoredWith128Bits(uint256 _value) {
require(_value < 340282366920938463463374607431768211455);
_;
}
/// @dev Returns true if the claimant owns the token.
/// @param _nft - The address of the NFT.
/// @param _claimant - Address claiming to own the token.
/// @param _tokenId - ID of token whose ownership to verify.
function _owns(address _nft, address _claimant, uint256 _tokenId) internal view returns (bool) {
ERC721 nonFungibleContract = _getNft(_nft);
return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
}
/// @dev Escrows the NFT, assigning ownership to this contract.
/// Throws if the escrow fails.
/// @param _nft - The address of the NFT.
/// @param _owner - Current owner address of token to escrow.
/// @param _tokenId - ID of token whose approval to verify.
function _escrow(address _nft, address _owner, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transferFrom(_owner, this, _tokenId);
}
/// @dev Transfers an NFT owned by this contract to another address.
/// Returns true if the transfer succeeds.
/// @param _nft - The address of the NFT.
/// @param _receiver - Address to transfer NFT to.
/// @param _tokenId - ID of token to transfer.
function _transfer(address _nft, address _receiver, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transfer(_receiver, _tokenId);
}
/// @dev Adds an auction to the list of open auctions. Also fires the
/// AuctionCreated event.
/// @param _tokenId The ID of the token to be put on auction.
/// @param _auction Auction to add.
function _addAuction(address _nft, uint256 _tokenId, Auction _auction) internal {
// Require that all auctions have a duration of
// at least one minute. (Keeps our math from getting hairy!)
require(_auction.duration >= 1 minutes);
nftToTokenIdToAuction[_nft][_tokenId] = _auction;
AuctionCreated(
address(_nft),
uint256(_tokenId),
uint256(_auction.startingPrice),
uint256(_auction.endingPrice),
uint256(_auction.duration)
);
}
/// @dev Cancels an auction unconditionally.
function _cancelAuction(address _nft, uint256 _tokenId, address _seller) internal {
_removeAuction(_nft, _tokenId);
_transfer(_nft, _seller, _tokenId);
AuctionCancelled(_nft, _tokenId);
}
/// @dev Computes the price and transfers winnings.
/// Does NOT transfer ownership of token.
function _bid(address _nft, uint256 _tokenId, uint256 _bidAmount)
internal
returns (uint256)
{
// Get a reference to the auction struct
Auction storage auction = nftToTokenIdToAuction[_nft][_tokenId];
// Explicitly check that this auction is currently live.
// (Because of how Ethereum mappings work, we can't just count
// on the lookup above failing. An invalid _tokenId will just
// return an auction object that is all zeros.)
require(_isOnAuction(auction));
// Check that the incoming bid is higher than the current
// price
uint256 price = _currentPrice(auction);
require(_bidAmount >= price);
// Grab a reference to the seller before the auction struct
// gets deleted.
address seller = auction.seller;
// The bid is good! Remove the auction before sending the fees
// to the sender so we can't have a reentrancy attack.
_removeAuction(_nft, _tokenId);
// Transfer proceeds to seller (if there are any!)
if (price > 0) {
// Calculate the auctioneer's cut.
// (NOTE: _computeCut() is guaranteed to return a
// value <= price, so this subtraction can't go negative.)
uint256 auctioneerCut = _computeCut(price);
uint256 sellerProceeds = price - auctioneerCut;
// NOTE: Doing a transfer() in the middle of a complex
// method like this is generally discouraged because of
// reentrancy attacks and DoS attacks if the seller is
// a contract with an invalid fallback function. We explicitly
// guard against reentrancy attacks by removing the auction
// before calling transfer(), and the only thing the seller
// can DoS is the sale of their own asset! (And if it's an
// accident, they can call cancelAuction(). )
seller.transfer(sellerProceeds);
}
// Tell the world!
AuctionSuccessful(_nft, _tokenId, price, msg.sender);
return price;
}
/// @dev Removes an auction from the list of open auctions.
/// @param _tokenId - ID of NFT on auction.
function _removeAuction(address _nft, uint256 _tokenId) internal {
delete nftToTokenIdToAuction[_nft][_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.startingPrice,
_auction.endingPrice,
_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.
return _price * ownerCut / 10000;
}
/// @dev Gets the NFT object from an address, validating that implementsERC721 is true.
/// @param _nft - Address of the NFT.
function _getNft(address _nft) internal view returns (ERC721) {
ERC721 candidateContract = ERC721(_nft);
//require(candidateContract.implementsERC721());
return candidateContract;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
/// @title Clock auction for non-fungible tokens.
contract ClockAuction is Pausable, ClockAuctionBase {
/// @dev Constructor creates a reference to the NFT ownership contract
/// and verifies the owner cut is in the valid range.
/// @param _cut - percent cut the owner takes on each auction, must be
/// between 0-10,000.
function ClockAuction(uint256 _cut) public {
require(_cut <= 10000);
ownerCut = _cut;
}
/// @dev Remove all Ether from the contract, which is the owner's cuts
/// as well as any Ether sent directly to the contract address.
/// Always transfers to the NFT contract, but can be called either by
/// the owner or the NFT contract.
function withdrawBalance() external {
require(
msg.sender == owner
);
msg.sender.transfer(this.balance);
}
/// @dev Creates and begins a new auction.
/// @param _nftAddress - address of a deployed contract implementing
/// the Nonfungible Interface.
/// @param _tokenId - ID of token to auction, sender must be owner.
/// @param _startingPrice - Price of item (in wei) at beginning of auction.
/// @param _endingPrice - Price of item (in wei) 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 _nftAddress,
uint256 _tokenId,
uint256 _startingPrice,
uint256 _endingPrice,
uint256 _duration,
address _seller
)
public
whenNotPaused
canBeStoredWith128Bits(_startingPrice)
canBeStoredWith128Bits(_endingPrice)
canBeStoredWith64Bits(_duration)
{
require(_owns(_nftAddress, msg.sender, _tokenId));
_escrow(_nftAddress, msg.sender, _tokenId);
Auction memory auction = Auction(
_nftAddress,
_seller,
uint128(_startingPrice),
uint128(_endingPrice),
uint64(_duration),
uint64(now)
);
_addAuction(_nftAddress, _tokenId, auction);
}
/// @dev Bids on an open auction, completing the auction and transferring
/// ownership of the NFT if enough Ether is supplied.
/// @param _nftAddress - address of a deployed contract implementing
/// the Nonfungible Interface.
/// @param _tokenId - ID of token to bid on.
function bid(address _nftAddress, uint256 _tokenId)
public
payable
whenNotPaused
{
// _bid will throw if the bid or funds transfer fails
_bid(_nftAddress, _tokenId, msg.value);
_transfer(_nftAddress, msg.sender, _tokenId);
}
/// @dev Cancels an auction that hasn't been won yet.
/// Returns the NFT to original owner.
/// @notice This is a state-modifying function that can
/// be called while the contract is paused.
/// @param _nftAddress - Address of the NFT.
/// @param _tokenId - ID of token on auction
function cancelAuction(address _nftAddress, uint256 _tokenId)
public
{
Auction storage auction = nftToTokenIdToAuction[_nftAddress][_tokenId];
require(_isOnAuction(auction));
address seller = auction.seller;
require(msg.sender == seller);
_cancelAuction(_nftAddress, _tokenId, seller);
}
/// @dev Cancels an auction when the contract is paused.
/// Only the owner may do this, and NFTs are returned to
/// the seller. This should only be used in emergencies.
/// @param _nftAddress - Address of the NFT.
/// @param _tokenId - ID of the NFT on auction to cancel.
function cancelAuctionWhenPaused(address _nftAddress, uint256 _tokenId)
whenPaused
onlyOwner
public
{
Auction storage auction = nftToTokenIdToAuction[_nftAddress][_tokenId];
require(_isOnAuction(auction));
_cancelAuction(_nftAddress, _tokenId, auction.seller);
}
/// @dev Returns auction info for an NFT on auction.
/// @param _nftAddress - Address of the NFT.
/// @param _tokenId - ID of NFT on auction.
function getAuction(address _nftAddress, uint256 _tokenId)
public
view
returns
(
address seller,
uint256 startingPrice,
uint256 endingPrice,
uint256 duration,
uint256 startedAt
) {
Auction storage auction = nftToTokenIdToAuction[_nftAddress][_tokenId];
require(_isOnAuction(auction));
return (
auction.seller,
auction.startingPrice,
auction.endingPrice,
auction.duration,
auction.startedAt
);
}
/// @dev Returns the current price of an auction.
/// @param _nftAddress - Address of the NFT.
/// @param _tokenId - ID of the token price we are checking.
function getCurrentPrice(address _nftAddress, uint256 _tokenId)
public
view
returns (uint256)
{
Auction storage auction = nftToTokenIdToAuction[_nftAddress][_tokenId];
require(_isOnAuction(auction));
return _currentPrice(auction);
}
}
/// @title Clock auction modified for sale of kitties
contract SaleClockAuction is ClockAuction {
// Delegate constructor
function SaleClockAuction(uint256 _cut) public
ClockAuction(_cut) {}
/// @dev Creates and begins a new auction.
/// @param _nftAddress - The address of the NFT.
/// @param _tokenId - ID of token to auction, sender must be owner.
/// @param _startingPrice - Price of item (in wei) at beginning of auction.
/// @param _endingPrice - Price of item (in wei) at end of auction.
/// @param _duration - Length of auction (in seconds).
function createAuction(
address _nftAddress,
uint256 _tokenId,
uint256 _startingPrice,
uint256 _endingPrice,
uint256 _duration
)
public
canBeStoredWith128Bits(_startingPrice)
canBeStoredWith128Bits(_endingPrice)
canBeStoredWith64Bits(_duration)
{
address seller = msg.sender;
_escrow(_nftAddress, seller, _tokenId);
Auction memory auction = Auction(
_nftAddress,
seller,
uint128(_startingPrice),
uint128(_endingPrice),
uint64(_duration),
uint64(now)
);
_addAuction(_nftAddress, _tokenId, auction);
}
/// @dev Updates lastSalePrice if seller is the nft contract
/// Otherwise, works the same as default bid method.
function bid(address _nftAddress, uint256 _tokenId)
public
payable
{
// _bid verifies token ID size
address seller = nftToTokenIdToAuction[_nftAddress][_tokenId].seller;
uint256 price = _bid(_nftAddress, _tokenId, msg.value);
_transfer(_nftAddress, msg.sender, _tokenId);
}
}
コントラクトが全部で6つ、
- ERC721
- Ownable
- ClockAuctionBase
- Pausable
- ClockAuction
- SaleClockAuction
あります。
継承関係をみてみてると、
SaleClockAuction ← ClockAuction ← Pausable, ClockAuctionBase
Pausable ← Ownable
て感じです。ERC721は出品されるERC721トークンのインターフェースとして使われています。
Ownable
これは色々なコントラクトで使われているもので、コントラクトのOwnerを設定し、Ownerだけが実行できるmodifierを提供するコントラクト。
Pausable
これは関数の実行を制限、制限解除するためのmodifierを提供するコントラクト。
ClockAuctionBase
これはオリジナルの、ロジックの重要な部分が含まれているので一応まとめます
contract ClockAuctionBase {
// Represents an auction on an NFT
struct Auction {
// Address of the NFT
address nftAddress;
// Current owner of NFT
address seller;
// Price (in wei) at beginning of auction
uint128 startingPrice;
// Price (in wei) at end of auction
uint128 endingPrice;
// Duration (in seconds) of auction
uint64 duration;
// Time when auction started
// NOTE: 0 if this auction has been concluded
uint64 startedAt;
}
// Cut owner takes on each auction, measured in basis points (1/100 of a percent).
// Values 0-10,000 map to 0%-100%
uint256 public ownerCut;
// Map from token ID to their corresponding auction.
mapping (address => mapping(uint256 => Auction)) nftToTokenIdToAuction;
event AuctionCreated(address nftAddress, uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration);
event AuctionSuccessful(address nftAddress, uint256 tokenId, uint256 totalPrice, address winner);
event AuctionCancelled(address nftAddress, uint256 tokenId);
/// @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 canBeStoredWith64Bits(uint256 _value) {
require(_value <= 18446744073709551615);
_;
}
modifier canBeStoredWith128Bits(uint256 _value) {
require(_value < 340282366920938463463374607431768211455);
_;
}
/// @dev Returns true if the claimant owns the token.
/// @param _nft - The address of the NFT.
/// @param _claimant - Address claiming to own the token.
/// @param _tokenId - ID of token whose ownership to verify.
function _owns(address _nft, address _claimant, uint256 _tokenId) internal view returns (bool) {
ERC721 nonFungibleContract = _getNft(_nft);
return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
}
/// @dev Escrows the NFT, assigning ownership to this contract.
/// Throws if the escrow fails.
/// @param _nft - The address of the NFT.
/// @param _owner - Current owner address of token to escrow.
/// @param _tokenId - ID of token whose approval to verify.
function _escrow(address _nft, address _owner, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transferFrom(_owner, this, _tokenId);
}
/// @dev Transfers an NFT owned by this contract to another address.
/// Returns true if the transfer succeeds.
/// @param _nft - The address of the NFT.
/// @param _receiver - Address to transfer NFT to.
/// @param _tokenId - ID of token to transfer.
function _transfer(address _nft, address _receiver, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transfer(_receiver, _tokenId);
}
/// @dev Adds an auction to the list of open auctions. Also fires the
/// AuctionCreated event.
/// @param _tokenId The ID of the token to be put on auction.
/// @param _auction Auction to add.
function _addAuction(address _nft, uint256 _tokenId, Auction _auction) internal {
// Require that all auctions have a duration of
// at least one minute. (Keeps our math from getting hairy!)
require(_auction.duration >= 1 minutes);
nftToTokenIdToAuction[_nft][_tokenId] = _auction;
AuctionCreated(
address(_nft),
uint256(_tokenId),
uint256(_auction.startingPrice),
uint256(_auction.endingPrice),
uint256(_auction.duration)
);
}
/// @dev Cancels an auction unconditionally.
function _cancelAuction(address _nft, uint256 _tokenId, address _seller) internal {
_removeAuction(_nft, _tokenId);
_transfer(_nft, _seller, _tokenId);
AuctionCancelled(_nft, _tokenId);
}
/// @dev Computes the price and transfers winnings.
/// Does NOT transfer ownership of token.
function _bid(address _nft, uint256 _tokenId, uint256 _bidAmount)
internal
returns (uint256)
{
// Get a reference to the auction struct
Auction storage auction = nftToTokenIdToAuction[_nft][_tokenId];
// Explicitly check that this auction is currently live.
// (Because of how Ethereum mappings work, we can't just count
// on the lookup above failing. An invalid _tokenId will just
// return an auction object that is all zeros.)
require(_isOnAuction(auction));
// Check that the incoming bid is higher than the current
// price
uint256 price = _currentPrice(auction);
require(_bidAmount >= price);
// Grab a reference to the seller before the auction struct
// gets deleted.
address seller = auction.seller;
// The bid is good! Remove the auction before sending the fees
// to the sender so we can't have a reentrancy attack.
_removeAuction(_nft, _tokenId);
// Transfer proceeds to seller (if there are any!)
if (price > 0) {
// Calculate the auctioneer's cut.
// (NOTE: _computeCut() is guaranteed to return a
// value <= price, so this subtraction can't go negative.)
uint256 auctioneerCut = _computeCut(price);
uint256 sellerProceeds = price - auctioneerCut;
// NOTE: Doing a transfer() in the middle of a complex
// method like this is generally discouraged because of
// reentrancy attacks and DoS attacks if the seller is
// a contract with an invalid fallback function. We explicitly
// guard against reentrancy attacks by removing the auction
// before calling transfer(), and the only thing the seller
// can DoS is the sale of their own asset! (And if it's an
// accident, they can call cancelAuction(). )
seller.transfer(sellerProceeds);
}
// Tell the world!
AuctionSuccessful(_nft, _tokenId, price, msg.sender);
return price;
}
/// @dev Removes an auction from the list of open auctions.
/// @param _tokenId - ID of NFT on auction.
function _removeAuction(address _nft, uint256 _tokenId) internal {
delete nftToTokenIdToAuction[_nft][_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.startingPrice,
_auction.endingPrice,
_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.
return _price * ownerCut / 10000;
}
/// @dev Gets the NFT object from an address, validating that implementsERC721 is true.
/// @param _nft - Address of the NFT.
function _getNft(address _nft) internal view returns (ERC721) {
ERC721 candidateContract = ERC721(_nft);
//require(candidateContract.implementsERC721());
return candidateContract;
}
}
変数
まずは変数からみていきます
// Represents an auction on an NFT
struct Auction {
// Address of the NFT
address nftAddress;
// Current owner of NFT
address seller;
// Price (in wei) at beginning of auction
uint128 startingPrice;
// Price (in wei) at end of auction
uint128 endingPrice;
// Duration (in seconds) of auction
uint64 duration;
// Time when auction started
// NOTE: 0 if this auction has been concluded
uint64 startedAt;
}
// Cut owner takes on each auction, measured in basis points (1/100 of a percent).
// Values 0-10,000 map to 0%-100%
uint256 public ownerCut;
// Map from token ID to their corresponding auction.
mapping (address => mapping(uint256 => Auction)) nftToTokenIdToAuction;
- Auction
- 登録されたトークンのアドレス
- erc721トークンの所有者
- 売買開始時の価格
- 売買終了時の価格
- 売買期間
- 開始する時間
open seaでは時間経過とともに価格が変化する販売形式を採用しているので、こんな感じのデータ構造になっています。
- owenrCut
ownerが取引の何%を手数料としてもらうかを決めます。0 - 10000の整数値で、0.01 - 100% まで設定できます。
- nftToTokenIdToAuction
tokenのアドレスとidをオークションと紐付けます。
event
Eventは
event AuctionCreated(address nftAddress, uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration);
event AuctionSuccessful(address nftAddress, uint256 tokenId, uint256 totalPrice, address winner);
event AuctionCancelled(address nftAddress, uint256 tokenId);
これはオークションの始まり、終わり、中止を表すイベントですね。
修飾子
// 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 canBeStoredWith64Bits(uint256 _value) {
require(_value <= 18446744073709551615);
_;
}
modifier canBeStoredWith128Bits(uint256 _value) {
require(_value < 340282366920938463463374607431768211455);
_;
}
- canBetoredWith64Bits
- canBeStoredWith128Bits
これらはそれぞれ、Auction 変数の中で定義されているuint64, uint128の変数に対して、64bit, 128bitでそれぞれ収まるような値が渡されていることをチェックしている修飾子です。
関数について
/// @dev Returns true if the claimant owns the token.
/// @param _nft - The address of the NFT.
/// @param _claimant - Address claiming to own the token.
/// @param _tokenId - ID of token whose ownership to verify.
function _owns(address _nft, address _claimant, uint256 _tokenId) internal view returns (bool) {
ERC721 nonFungibleContract = _getNft(_nft);
return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
}
/// @dev Escrows the NFT, assigning ownership to this contract.
/// Throws if the escrow fails.
/// @param _nft - The address of the NFT.
/// @param _owner - Current owner address of token to escrow.
/// @param _tokenId - ID of token whose approval to verify.
function _escrow(address _nft, address _owner, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transferFrom(_owner, this, _tokenId);
}
/// @dev Transfers an NFT owned by this contract to another address.
/// Returns true if the transfer succeeds.
/// @param _nft - The address of the NFT.
/// @param _receiver - Address to transfer NFT to.
/// @param _tokenId - ID of token to transfer.
function _transfer(address _nft, address _receiver, uint256 _tokenId) internal {
ERC721 nonFungibleContract = _getNft(_nft);
// it will throw if transfer fails
nonFungibleContract.transfer(_receiver, _tokenId);
}
/// @dev Adds an auction to the list of open auctions. Also fires the
/// AuctionCreated event.
/// @param _tokenId The ID of the token to be put on auction.
/// @param _auction Auction to add.
function _addAuction(address _nft, uint256 _tokenId, Auction _auction) internal {
// Require that all auctions have a duration of
// at least one minute. (Keeps our math from getting hairy!)
require(_auction.duration >= 1 minutes);
nftToTokenIdToAuction[_nft][_tokenId] = _auction;
AuctionCreated(
address(_nft),
uint256(_tokenId),
uint256(_auction.startingPrice),
uint256(_auction.endingPrice),
uint256(_auction.duration)
);
}
/// @dev Cancels an auction unconditionally.
function _cancelAuction(address _nft, uint256 _tokenId, address _seller) internal {
_removeAuction(_nft, _tokenId);
_transfer(_nft, _seller, _tokenId);
AuctionCancelled(_nft, _tokenId);
}
/// @dev Computes the price and transfers winnings.
/// Does NOT transfer ownership of token.
function _bid(address _nft, uint256 _tokenId, uint256 _bidAmount)
internal
returns (uint256)
{
// Get a reference to the auction struct
Auction storage auction = nftToTokenIdToAuction[_nft][_tokenId];
// Explicitly check that this auction is currently live.
// (Because of how Ethereum mappings work, we can't just count
// on the lookup above failing. An invalid _tokenId will just
// return an auction object that is all zeros.)
require(_isOnAuction(auction));
// Check that the incoming bid is higher than the current
// price
uint256 price = _currentPrice(auction);
require(_bidAmount >= price);
// Grab a reference to the seller before the auction struct
// gets deleted.
address seller = auction.seller;
// The bid is good! Remove the auction before sending the fees
// to the sender so we can't have a reentrancy attack.
_removeAuction(_nft, _tokenId);
// Transfer proceeds to seller (if there are any!)
if (price > 0) {
// Calculate the auctioneer's cut.
// (NOTE: _computeCut() is guaranteed to return a
// value <= price, so this subtraction can't go negative.)
uint256 auctioneerCut = _computeCut(price);
uint256 sellerProceeds = price - auctioneerCut;
// NOTE: Doing a transfer() in the middle of a complex
// method like this is generally discouraged because of
// reentrancy attacks and DoS attacks if the seller is
// a contract with an invalid fallback function. We explicitly
// guard against reentrancy attacks by removing the auction
// before calling transfer(), and the only thing the seller
// can DoS is the sale of their own asset! (And if it's an
// accident, they can call cancelAuction(). )
seller.transfer(sellerProceeds);
}
// Tell the world!
AuctionSuccessful(_nft, _tokenId, price, msg.sender);
return price;
}
/// @dev Removes an auction from the list of open auctions.
/// @param _tokenId - ID of NFT on auction.
function _removeAuction(address _nft, uint256 _tokenId) internal {
delete nftToTokenIdToAuction[_nft][_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.startingPrice,
_auction.endingPrice,
_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.
return _price * ownerCut / 10000;
}
/// @dev Gets the NFT object from an address, validating that implementsERC721 is true.
/// @param _nft - Address of the NFT.
function _getNft(address _nft) internal view returns (ERC721) {
ERC721 candidateContract = ERC721(_nft);
//require(candidateContract.implementsERC721());
return candidateContract;
}
ここでは、ClockAuction contractないの関数で使用される関数がinternal修飾子で定義されています。
- _owns
- tokenのaddress, userのaddress, tokenのidを引数にとり、そのトークンがuserのものかどうかを返す。
- _escrow
- トークンの所有権をオーナーからescrowコントラクトへ移す
- _transfer
- トークンを受け取り人のaddressへ移す
- _addAuction
- 新しいオークションの情報を追加します。
- _cancelAuction
- _removeAuction ⇨ _transfer
- オークションの削除, トークンの返還
- _bid
- 入札時の送金処理
- _removeAuction
- nftToTokenIdToAuction変数からオークションの情報を削除する
- _isOnAuction
- startedAtが設定されているかどうかでトークンがオークションにあるかどうかを確認
- _currentPrice
- 現在の価格を表示
- _computeCurrentPrice
- 最初の価格、終了時の価格、今の価格、開始から経過した時間を返す
- _computeCut
- コントラクトOwnerがもらう金額を計算
- _getNft
- ERC721トークンコントラクトを取得
ClockAuction
これはオークションの開催、キャンセル、オークション情報の取得ができるようになっています。
public 関数が定義してありますが中身の処理はClockAuctionBaseの中に全てあります。
中身をみながら記事を書いてましたが意外と味気ない感じでした。仕組みとしては時間とともに価格が線形に変化していくオークションで、ERC721のインターフェースを用意することで外部のERC721トークンを登録できるようにしている、というものでした。
Mainnetの方に上がっているものもみてみようと思います。