pragma solidity ^0.4.13;
contract MoonCatRescue {
enum Modes { Inactive, Disabled, Test, Live }
Modes public mode = Modes.Inactive;
address owner;
bytes16 public imageGenerationCodeMD5 = 0xdbad5c08ec98bec48490e3c196eec683; // use this to verify mooncatparser.js the cat image data generation javascript file.
string public name = "MoonCats";
string public symbol = "?"; // unicode cat symbol
uint8 public decimals = 0;
uint256 public totalSupply = 25600;
uint16 public remainingCats = 25600 - 256; // there will only ever be 25,000 cats
uint16 public remainingGenesisCats = 256; // there can only be a maximum of 256 genesis cats
uint16 public rescueIndex = 0;
bytes5[25600] public rescueOrder;
bytes32 public searchSeed = 0x0; // gets set with the immediately preceding blockhash when the contract is activated to prevent "premining"
struct AdoptionOffer {
bool exists;
bytes5 catId;
address seller;
uint price;
address onlyOfferTo;
}
struct AdoptionRequest{
bool exists;
bytes5 catId;
address requester;
uint price;
}
mapping (bytes5 => AdoptionOffer) public adoptionOffers;
mapping (bytes5 => AdoptionRequest) public adoptionRequests;
mapping (bytes5 => bytes32) public catNames;
mapping (bytes5 => address) public catOwners;
mapping (address => uint256) public balanceOf; //number of cats owned by a given address
mapping (address => uint) public pendingWithdrawals;
/* events */
event CatRescued(address indexed to, bytes5 indexed catId);
event CatNamed(bytes5 indexed catId, bytes32 catName);
event Transfer(address indexed from, address indexed to, uint256 value);
event CatAdopted(bytes5 indexed catId, uint price, address indexed from, address indexed to);
event AdoptionOffered(bytes5 indexed catId, uint price, address indexed toAddress);
event AdoptionOfferCancelled(bytes5 indexed catId);
event AdoptionRequested(bytes5 indexed catId, uint price, address indexed from);
event AdoptionRequestCancelled(bytes5 indexed catId);
event GenesisCatsAdded(bytes5[16] catIds);
function MoonCatRescue() payable {
owner = msg.sender;
assert((remainingCats + remainingGenesisCats) == totalSupply);
assert(rescueOrder.length == totalSupply);
assert(rescueIndex == 0);
}
/* registers and validates cats that are found */
function rescueCat(bytes32 seed) activeMode returns (bytes5) {
require(remainingCats > 0); // cannot register any cats once supply limit is reached
bytes32 catIdHash = keccak256(seed, searchSeed); // generate the prospective catIdHash
require(catIdHash[0] | catIdHash[1] | catIdHash[2] == 0x0); // ensures the validity of the catIdHash
bytes5 catId = bytes5((catIdHash & 0xffffffff) << 216); // one byte to indicate genesis, and the last 4 bytes of the catIdHash
require(catOwners[catId] == 0x0); // if the cat is already registered, throw an error. All cats are unique.
rescueOrder[rescueIndex] = catId;
rescueIndex++;
catOwners[catId] = msg.sender;
balanceOf[msg.sender]++;
remainingCats--;
CatRescued(msg.sender, catId);
return catId;
}
/* assigns a name to a cat, once a name is assigned it cannot be changed */
function nameCat(bytes5 catId, bytes32 catName) onlyCatOwner(catId) {
require(catNames[catId] == 0x0); // ensure the current name is empty; cats can only be named once
require(!adoptionOffers[catId].exists); // cats cannot be named while they are up for adoption
catNames[catId] = catName;
CatNamed(catId, catName);
}
/* puts a cat up for anyone to adopt */
function makeAdoptionOffer(bytes5 catId, uint price) onlyCatOwner(catId) {
require(price > 0);
adoptionOffers[catId] = AdoptionOffer(true, catId, msg.sender, price, 0x0);
AdoptionOffered(catId, price, 0x0);
}
/* puts a cat up for a specific address to adopt */
function makeAdoptionOfferToAddress(bytes5 catId, uint price, address to) onlyCatOwner(catId) isNotSender(to){
adoptionOffers[catId] = AdoptionOffer(true, catId, msg.sender, price, to);
AdoptionOffered(catId, price, to);
}
/* cancel an adoption offer */
function cancelAdoptionOffer(bytes5 catId) onlyCatOwner(catId) {
adoptionOffers[catId] = AdoptionOffer(false, catId, 0x0, 0, 0x0);
AdoptionOfferCancelled(catId);
}
/* accepts an adoption offer */
function acceptAdoptionOffer(bytes5 catId) payable {
AdoptionOffer storage offer = adoptionOffers[catId];
require(offer.exists);
require(offer.onlyOfferTo == 0x0 || offer.onlyOfferTo == msg.sender);
require(msg.value >= offer.price);
if(msg.value > offer.price) {
pendingWithdrawals[msg.sender] += (msg.value - offer.price); // if the submitted amount exceeds the price allow the buyer to withdraw the difference
}
transferCat(catId, catOwners[catId], msg.sender, offer.price);
}
/* transfer a cat directly without payment */
function giveCat(bytes5 catId, address to) onlyCatOwner(catId) {
transferCat(catId, msg.sender, to, 0);
}
/* requests adoption of a cat with an ETH offer */
function makeAdoptionRequest(bytes5 catId) payable isNotSender(catOwners[catId]) {
require(catOwners[catId] != 0x0); // the cat must be owned
AdoptionRequest storage existingRequest = adoptionRequests[catId];
require(msg.value > 0);
require(msg.value > existingRequest.price);
if(existingRequest.price > 0) {
pendingWithdrawals[existingRequest.requester] += existingRequest.price;
}
adoptionRequests[catId] = AdoptionRequest(true, catId, msg.sender, msg.value);
AdoptionRequested(catId, msg.value, msg.sender);
}
/* allows the owner of the cat to accept an adoption request */
function acceptAdoptionRequest(bytes5 catId) onlyCatOwner(catId) {
AdoptionRequest storage existingRequest = adoptionRequests[catId];
require(existingRequest.exists);
address existingRequester = existingRequest.requester;
uint existingPrice = existingRequest.price;
adoptionRequests[catId] = AdoptionRequest(false, catId, 0x0, 0); // the adoption request must be cancelled before calling transferCat to prevent refunding the requester.
transferCat(catId, msg.sender, existingRequester, existingPrice);
}
/* allows the requester to cancel their adoption request */
function cancelAdoptionRequest(bytes5 catId) {
AdoptionRequest storage existingRequest = adoptionRequests[catId];
require(existingRequest.exists);
require(existingRequest.requester == msg.sender);
uint price = existingRequest.price;
adoptionRequests[catId] = AdoptionRequest(false, catId, 0x0, 0);
msg.sender.transfer(price);
AdoptionRequestCancelled(catId);
}
function withdraw() {
uint amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
/* owner only functions */
/* disable contract before activation. A safeguard if a bug is found before the contract is activated */
function disableBeforeActivation() onlyOwner inactiveMode {
mode = Modes.Disabled; // once the contract is disabled it's mode cannot be changed
}
/* activates the contract in *Live* mode which sets the searchSeed and enables rescuing */
function activate() onlyOwner inactiveMode {
searchSeed = block.blockhash(block.number - 1); // once the searchSeed is set it cannot be changed;
mode = Modes.Live; // once the contract is activated it's mode cannot be changed
}
/* activates the contract in *Test* mode which sets the searchSeed and enables rescuing */
function activateInTestMode() onlyOwner inactiveMode { //
searchSeed = 0x5713bdf5d1c3398a8f12f881f0f03b5025b6f9c17a97441a694d5752beb92a3d; // once the searchSeed is set it cannot be changed;
mode = Modes.Test; // once the contract is activated it's mode cannot be changed
}
/* add genesis cats in groups of 16 */
function addGenesisCatGroup() onlyOwner activeMode {
require(remainingGenesisCats > 0);
bytes5[16] memory newCatIds;
uint256 price = (17 - (remainingGenesisCats / 16)) * 300000000000000000;
for(uint8 i = 0; i < 16; i++) {
uint16 genesisCatIndex = 256 - remainingGenesisCats;
bytes5 genesisCatId = (bytes5(genesisCatIndex) << 24) | 0xff00000ca7;
newCatIds[i] = genesisCatId;
rescueOrder[rescueIndex] = genesisCatId;
rescueIndex++;
balanceOf[0x0]++;
remainingGenesisCats--;
adoptionOffers[genesisCatId] = AdoptionOffer(true, genesisCatId, owner, price, 0x0);
}
GenesisCatsAdded(newCatIds);
}
/* aggregate getters */
function getCatIds() constant returns (bytes5[]) {
bytes5[] memory catIds = new bytes5[](rescueIndex);
for (uint i = 0; i < rescueIndex; i++) {
catIds[i] = rescueOrder[i];
}
return catIds;
}
function getCatNames() constant returns (bytes32[]) {
bytes32[] memory names = new bytes32[](rescueIndex);
for (uint i = 0; i < rescueIndex; i++) {
names[i] = catNames[rescueOrder[i]];
}
return names;
}
function getCatOwners() constant returns (address[]) {
address[] memory owners = new address[](rescueIndex);
for (uint i = 0; i < rescueIndex; i++) {
owners[i] = catOwners[rescueOrder[i]];
}
return owners;
}
function getCatOfferPrices() constant returns (uint[]) {
uint[] memory catOffers = new uint[](rescueIndex);
for (uint i = 0; i < rescueIndex; i++) {
bytes5 catId = rescueOrder[i];
if(adoptionOffers[catId].exists && adoptionOffers[catId].onlyOfferTo == 0x0) {
catOffers[i] = adoptionOffers[catId].price;
}
}
return catOffers;
}
function getCatRequestPrices() constant returns (uint[]) {
uint[] memory catRequests = new uint[](rescueIndex);
for (uint i = 0; i < rescueIndex; i++) {
bytes5 catId = rescueOrder[i];
catRequests[i] = adoptionRequests[catId].price;
}
return catRequests;
}
function getCatDetails(bytes5 catId) constant returns (bytes5 id,
address owner,
bytes32 name,
address onlyOfferTo,
uint offerPrice,
address requester,
uint requestPrice) {
return (catId,
catOwners[catId],
catNames[catId],
adoptionOffers[catId].onlyOfferTo,
adoptionOffers[catId].price,
adoptionRequests[catId].requester,
adoptionRequests[catId].price);
}
/* modifiers */
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier inactiveMode() {
require(mode == Modes.Inactive);
_;
}
modifier activeMode() {
require(mode == Modes.Live || mode == Modes.Test);
_;
}
modifier onlyCatOwner(bytes5 catId) {
require(catOwners[catId] == msg.sender);
_;
}
modifier isNotSender(address a) {
require(msg.sender != a);
_;
}
/* transfer helper */
function transferCat(bytes5 catId, address from, address to, uint price) private {
catOwners[catId] = to;
balanceOf[from]--;
balanceOf[to]++;
adoptionOffers[catId] = AdoptionOffer(false, catId, 0x0, 0, 0x0); // cancel any existing adoption offer when cat is transferred
AdoptionRequest storage request = adoptionRequests[catId]; //if the recipient has a pending adoption request, cancel it
if(request.requester == to) {
pendingWithdrawals[to] += request.price;
adoptionRequests[catId] = AdoptionRequest(false, catId, 0x0, 0);
}
pendingWithdrawals[from] += price;
Transfer(from, to, 1);
CatAdopted(catId, price, from, to);
}
}
{
"compilationTarget": {
"MoonCatRescue.sol": "MoonCatRescue"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"catId","type":"bytes5"},{"name":"price","type":"uint256"}],"name":"makeAdoptionOffer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"activate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"remainingGenesisCats","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"remainingCats","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"}],"name":"acceptAdoptionOffer","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"mode","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"catId","type":"bytes5"}],"name":"getCatDetails","outputs":[{"name":"id","type":"bytes5"},{"name":"owner","type":"address"},{"name":"name","type":"bytes32"},{"name":"onlyOfferTo","type":"address"},{"name":"offerPrice","type":"uint256"},{"name":"requester","type":"address"},{"name":"requestPrice","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCatOwners","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes5"}],"name":"catOwners","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"rescueOrder","outputs":[{"name":"","type":"bytes5"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"seed","type":"bytes32"}],"name":"rescueCat","outputs":[{"name":"","type":"bytes5"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"}],"name":"cancelAdoptionOffer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCatIds","outputs":[{"name":"","type":"bytes5[]"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCatNames","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"},{"name":"catName","type":"bytes32"}],"name":"nameCat","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"activateInTestMode","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes5"}],"name":"adoptionOffers","outputs":[{"name":"exists","type":"bool"},{"name":"catId","type":"bytes5"},{"name":"seller","type":"address"},{"name":"price","type":"uint256"},{"name":"onlyOfferTo","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes5"}],"name":"catNames","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCatRequestPrices","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"}],"name":"cancelAdoptionRequest","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"disableBeforeActivation","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"addGenesisCatGroup","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"},{"name":"price","type":"uint256"},{"name":"to","type":"address"}],"name":"makeAdoptionOfferToAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"searchSeed","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"imageGenerationCodeMD5","outputs":[{"name":"","type":"bytes16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes5"}],"name":"adoptionRequests","outputs":[{"name":"exists","type":"bool"},{"name":"catId","type":"bytes5"},{"name":"requester","type":"address"},{"name":"price","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"}],"name":"acceptAdoptionRequest","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getCatOfferPrices","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"}],"name":"makeAdoptionRequest","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"rescueIndex","outputs":[{"name":"","type":"uint16"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"pendingWithdrawals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"catId","type":"bytes5"},{"name":"to","type":"address"}],"name":"giveCat","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":true,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":true,"name":"catId","type":"bytes5"}],"name":"CatRescued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"},{"indexed":false,"name":"catName","type":"bytes32"}],"name":"CatNamed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"}],"name":"CatAdopted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":true,"name":"toAddress","type":"address"}],"name":"AdoptionOffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"}],"name":"AdoptionOfferCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"},{"indexed":false,"name":"price","type":"uint256"},{"indexed":true,"name":"from","type":"address"}],"name":"AdoptionRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"catId","type":"bytes5"}],"name":"AdoptionRequestCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"catIds","type":"bytes5[16]"}],"name":"GenesisCatsAdded","type":"event"}]