Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
470 lines (365 sloc) 16.1 KB
pragma solidity 0.4.18;
/*
Sketches:
- can be created
- can be traded: you make a bid, the other party can accept or you can withdraw the bid
- can not be destroyed
*/
contract Ownable {
address public owner;
function Ownable() public {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0));
owner = newOwner;
}
}
contract SketchMarket is Ownable {
// ERC-20 compatibility {
string public standard = "CryptoSketches";
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 value);
// }
// Sketch storage {
mapping(uint256 => string) public sketchIndexToName;
mapping(uint256 => string) public sketchIndexToData;
mapping(uint256 => address) public sketchIndexToHolder;
mapping(uint256 => address) public sketchIndexToAuthor;
mapping(uint256 => uint8) public sketchIndexToOwnerFlag;
mapping(address => uint256) public sketchAuthorCount;
event SketchCreated(address indexed author, uint256 indexed sketchIndex);
// }
// Sketch trading {
// 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;
// Amount owner takes on each submission, measured in Wei.
uint256 public listingFeeInWei;
mapping (uint256 => Offer) public sketchIndexToOffer;
mapping (uint256 => Bid) public sketchIndexToHighestBid;
mapping (address => uint256) public accountToWithdrawableValue;
event SketchTransfer(uint256 indexed sketchIndex, address indexed fromAddress, address indexed toAddress);
event SketchOffered(uint256 indexed sketchIndex, uint256 minValue, address indexed toAddress);
event SketchBidEntered(uint256 indexed sketchIndex, uint256 value, address indexed fromAddress);
event SketchBidWithdrawn(uint256 indexed sketchIndex, uint256 value, address indexed fromAddress);
event SketchBought(uint256 indexed sketchIndex, uint256 value, address indexed fromAddress, address indexed toAddress);
event SketchNoLongerForSale(uint256 indexed sketchIndex);
struct Offer {
bool isForSale;
uint256 sketchIndex;
address seller;
uint256 minValue; // ETH
address onlySellTo; // require a specific seller address
}
struct Bid {
bool hasBid;
uint256 sketchIndex;
address bidder;
uint256 value;
}
// }
// -- Constructor (see also: Ownable)
function SketchMarket() public payable {
// ERC-20 token
totalSupply = 0;
name = "CRYPTOSKETCHES";
symbol = "S̈";
decimals = 0; // whole number; number of sketches owned
// Trading parameters
ownerCut = 375; // 3.75% cut to auctioneer
listingFeeInWei = 5000000000000000; // 0.005 ETH, to discourage spam
}
function setOwnerCut(uint256 _ownerCut) external onlyOwner {
require(_ownerCut == uint256(uint16(_ownerCut)));
require(_ownerCut <= 10000);
ownerCut = _ownerCut;
}
function setListingFeeInWei(uint256 _listingFeeInWei) external onlyOwner {
require(_listingFeeInWei == uint256(uint128(_listingFeeInWei))); // length check
listingFeeInWei = _listingFeeInWei;
}
// -- Creation and fetching methods
function createSketch(string _name, string _data) external payable {
require(msg.value == listingFeeInWei);
require(bytes(_name).length < 256); // limit name byte size to 255
require(bytes(_data).length < 1048576); // limit drawing byte size to 1,048,576
accountToWithdrawableValue[owner] += msg.value; // auctioneer gets paid
sketchIndexToHolder[totalSupply] = msg.sender;
sketchIndexToAuthor[totalSupply] = msg.sender;
sketchAuthorCount[msg.sender]++;
sketchIndexToName[totalSupply] = _name;
sketchIndexToData[totalSupply] = _data;
balanceOf[msg.sender]++;
SketchCreated(msg.sender, totalSupply);
totalSupply++;
}
function setOwnerFlag(uint256 index, uint8 _ownerFlag) external onlyOwner {
sketchIndexToOwnerFlag[index] = _ownerFlag;
}
function getSketch(uint256 index) external view returns (string _name, string _data, address _holder, address _author, uint8 _ownerFlag, uint256 _highestBidValue, uint256 _offerMinValue) {
require(totalSupply != 0);
require(index < totalSupply);
_name = sketchIndexToName[index];
_data = sketchIndexToData[index];
_holder = sketchIndexToHolder[index];
_author = sketchIndexToAuthor[index];
_ownerFlag = sketchIndexToOwnerFlag[index];
_highestBidValue = sketchIndexToHighestBid[index].value;
_offerMinValue = sketchIndexToOffer[index].minValue;
}
function getBidCountForSketchesWithHolder(address _holder) external view returns (uint256) {
uint256 count = balanceOf[_holder];
if (count == 0) {
return 0;
} else {
uint256 result = 0;
uint256 totalCount = totalSupply;
uint256 sketchIndex;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if ((sketchIndexToHolder[sketchIndex] == _holder) && sketchIndexToHighestBid[sketchIndex].hasBid) {
result++;
}
}
return result;
}
}
function getSketchesOnOffer() external view returns (uint256[]) {
if (totalSupply == 0) {
return new uint256[](0);
}
uint256 count = 0;
uint256 totalCount = totalSupply;
uint256 sketchIndex;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToOffer[sketchIndex].isForSale) {
count++;
}
}
if (count == 0) {
return new uint256[](0);
}
uint256[] memory result = new uint256[](count);
uint256 resultIndex = 0;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToOffer[sketchIndex].isForSale) {
result[resultIndex] = sketchIndex;
resultIndex++;
}
}
return result;
}
function getSketchesOnOfferWithHolder(address _holder) external view returns (uint256[]) {
if (totalSupply == 0) {
return new uint256[](0);
}
uint256 count = 0;
uint256 totalCount = totalSupply;
uint256 sketchIndex;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToOffer[sketchIndex].isForSale && (sketchIndexToHolder[sketchIndex] == _holder)) {
count++;
}
}
if (count == 0) {
return new uint256[](0);
}
uint256[] memory result = new uint256[](count);
uint256 resultIndex = 0;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToOffer[sketchIndex].isForSale && (sketchIndexToHolder[sketchIndex] == _holder)) {
result[resultIndex] = sketchIndex;
resultIndex++;
}
}
return result;
}
function getSketchesWithHolder(address _holder) external view returns (uint256[]) {
uint256 count = balanceOf[_holder];
if (count == 0) {
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](count);
uint256 totalCount = totalSupply;
uint256 resultIndex = 0;
uint256 sketchIndex;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToHolder[sketchIndex] == _holder) {
result[resultIndex] = sketchIndex;
resultIndex++;
}
}
return result;
}
}
function getSketchesWithAuthor(address _author) external view returns (uint256[]) {
uint256 count = sketchAuthorCount[_author];
if (count == 0) {
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](count);
uint256 totalCount = totalSupply;
uint256 resultIndex = 0;
uint256 sketchIndex;
for (sketchIndex = 0; sketchIndex <= totalCount; sketchIndex++) {
if (sketchIndexToAuthor[sketchIndex] == _author) {
result[resultIndex] = sketchIndex;
resultIndex++;
}
}
return result;
}
}
// -- Trading methods
modifier onlyHolderOf(uint256 sketchIndex) {
require(totalSupply != 0);
require(sketchIndex < totalSupply);
require(sketchIndexToHolder[sketchIndex] == msg.sender);
_;
}
// Transfer holdership without requiring payment
function transferSketch(address to, uint256 sketchIndex) external onlyHolderOf(sketchIndex) {
require(to != address(0));
require(balanceOf[msg.sender] > 0);
if (sketchIndexToOffer[sketchIndex].isForSale) {
sketchNoLongerForSale(sketchIndex); // remove the offer
}
sketchIndexToHolder[sketchIndex] = to;
balanceOf[msg.sender]--;
balanceOf[to]++;
Transfer(msg.sender, to, 1); // ERC-20
SketchTransfer(sketchIndex, msg.sender, to);
// If the recipient had bid for the Sketch, remove the bid and make it possible to refund its value
Bid storage bid = sketchIndexToHighestBid[sketchIndex];
if (bid.bidder == to) {
accountToWithdrawableValue[to] += bid.value;
sketchIndexToHighestBid[sketchIndex] = Bid(false, sketchIndex, 0x0, 0);
}
}
// Withdraw Sketch from sale (NOTE: does not cancel bids, since bids must be withdrawn manually by bidders)
function sketchNoLongerForSale(uint256 _sketchIndex) public onlyHolderOf(_sketchIndex) {
sketchIndexToOffer[_sketchIndex] = Offer(false, _sketchIndex, msg.sender, 0, 0x0);
SketchNoLongerForSale(_sketchIndex);
}
// Place a Sketch up for sale, to any buyer
function offerSketchForSale(uint256 _sketchIndex, uint256 _minSalePriceInWei) public onlyHolderOf(_sketchIndex) {
sketchIndexToOffer[_sketchIndex] = Offer(true, _sketchIndex, msg.sender, _minSalePriceInWei, 0x0);
SketchOffered(_sketchIndex, _minSalePriceInWei, 0x0);
}
// Place a Sketch up for sale, but only to a specific buyer
function offerSketchForSaleToAddress(uint256 _sketchIndex, uint256 _minSalePriceInWei, address _toAddress) public onlyHolderOf(_sketchIndex) {
require(_toAddress != address(0));
require(_toAddress != msg.sender);
sketchIndexToOffer[_sketchIndex] = Offer(true, _sketchIndex, msg.sender, _minSalePriceInWei, _toAddress);
SketchOffered(_sketchIndex, _minSalePriceInWei, _toAddress);
}
// Accept a bid for a Sketch that you own, receiving the amount for withdrawal at any time - note minPrice safeguard!
function acceptBidForSketch(uint256 sketchIndex, uint256 minPrice) public onlyHolderOf(sketchIndex) {
address seller = msg.sender;
require(balanceOf[seller] > 0);
Bid storage bid = sketchIndexToHighestBid[sketchIndex];
uint256 price = bid.value;
address bidder = bid.bidder;
require(price > 0);
require(price == uint256(uint128(price))); // length check for computeCut(...)
require(minPrice == uint256(uint128(minPrice))); // length check for computeCut(...)
require(price >= minPrice); // you may be accepting a different bid than you think, but its value will be at least as high
sketchIndexToHolder[sketchIndex] = bidder; // transfer actual holdership!
balanceOf[seller]--; // update balances
balanceOf[bidder]++;
Transfer(seller, bidder, 1);
sketchIndexToOffer[sketchIndex] = Offer(false, sketchIndex, bidder, 0, 0x0); // remove the offer
sketchIndexToHighestBid[sketchIndex] = Bid(false, sketchIndex, 0x0, 0); // remove the bid
uint256 ownerProceeds = computeCut(price);
uint256 holderProceeds = price - ownerProceeds;
accountToWithdrawableValue[seller] += holderProceeds; // make profit available to seller for withdrawal
accountToWithdrawableValue[owner] += ownerProceeds; // make cut available to auctioneer for withdrawal
SketchBought(sketchIndex, price, seller, bidder); // note that SketchNoLongerForSale event will not be fired
}
// Buy a Sketch that's up for sale now, provided you've matched the Offer price and it's not on offer to a specific buyer
function buySketch(uint256 sketchIndex) external payable {
Offer storage offer = sketchIndexToOffer[sketchIndex];
uint256 messageValue = msg.value;
require(totalSupply != 0);
require(sketchIndex < totalSupply);
require(offer.isForSale);
require(offer.onlySellTo == 0x0 || offer.onlySellTo == msg.sender);
require(messageValue >= offer.minValue);
require(messageValue == uint256(uint128(messageValue))); // length check for computeCut(...)
require(offer.seller == sketchIndexToHolder[sketchIndex]); // the holder may have changed since an Offer was last put up
address holder = offer.seller;
require(balanceOf[holder] > 0);
sketchIndexToHolder[sketchIndex] = msg.sender; // transfer actual holdership!
balanceOf[holder]--; // update balances
balanceOf[msg.sender]++;
Transfer(holder, msg.sender, 1);
sketchNoLongerForSale(sketchIndex); // remove the offer
uint256 ownerProceeds = computeCut(messageValue);
uint256 holderProceeds = messageValue - ownerProceeds;
accountToWithdrawableValue[owner] += ownerProceeds;
accountToWithdrawableValue[holder] += holderProceeds;
SketchBought(sketchIndex, messageValue, holder, msg.sender);
// Refund any bid the new buyer had placed for this Sketch.
// Other bids have to stay put for continued consideration or until their values have been withdrawn.
Bid storage bid = sketchIndexToHighestBid[sketchIndex];
if (bid.bidder == msg.sender) {
accountToWithdrawableValue[msg.sender] += bid.value;
sketchIndexToHighestBid[sketchIndex] = Bid(false, sketchIndex, 0x0, 0); // remove the bid
}
}
// Withdraw any value owed to:
// (a) a buyer that withdraws their bid or invalidates it by purchasing a Sketch outright for its asking price
// (b) a seller owed funds from the sale of a Sketch
function withdraw() external {
uint256 amount = accountToWithdrawableValue[msg.sender];
// Zero the pending refund before transferring to prevent re-entrancy attacks
accountToWithdrawableValue[msg.sender] = 0;
msg.sender.transfer(amount);
}
// Enter a bid, regardless of whether the Sketch holder wishes to sell or not
function enterBidForSketch(uint256 sketchIndex) external payable {
require(totalSupply != 0);
require(sketchIndex < totalSupply);
require(sketchIndexToHolder[sketchIndex] != 0x0); // can't bid on "non-owned" Sketch (theoretically impossible anyway)
require(sketchIndexToHolder[sketchIndex] != msg.sender); // can't bid on a Sketch that you own
uint256 price = msg.value; // in wei
require(price > 0); // can't bid zero
require(price == uint256(uint128(price))); // length check for computeCut(...)
Bid storage existing = sketchIndexToHighestBid[sketchIndex];
require(price > existing.value); // can't bid less than highest bid
if (existing.value > 0) {
// Place the amount from the previous highest bid into escrow for withdrawal at any time
accountToWithdrawableValue[existing.bidder] += existing.value;
}
sketchIndexToHighestBid[sketchIndex] = Bid(true, sketchIndex, msg.sender, price);
SketchBidEntered(sketchIndex, price, msg.sender);
}
function withdrawBidForSketch(uint256 sketchIndex) public {
require(totalSupply != 0);
require(sketchIndex < totalSupply);
require(sketchIndexToHolder[sketchIndex] != 0x0); // can't bid on "non-owned" Sketch (theoretically impossible anyway)
require(sketchIndexToHolder[sketchIndex] != msg.sender); // can't withdraw a bid for a Sketch that you own
Bid storage bid = sketchIndexToHighestBid[sketchIndex];
require(bid.bidder == msg.sender); // it has to be your bid
SketchBidWithdrawn(sketchIndex, bid.value, msg.sender);
uint256 amount = bid.value;
sketchIndexToHighestBid[sketchIndex] = Bid(false, sketchIndex, 0x0, 0);
// Refund the bid money directly
msg.sender.transfer(amount);
}
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. The result of this
// function is always guaranteed to be <= _price.
return price * ownerCut / 10000;
}
}