pragma solidity ^0.4.11;
contract Grid {
// The account address with admin privilege to this contract
// This is also the default owner of all unowned pixels
address admin;
// The size in number of pixels of the square grid on each side
uint16 public size;
// The default price of unowned pixels
uint public defaultPrice;
// The price-fee ratio used in the following formula:
// salePrice / feeRatio = fee
// payout = salePrice - fee
// Higher feeRatio equates to lower fee percentage
uint public feeRatio;
// The price increment rate used in the following formula:
// price = prevPrice + (prevPrice * incrementRate / 100);
uint public incrementRate;
// A record of a user who may at any time be an owner of pixels or simply has
// unclaimed withdrawal from a failed purchase or a successful sale
struct User {
// Number of Wei that can be withdrawn by the user
uint pendingWithdrawal;
// Number of Wei in total ever credited to the user as a result of a
// successful sale
uint totalSales;
}
struct Pixel {
// User with permission to modify the pixel. A successful sale of the
// pixel will result in payouts being credited to the pendingWithdrawal of
// the User
address owner;
// Current listed price of the pixel
uint price;
// Current color of the pixel. A valid of 0 is considered transparent and
// not black. Use 1 for black.
uint24 color;
}
// The state of the pixel grid
mapping(uint32 => Pixel) pixels;
// The state of all users who have transacted with this contract
mapping(address => User) users;
// An optional message that is shown in some parts of the UI and in the
// details pane of every owned pixel
mapping(address => string) messages;
//============================================================================
// Events
//============================================================================
event PixelTransfer(uint16 row, uint16 col, uint price, address prevOwner, address newOwner);
event PixelColor(uint16 row, uint16 col, address owner, uint24 color);
event PixelPrice(uint16 row, uint16 col, address owner, uint price);
event UserMessage(address user, string message);
//============================================================================
// Basic API and helper functions
//============================================================================
function Grid(
uint16 _size,
uint _defaultPrice,
uint _feeRatio,
uint _incrementRate) {
admin = msg.sender;
defaultPrice = _defaultPrice;
feeRatio = _feeRatio;
size = _size;
incrementRate = _incrementRate;
}
modifier onlyAdmin {
require(msg.sender == admin);
_;
}
modifier onlyOwner(uint16 row, uint16 col) {
require(msg.sender == getPixelOwner(row, col));
_;
}
function getKey(uint16 row, uint16 col) constant returns (uint32) {
require(row < size && col < size);
return uint32(SafeMath.add(SafeMath.mul(row, size), col));
}
function() payable {}
//============================================================================
// Admin API
//============================================================================
function setAdmin(address _admin) onlyAdmin {
admin = _admin;
}
function setFeeRatio(uint _feeRatio) onlyAdmin {
feeRatio = _feeRatio;
}
function setDefaultPrice(uint _defaultPrice) onlyAdmin {
defaultPrice = _defaultPrice;
}
//============================================================================
// Public Querying API
//============================================================================
function getPixelColor(uint16 row, uint16 col) constant returns (uint24) {
uint32 key = getKey(row, col);
return pixels[key].color;
}
function getPixelOwner(uint16 row, uint16 col) constant returns (address) {
uint32 key = getKey(row, col);
if (pixels[key].owner == 0) {
return admin;
}
return pixels[key].owner;
}
function getPixelPrice(uint16 row, uint16 col) constant returns (uint) {
uint32 key = getKey(row, col);
if (pixels[key].owner == 0) {
return defaultPrice;
}
return pixels[key].price;
}
function getUserMessage(address user) constant returns (string) {
return messages[user];
}
function getUserTotalSales(address user) constant returns (uint) {
return users[user].totalSales;
}
//============================================================================
// Public Transaction API
//============================================================================
function checkPendingWithdrawal() constant returns (uint) {
return users[msg.sender].pendingWithdrawal;
}
function withdraw() {
if (users[msg.sender].pendingWithdrawal > 0) {
uint amount = users[msg.sender].pendingWithdrawal;
users[msg.sender].pendingWithdrawal = 0;
msg.sender.transfer(amount);
}
}
function buyPixel(uint16 row, uint16 col, uint24 newColor) payable {
uint balance = users[msg.sender].pendingWithdrawal;
// Return instead of letting getKey throw here to correctly refund the
// transaction by updating the user balance in user.pendingWithdrawal
if (row >= size || col >= size) {
users[msg.sender].pendingWithdrawal = SafeMath.add(balance, msg.value);
return;
}
uint32 key = getKey(row, col);
uint price = getPixelPrice(row, col);
address owner = getPixelOwner(row, col);
// Return instead of throw here to correctly refund the transaction by
// updating the user balance in user.pendingWithdrawal
if (msg.value < price) {
users[msg.sender].pendingWithdrawal = SafeMath.add(balance, msg.value);
return;
}
uint fee = SafeMath.div(msg.value, feeRatio);
uint payout = SafeMath.sub(msg.value, fee);
uint adminBalance = users[admin].pendingWithdrawal;
users[admin].pendingWithdrawal = SafeMath.add(adminBalance, fee);
uint ownerBalance = users[owner].pendingWithdrawal;
users[owner].pendingWithdrawal = SafeMath.add(ownerBalance, payout);
users[owner].totalSales = SafeMath.add(users[owner].totalSales, payout);
// Increase the price automatically based on the global incrementRate
uint increase = SafeMath.div(SafeMath.mul(price, incrementRate), 100);
pixels[key].price = SafeMath.add(price, increase);
pixels[key].owner = msg.sender;
PixelTransfer(row, col, price, owner, msg.sender);
setPixelColor(row, col, newColor);
}
//============================================================================
// Owner Management API
//============================================================================
function transferPixel(uint16 row, uint16 col, address newOwner) onlyOwner(row, col) {
uint32 key = getKey(row, col);
address owner = pixels[key].owner;
if (owner != newOwner) {
pixels[key].owner = newOwner;
PixelTransfer(row, col, 0, owner, newOwner);
}
}
function setPixelColor(uint16 row, uint16 col, uint24 color) onlyOwner(row, col) {
uint32 key = getKey(row, col);
if (pixels[key].color != color) {
pixels[key].color = color;
PixelColor(row, col, pixels[key].owner, color);
}
}
function setPixelPrice(uint16 row, uint16 col, uint newPrice) onlyOwner(row, col) {
uint32 key = getKey(row, col);
// The owner can only lower the price. Price increases are determined by
// the global incrementRate
require(pixels[key].price > newPrice);
pixels[key].price = newPrice;
PixelPrice(row, col, pixels[key].owner, newPrice);
}
//============================================================================
// User Management API
//============================================================================
function setUserMessage(string message) {
messages[msg.sender] = message;
UserMessage(msg.sender, message);
}
}
library SafeMath {
function mul(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
{
"compilationTarget": {
"Grid.sol": "Grid"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"incrementRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"},{"name":"newOwner","type":"address"}],"name":"transferPixel","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_feeRatio","type":"uint256"}],"name":"setFeeRatio","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"},{"name":"color","type":"uint24"}],"name":"setPixelColor","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"feeRatio","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"},{"name":"newColor","type":"uint24"}],"name":"buyPixel","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_defaultPrice","type":"uint256"}],"name":"setDefaultPrice","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_admin","type":"address"}],"name":"setAdmin","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"message","type":"string"}],"name":"setUserMessage","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"}],"name":"getPixelOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"size","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"}],"name":"getKey","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"}],"name":"getUserTotalSales","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"},{"name":"newPrice","type":"uint256"}],"name":"setPixelPrice","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"}],"name":"getPixelColor","outputs":[{"name":"","type":"uint24"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"}],"name":"getUserMessage","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"defaultPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"row","type":"uint16"},{"name":"col","type":"uint16"}],"name":"getPixelPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"checkPendingWithdrawal","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"_size","type":"uint16"},{"name":"_defaultPrice","type":"uint256"},{"name":"_feeRatio","type":"uint256"},{"name":"_incrementRate","type":"uint256"}],"payable":false,"type":"constructor"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"row","type":"uint16"},{"indexed":false,"name":"col","type":"uint16"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":false,"name":"prevOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"PixelTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"row","type":"uint16"},{"indexed":false,"name":"col","type":"uint16"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"color","type":"uint24"}],"name":"PixelColor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"row","type":"uint16"},{"indexed":false,"name":"col","type":"uint16"},{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"price","type":"uint256"}],"name":"PixelPrice","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"user","type":"address"},{"indexed":false,"name":"message","type":"string"}],"name":"UserMessage","type":"event"}]