pragma solidity ^0.4.11;
contract Ownable {
address public owner;
/**
* @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 {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)
contract ERC721 {
// Required methods
function totalSupply() public view returns (uint256 total);
function balanceOf(address _owner) public view returns (uint256 balance);
function ownerOf(uint256 _tokenId) external view returns (address owner);
function approve(address _to, uint256 _tokenId) external;
function transfer(address _to, uint256 _tokenId) external;
function transferFrom(address _from, address _to, uint256 _tokenId) external;
// Events
event Transfer(address from, address to, uint256 tokenId);
event Approval(address owner, address approved, uint256 tokenId);
// Optional
// function name() public view returns (string name);
// function symbol() public view returns (string symbol);
// function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
// function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);
// ERC-165 Compatibility (https://github.com/ethereum/EIPs/issues/165)
function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}
contract MonsterAccessControl {
event ContractUpgrade(address newContract);
// The addresses of the accounts (or contracts) that can execute actions within each roles.
address public adminAddress;
/// @dev Access modifier for CEO-only functionality
modifier onlyAdmin() {
require(msg.sender == adminAddress);
_;
}
}
// This contract stores all data on the blockchain
// only our other contracts can interact with this
// the data here will be valid for all eternity even if other contracts get updated
// this way we can make sure that our Monsters have a hard-coded value attached to them
// that no one including us can change(!)
contract MonstersData {
address coreContract; //
struct Monster {
// timestamp of block when this monster was spawned/created
uint64 birthTime;
// generation number
// gen0 is the very first generation - the later monster spawn the less likely they are to have
// special attributes and stats
// uint16 generation;
uint16 hp; // health points
uint16 attack; // attack points
uint16 defense; // defense points
uint16 spAttack; // special attack
uint16 spDefense; // special defense
uint16 speed; // speed responsible of who attacks first(!)
uint16 typeOne;
uint16 typeTwo;
uint16 mID; // this id (from 1 to 151) is responsible for everything visually like showing the real deal!
bool tradeable;
//uint16 uID; // unique id
// These attributes are handled by mappings since they would overflow the maximum stack
//bool female
// string nickname
}
// lv1 base stats
struct MonsterBaseStats {
uint16 hp;
uint16 attack;
uint16 defense;
uint16 spAttack;
uint16 spDefense;
uint16 speed;
}
// lomonsterion struct used for travelling around the "world"
//
struct Area {
// areaID used in-engine to determine world position
// minimum level to enter this area...
uint16 minLevel;
}
struct Trainer {
// timestamp of block when this player/trainer was created
uint64 birthTime;
// add username
string username;
// current area in the "world"
uint16 currArea;
address owner;
}
// take timestamp of block this game was created on the blockchain
uint64 creationBlock = uint64(now);
}
contract MonstersBase is MonsterAccessControl, MonstersData {
/// @dev Transfer event as defined in current draft of ERC721. Emitted every time a monster
/// ownership is assigned, including births.
event Transfer(address from, address to, uint256 tokenId);
bool lockedMonsterCreator = false;
MonsterAuction public monsterAuction;
MonsterCreatorInterface public monsterCreator;
function setMonsterCreatorAddress(address _address) external onlyAdmin {
// only set this once so we (the devs) can't cheat!
require(!lockedMonsterCreator);
MonsterCreatorInterface candidateContract = MonsterCreatorInterface(_address);
monsterCreator = candidateContract;
lockedMonsterCreator = true;
}
// An approximation of currently how many seconds are in between blocks.
uint256 public secondsPerBlock = 15;
// array containing all monsters in existence
Monster[] monsters;
uint8[] areas;
uint8 areaIndex = 0;
mapping(address => Trainer) public addressToTrainer;
/// @dev A mapping from monster IDs to the address that owns them. All monster have
/// some valid owner address, even gen0 monster are created with a non-zero owner.
mapping (uint256 => address) public monsterIndexToOwner;
// @dev A mapping from owner address to count of tokens that address owns.
// Used internally inside balanceOf() to resolve ownership count.
mapping (address => uint256) ownershipTokenCount;
mapping (uint256 => address) public monsterIndexToApproved;
mapping (uint256 => string) public monsterIdToNickname;
mapping (uint256 => bool) public monsterIdToTradeable;
mapping (uint256 => uint256) public monsterIdToGeneration;
mapping (uint256 => MonsterBaseStats) public baseStats;
mapping (uint256 => uint8[7]) public monsterIdToIVs;
// adds new area to world
function _createArea() internal {
areaIndex++;
areas.push(areaIndex);
}
function _createMonster(
uint256 _generation,
uint256 _hp,
uint256 _attack,
uint256 _defense,
uint256 _spAttack,
uint256 _spDefense,
uint256 _speed,
uint256 _typeOne,
uint256 _typeTwo,
address _owner,
uint256 _mID,
bool tradeable
)
internal
returns (uint)
{
Monster memory _monster = Monster({
birthTime: uint64(now),
hp: uint16(_hp),
attack: uint16(_attack),
defense: uint16(_defense),
spAttack: uint16(_spAttack),
spDefense: uint16(_spDefense),
speed: uint16(_speed),
typeOne: uint16(_typeOne),
typeTwo: uint16(_typeTwo),
mID: uint16(_mID),
tradeable: tradeable
});
uint256 newMonsterId = monsters.push(_monster) - 1;
monsterIdToTradeable[newMonsterId] = tradeable;
monsterIdToGeneration[newMonsterId] = _generation;
require(newMonsterId == uint256(uint32(newMonsterId)));
monsterIdToNickname[newMonsterId] = "";
_transfer(0, _owner, newMonsterId);
return newMonsterId;
}
function _createTrainer(string _username, uint16 _starterId, address _owner)
internal
returns (uint mon)
{
Trainer memory _trainer = Trainer({
birthTime: uint64(now),
username: string(_username),
currArea: uint16(1), // sets to first area!,
owner: address(_owner)
});
// starter stats are hardcoded!
if (_starterId == 1) {
uint8[8] memory Stats = uint8[8](monsterCreator.getMonsterStats(1));
mon = _createMonster(0, Stats[0], Stats[1], Stats[2], Stats[3], Stats[4], Stats[5], Stats[6], Stats[7], _owner, 1, false);
} else if (_starterId == 2) {
uint8[8] memory Stats2 = uint8[8](monsterCreator.getMonsterStats(4));
mon = _createMonster(0, Stats2[0], Stats2[1], Stats2[2], Stats2[3], Stats2[4], Stats2[5], Stats2[6], Stats2[7], _owner, 4, false);
} else if (_starterId == 3) {
uint8[8] memory Stats3 = uint8[8](monsterCreator.getMonsterStats(7));
mon = _createMonster(0, Stats3[0], Stats3[1], Stats3[2], Stats3[3], Stats3[4], Stats3[5], Stats3[6], Stats3[7], _owner, 7, false);
}
}
function _moveToArea(uint16 _newArea, address player) internal {
addressToTrainer[player].currArea = _newArea;
}
// assigns ownership of monster to address
function _transfer(address _from, address _to, uint256 _tokenId) internal {
ownershipTokenCount[_to]++;
monsterIndexToOwner[_tokenId] = _to;
if (_from != address(0)) {
ownershipTokenCount[_from]--;
// clear any previously approved ownership exchange
delete monsterIndexToApproved[_tokenId];
}
// Emit Transfer event
Transfer(_from, _to, _tokenId);
}
// Only admin can fix how many seconds per blocks are currently observed.
function setSecondsPerBlock(uint256 secs) external onlyAdmin {
//require(secs < cooldowns[0]);
secondsPerBlock = secs;
}
}
/// @title The external contract that is responsible for generating metadata for the monsters,
/// it has one function that will return the data as bytes.
contract ERC721Metadata {
/// @dev Given a token Id, returns a byte array that is supposed to be converted into string.
function getMetadata(uint256 _tokenId, string) public view returns (bytes32[4] buffer, uint256 count) {
if (_tokenId == 1) {
buffer[0] = "Hello World! :D";
count = 15;
} else if (_tokenId == 2) {
buffer[0] = "I would definitely choose a medi";
buffer[1] = "um length string.";
count = 49;
} else if (_tokenId == 3) {
buffer[0] = "Lorem ipsum dolor sit amet, mi e";
buffer[1] = "st accumsan dapibus augue lorem,";
buffer[2] = " tristique vestibulum id, libero";
buffer[3] = " suscipit varius sapien aliquam.";
count = 128;
}
}
}
contract MonsterOwnership is MonstersBase, ERC721 {
string public constant name = "ChainMonsters";
string public constant symbol = "CHMO";
// The contract that will return monster metadata
ERC721Metadata public erc721Metadata;
bytes4 constant InterfaceSignature_ERC165 =
bytes4(keccak256('supportsInterface(bytes4)'));
bytes4 constant InterfaceSignature_ERC721 =
bytes4(keccak256('name()')) ^
bytes4(keccak256('symbol()')) ^
bytes4(keccak256('totalSupply()')) ^
bytes4(keccak256('balanceOf(address)')) ^
bytes4(keccak256('ownerOf(uint256)')) ^
bytes4(keccak256('approve(address,uint256)')) ^
bytes4(keccak256('transfer(address,uint256)')) ^
bytes4(keccak256('transferFrom(address,address,uint256)')) ^
bytes4(keccak256('tokensOfOwner(address)')) ^
bytes4(keccak256('tokenMetadata(uint256,string)'));
function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
// DEBUG ONLY
//require((InterfaceSignature_ERC165 == 0x01ffc9a7) && (InterfaceSignature_ERC721 == 0x9a20483d));
return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721));
}
/// @dev Set the address of the sibling contract that tracks metadata.
/// CEO only.
function setMetadataAddress(address _contractAddress) public onlyAdmin {
erc721Metadata = ERC721Metadata(_contractAddress);
}
function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
return monsterIndexToOwner[_tokenId] == _claimant;
}
function _isTradeable(uint256 _tokenId) external view returns (bool) {
return monsterIdToTradeable[_tokenId];
}
/// @dev Checks if a given address currently has transferApproval for a particular monster.
/// @param _claimant the address we are confirming monster is approved for.
/// @param _tokenId monster id, only valid when > 0
function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) {
return monsterIndexToApproved[_tokenId] == _claimant;
}
/// @dev Marks an address as being approved for transferFrom(), overwriting any previous
/// approval. Setting _approved to address(0) clears all transfer approval.
/// NOTE: _approve() does NOT send the Approval event. This is intentional because
/// _approve() and transferFrom() are used together for putting monsters on auction, and
/// there is no value in spamming the log with Approval events in that case.
function _approve(uint256 _tokenId, address _approved) internal {
monsterIndexToApproved[_tokenId] = _approved;
}
function balanceOf(address _owner) public view returns (uint256 count) {
return ownershipTokenCount[_owner];
}
function transfer (address _to, uint256 _tokenId) external {
// Safety check to prevent against an unexpected 0x0 default.
require(_to != address(0));
// Disallow transfers to this contract to prevent accidental misuse.
// The contract should never own any monsters (except very briefly
// after a gen0 monster is created and before it goes on auction).
require(_to != address(this));
// You can only send your own monster.
require(_owns(msg.sender, _tokenId));
// Reassign ownership, clear pending approvals, emit Transfer event.
_transfer(msg.sender, _to, _tokenId);
}
/// @notice Grant another address the right to transfer a specific monster via
/// transferFrom(). This is the preferred flow for transfering NFTs to contracts.
/// @param _to The address to be granted transfer approval. Pass address(0) to
/// clear all approvals.
/// @param _tokenId The ID of the monster that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function approve(address _to, uint256 _tokenId ) external {
// Only an owner can grant transfer approval.
require(_owns(msg.sender, _tokenId));
// Register the approval (replacing any previous approval).
_approve(_tokenId, _to);
// Emit approval event.
Approval(msg.sender, _to, _tokenId);
}
/// @notice Transfer a monster owned by another address, for which the calling address
/// has previously been granted transfer approval by the owner.
/// @param _from The address that owns the monster to be transfered.
/// @param _to The address that should take ownership of the monster. Can be any address,
/// including the caller.
/// @param _tokenId The ID of the monster to be transferred.
/// @dev Required for ERC-721 compliance.
function transferFrom (address _from, address _to, uint256 _tokenId ) external {
// Safety check to prevent against an unexpected 0x0 default.
require(_to != address(0));
// Disallow transfers to this contract to prevent accidental misuse.
// The contract should never own any monsters (except very briefly
// after a gen0 monster is created and before it goes on auction).
require(_to != address(this));
// Check for approval and valid ownership
//require(_approvedFor(msg.sender, _tokenId));
require(_owns(_from, _tokenId));
// Reassign ownership (also clears pending approvals and emits Transfer event).
_transfer(_from, _to, _tokenId);
}
function totalSupply() public view returns (uint) {
return monsters.length;
}
function ownerOf(uint256 _tokenId)
external
view
returns (address owner)
{
owner = monsterIndexToOwner[_tokenId];
require(owner != address(0));
}
function tokensOfOwner(address _owner) external view returns(uint256[] ownerTokens) {
uint256 tokenCount = balanceOf(_owner);
if (tokenCount == 0) {
// Return an empty array
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](tokenCount);
uint256 totalMonsters = totalSupply();
uint256 resultIndex = 0;
uint256 monsterId;
for (monsterId = 0; monsterId <= totalMonsters; monsterId++) {
if (monsterIndexToOwner[monsterId] == _owner) {
result[resultIndex] = monsterId;
resultIndex++;
}
}
return result;
}
}
/// @dev Adapted from memcpy() by @arachnid (Nick Johnson <arachnid@notdot.net>)
/// This method is licenced under the Apache License.
/// Ref: https://github.com/Arachnid/solidity-stringutils/blob/2f6ca9accb48ae14c66f1437ec50ed19a0616f78/strings.sol
function _memcpy(uint _dest, uint _src, uint _len) private view {
// Copy word-length chunks while possible
for(; _len >= 32; _len -= 32) {
assembly {
mstore(_dest, mload(_src))
}
_dest += 32;
_src += 32;
}
// Copy remaining bytes
uint256 mask = 256 ** (32 - _len) - 1;
assembly {
let srcpart := and(mload(_src), not(mask))
let destpart := and(mload(_dest), mask)
mstore(_dest, or(destpart, srcpart))
}
}
/// @dev Adapted from toString(slice) by @arachnid (Nick Johnson <arachnid@notdot.net>)
/// This method is licenced under the Apache License.
/// Ref: https://github.com/Arachnid/solidity-stringutils/blob/2f6ca9accb48ae14c66f1437ec50ed19a0616f78/strings.sol
function _toString(bytes32[4] _rawBytes, uint256 _stringLength) private view returns (string) {
var outputString = new string(_stringLength);
uint256 outputPtr;
uint256 bytesPtr;
assembly {
outputPtr := add(outputString, 32)
bytesPtr := _rawBytes
}
_memcpy(outputPtr, bytesPtr, _stringLength);
return outputString;
}
/// @notice Returns a URI pointing to a metadata package for this token conforming to
/// ERC-721 (https://github.com/ethereum/EIPs/issues/721)
/// @param _tokenId The ID number of the monster whose metadata should be returned.
function tokenMetadata(uint256 _tokenId, string _preferredTransport) external view returns (string infoUrl) {
require(erc721Metadata != address(0));
bytes32[4] memory buffer;
uint256 count;
(buffer, count) = erc721Metadata.getMetadata(_tokenId, _preferredTransport);
return _toString(buffer, count);
}
}
contract MonsterAuctionBase {
// Reference to contract tracking NFT ownership
ERC721 public nonFungibleContract;
ChainMonstersCore public core;
struct Auction {
// current owner
address seller;
// price in wei
uint256 price;
// time when auction started
uint64 startedAt;
uint256 id;
}
// 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(uint256 => Auction) tokenIdToAuction;
mapping(uint256 => address) public auctionIdToSeller;
mapping (address => uint256) public ownershipAuctionCount;
event AuctionCreated(uint256 tokenId, uint256 price, uint256 uID, address seller);
event AuctionSuccessful(uint256 tokenId, uint256 price, address newOwner, uint256 uID);
event AuctionCancelled(uint256 tokenId, uint256 uID);
function _transfer(address _receiver, uint256 _tokenId) internal {
// it will throw if transfer fails
nonFungibleContract.transfer(_receiver, _tokenId);
}
function _addAuction(uint256 _tokenId, Auction _auction) internal {
tokenIdToAuction[_tokenId] = _auction;
AuctionCreated(
uint256(_tokenId),
uint256(_auction.price),
uint256(_auction.id),
address(_auction.seller)
);
}
function _cancelAuction(uint256 _tokenId, address _seller) internal {
Auction storage _auction = tokenIdToAuction[_tokenId];
uint256 uID = _auction.id;
_removeAuction(_tokenId);
ownershipAuctionCount[_seller]--;
_transfer(_seller, _tokenId);
AuctionCancelled(_tokenId, uID);
}
function _buy(uint256 _tokenId, uint256 _bidAmount)
internal
returns (uint256)
{
Auction storage auction = tokenIdToAuction[_tokenId];
require(_isOnAuction(auction));
uint256 price = auction.price;
require(_bidAmount >= price);
address seller = auction.seller;
uint256 uID = auction.id;
// Auction Bid looks fine! so remove
_removeAuction(_tokenId);
ownershipAuctionCount[seller]--;
if (price > 0) {
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(). )
if(seller != address(core)) {
seller.transfer(sellerProceeds);
}
}
// Calculate any excess funds included with the bid. If the excess
// is anything worth worrying about, transfer it back to bidder.
// NOTE: We checked above that the bid amount is greater than or
// equal to the price so this cannot underflow.
uint256 bidExcess = _bidAmount - price;
// Return the funds. Similar to the previous transfer, this is
// not susceptible to a re-entry attack because the auction is
// removed before any transfers occur.
msg.sender.transfer(bidExcess);
// Tell the world!
AuctionSuccessful(_tokenId, price, msg.sender, uID);
return price;
}
function _removeAuction(uint256 _tokenId) internal {
delete tokenIdToAuction[_tokenId];
}
function _isOnAuction(Auction storage _auction) internal view returns (bool) {
return (_auction.startedAt > 0);
}
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;
}
}
contract MonsterAuction is MonsterAuctionBase, Ownable {
bool public isMonsterAuction = true;
uint256 public auctionIndex = 0;
/// @dev The ERC-165 interface signature for ERC-721.
/// Ref: https://github.com/ethereum/EIPs/issues/165
/// Ref: https://github.com/ethereum/EIPs/issues/721
bytes4 constant InterfaceSignature_ERC721 = bytes4(0x9a20483d);
function MonsterAuction(address _nftAddress, uint256 _cut) public {
require(_cut <= 10000);
ownerCut = _cut;
ERC721 candidateContract = ERC721(_nftAddress);
nonFungibleContract = candidateContract;
ChainMonstersCore candidateCoreContract = ChainMonstersCore(_nftAddress);
core = candidateCoreContract;
}
// only possible to decrease ownerCut!
function setOwnerCut(uint256 _cut) external onlyOwner {
require(_cut <= ownerCut);
ownerCut = _cut;
}
function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
}
function _escrow(address _owner, uint256 _tokenId) internal {
// it will throw if transfer fails
nonFungibleContract.transferFrom(_owner, this, _tokenId);
}
function withdrawBalance() external onlyOwner {
uint256 balance = this.balance;
owner.transfer(balance);
}
function tokensInAuctionsOfOwner(address _owner) external view returns(uint256[] auctionTokens) {
uint256 numAuctions = ownershipAuctionCount[_owner];
uint256[] memory result = new uint256[](numAuctions);
uint256 totalAuctions = core.totalSupply();
uint256 resultIndex = 0;
uint256 auctionId;
for (auctionId = 0; auctionId <= totalAuctions; auctionId++) {
Auction storage auction = tokenIdToAuction[auctionId];
if (auction.seller == _owner) {
result[resultIndex] = auctionId;
resultIndex++;
}
}
return result;
}
function createAuction(uint256 _tokenId, uint256 _price, address _seller) external {
require(_price == uint256(_price));
require(core._isTradeable(_tokenId));
require(_owns(msg.sender, _tokenId));
_escrow(msg.sender, _tokenId);
Auction memory auction = Auction(
_seller,
uint256(_price),
uint64(now),
uint256(auctionIndex)
);
auctionIdToSeller[auctionIndex] = _seller;
ownershipAuctionCount[_seller]++;
auctionIndex++;
_addAuction(_tokenId, auction);
}
function buy(uint256 _tokenId) external payable {
//delete auctionIdToSeller[_tokenId];
// buy will throw if the bid or funds transfer fails
_buy (_tokenId, msg.value);
_transfer(msg.sender, _tokenId);
}
function cancelAuction(uint256 _tokenId) external {
Auction storage auction = tokenIdToAuction[_tokenId];
require(_isOnAuction(auction));
address seller = auction.seller;
require(msg.sender == seller);
_cancelAuction(_tokenId, seller);
}
function getAuction(uint256 _tokenId)
external
view
returns
(
address seller,
uint256 price,
uint256 startedAt
) {
Auction storage auction = tokenIdToAuction[_tokenId];
require(_isOnAuction(auction));
return (
auction.seller,
auction.price,
auction.startedAt
);
}
function getPrice(uint256 _tokenId)
external
view
returns (uint256)
{
Auction storage auction = tokenIdToAuction[_tokenId];
require(_isOnAuction(auction));
return auction.price;
}
}
contract ChainMonstersAuction is MonsterOwnership {
function setMonsterAuctionAddress(address _address) external onlyAdmin {
MonsterAuction candidateContract = MonsterAuction(_address);
require(candidateContract.isMonsterAuction());
monsterAuction = candidateContract;
}
uint256 public constant PROMO_CREATION_LIMIT = 5000;
uint256 public constant GEN0_CREATION_LIMIT = 5000;
// Counts the number of monster the contract owner has created.
uint256 public promoCreatedCount;
uint256 public gen0CreatedCount;
// its stats are completely dependent on the spawn alghorithm
function createPromoMonster(uint256 _mId, address _owner) external onlyAdmin {
// during generation we have to keep in mind that we have only 10,000 tokens available
// which have to be divided by 151 monsters, some rarer than others
// see WhitePaper for gen0/promo monster plan
require(promoCreatedCount < PROMO_CREATION_LIMIT);
promoCreatedCount++;
uint8[8] memory Stats = uint8[8](monsterCreator.getMonsterStats(uint256(_mId)));
uint8[7] memory IVs = uint8[7](monsterCreator.getGen0IVs());
uint256 monsterId = _createMonster(0, Stats[0], Stats[1], Stats[2], Stats[3], Stats[4], Stats[5], Stats[6], Stats[7], _owner, _mId, true);
monsterIdToTradeable[monsterId] = true;
monsterIdToIVs[monsterId] = IVs;
}
function createGen0Auction(uint256 _mId, uint256 price) external onlyAdmin {
require(gen0CreatedCount < GEN0_CREATION_LIMIT);
uint8[8] memory Stats = uint8[8](monsterCreator.getMonsterStats(uint256(_mId)));
uint8[7] memory IVs = uint8[7](monsterCreator.getGen0IVs());
uint256 monsterId = _createMonster(0, Stats[0], Stats[1], Stats[2], Stats[3], Stats[4], Stats[5], Stats[6], Stats[7], this, _mId, true);
monsterIdToTradeable[monsterId] = true;
monsterIdToIVs[monsterId] = IVs;
monsterAuction.createAuction(monsterId, price, address(this));
gen0CreatedCount++;
}
}
// used during launch for world championship
// can and will be upgraded during development with new battle system!
// this is just to give players something to do and test their monsters
// also demonstrates how we can build up more mechanics on top of our locked core contract!
contract MonsterChampionship is Ownable {
bool public isMonsterChampionship = true;
ChainMonstersCore core;
// list of top ten
address[10] topTen;
// holds the address current "world" champion
address public currChampion;
mapping (address => uint256) public addressToPowerlevel;
mapping (uint256 => address) public rankToAddress;
// try to beat every other player in the top10 with your strongest monster!
// effectively looping through all top10 players, beating them one by one
// and if strong enough placing your in the top10 as well
function contestChampion(uint256 _tokenId) external {
uint maxIndex = 9;
// fail tx if player is already champion!
// in theory players could increase their powerlevel by contesting themselves but
// this check stops that from happening so other players have the chance to
// become the temporary champion!
if (currChampion == msg.sender)
revert();
require(core.isTrainer(msg.sender));
require(core.monsterIndexToOwner(_tokenId) == msg.sender);
uint myPowerlevel = core.getMonsterPowerLevel(_tokenId);
// checks if this transaction is useless
// since we can't fight against ourself!
// also stops reentrancy attacks
require(myPowerlevel > addressToPowerlevel[msg.sender]);
uint myRank = 0;
for (uint i=0; i<=maxIndex; i++) {
//if (addres)
if ( myPowerlevel > addressToPowerlevel[topTen[i]] ) {
// you have beaten this one so increase temporary rank
myRank = i;
if (myRank == maxIndex) {
currChampion = msg.sender;
}
}
}
addressToPowerlevel[msg.sender] = myPowerlevel;
address[10] storage newTopTen = topTen;
if (currChampion == msg.sender) {
for (uint j=0; j<maxIndex; j++) {
// remove ourselves from this list in case
if (newTopTen[j] == msg.sender) {
newTopTen[j] = 0x0;
break;
}
}
}
for (uint x=0; x<=myRank; x++) {
if (x == myRank) {
newTopTen[x] = msg.sender;
} else {
if (x < maxIndex)
newTopTen[x] = topTen[x+1];
}
}
topTen = newTopTen;
}
function getTopPlayers()
external
view
returns (
address[10] players
) {
players = topTen;
}
function MonsterChampionship(address coreContract) public {
core = ChainMonstersCore(coreContract);
}
function withdrawBalance() external onlyOwner {
uint256 balance = this.balance;
owner.transfer(balance);
}
}
// where the not-so-much "hidden" magic happens
contract MonsterCreatorInterface is Ownable {
uint8 public lockedMonsterStatsCount = 0;
uint nonce = 0;
function rand(uint8 min, uint8 max) public returns (uint8) {
nonce++;
uint8 result = (uint8(sha3(block.blockhash(block.number-1), nonce ))%max);
if (result < min)
{
result = result+min;
}
return result;
}
function shinyRand(uint16 min, uint16 max) public returns (uint16) {
nonce++;
uint16 result = (uint16(sha3(block.blockhash(block.number-1), nonce ))%max);
if (result < min)
{
result = result+min;
}
return result;
}
mapping(uint256 => uint8[8]) public baseStats;
function addBaseStats(uint256 _mId, uint8[8] data) external onlyOwner {
// lock" the stats down forever
// since hp is never going to be 0 this is a valid check
// so we have to be extra careful when adding new baseStats!
require(data[0] > 0);
require(baseStats[_mId][0] == 0);
baseStats[_mId] = data;
}
function _addBaseStats(uint256 _mId, uint8[8] data) internal {
baseStats[_mId] = data;
lockedMonsterStatsCount++;
}
function MonsterCreatorInterface() public {
// these monsters are already down and "locked" down stats/design wise
_addBaseStats(1, [45, 49, 49, 65, 65, 45, 12, 4]);
_addBaseStats(2, [60, 62, 63, 80, 80, 60, 12, 4]);
_addBaseStats(3, [80, 82, 83, 100, 100, 80, 12, 4]);
_addBaseStats(4, [39, 52, 43, 60, 50, 65, 10, 6]);
_addBaseStats(5, [58, 64, 58, 80, 65, 80, 10, 6]);
_addBaseStats(6, [78, 84, 78, 109, 85, 100, 10, 6]);
_addBaseStats(7, [44, 48, 65, 50, 64, 43, 11, 14]);
_addBaseStats(8, [59, 63, 80, 65, 80, 58, 11, 14]);
_addBaseStats(9, [79, 83, 100, 85, 105, 78, 11, 14]);
_addBaseStats(10, [40, 35, 30, 20, 20, 50, 7, 4]);
_addBaseStats(149, [55, 50, 45, 135, 95, 120, 8, 14]);
_addBaseStats(150, [91, 134, 95, 100, 100, 80, 2, 5]);
_addBaseStats(151, [100, 100, 100, 100, 100, 100, 5, 19]);
}
// this serves as a lookup for new monsters to be generated since all monsters
// of the same id share the base stats
function getMonsterStats( uint256 _mID) external constant returns(uint8[8] stats) {
stats[0] = baseStats[_mID][0];
stats[1] = baseStats[_mID][1];
stats[2] = baseStats[_mID][2];
stats[3] = baseStats[_mID][3];
stats[4] = baseStats[_mID][4];
stats[5] = baseStats[_mID][5];
stats[6] = baseStats[_mID][6];
stats[7] = baseStats[_mID][7];
}
// generates randomized IVs for a new monster
function getMonsterIVs() external returns(uint8[7] ivs) {
bool shiny = false;
uint16 chance = shinyRand(1, 8192);
if (chance == 42) {
shiny = true;
}
// IVs range between 0 and 31
// stat range modified for shiny monsters!
if (shiny == true) {
ivs[0] = uint8(rand(10, 31));
ivs[1] = uint8(rand(10, 31));
ivs[2] = uint8(rand(10, 31));
ivs[3] = uint8(rand(10, 31));
ivs[4] = uint8(rand(10, 31));
ivs[5] = uint8(rand(10, 31));
ivs[6] = 1;
} else {
ivs[0] = uint8(rand(0, 31));
ivs[1] = uint8(rand(0, 31));
ivs[2] = uint8(rand(0, 31));
ivs[3] = uint8(rand(0, 31));
ivs[4] = uint8(rand(0, 31));
ivs[5] = uint8(rand(0, 31));
ivs[6] = 0;
}
}
// gen0 monsters profit from shiny boost while shiny gen0s have potentially even higher IVs!
// further increasing the rarity by also doubling the shiny chance!
function getGen0IVs() external returns (uint8[7] ivs) {
bool shiny = false;
uint16 chance = shinyRand(1, 4096);
if (chance == 42) {
shiny = true;
}
if (shiny) {
ivs[0] = uint8(rand(15, 31));
ivs[1] = uint8(rand(15, 31));
ivs[2] = uint8(rand(15, 31));
ivs[3] = uint8(rand(15, 31));
ivs[4] = uint8(rand(15, 31));
ivs[5] = uint8(rand(15, 31));
ivs[6] = 1;
} else {
ivs[0] = uint8(rand(10, 31));
ivs[1] = uint8(rand(10, 31));
ivs[2] = uint8(rand(10, 31));
ivs[3] = uint8(rand(10, 31));
ivs[4] = uint8(rand(10, 31));
ivs[5] = uint8(rand(10, 31));
ivs[6] = 0;
}
}
function withdrawBalance() external onlyOwner {
uint256 balance = this.balance;
owner.transfer(balance);
}
}
contract GameLogicContract {
bool public isGameLogicContract = true;
function GameLogicContract() public {
}
}
contract ChainMonstersCore is ChainMonstersAuction, Ownable {
// using a bool to enable us to prepare the game
bool hasLaunched = false;
// this address will hold future gamelogic in place
address gameContract;
function ChainMonstersCore() public {
adminAddress = msg.sender;
_createArea(); // area 1
_createArea(); // area 2
}
// we don't know the exact interfaces yet so use the lockedMonsterStats value to determine if the game is "ready"
// see WhitePaper for explaination for our upgrade and development roadmap
function setGameLogicContract(address _candidateContract) external onlyOwner {
require(monsterCreator.lockedMonsterStatsCount() == 151);
require(GameLogicContract(_candidateContract).isGameLogicContract());
gameContract = _candidateContract;
}
// only callable by gameContract after the full game is launched
// since all additional monsters after the promo/gen0 ones need to use this coreContract
// contract as well we have to prepare this core for our future updates where
// players can freely roam the world and hunt ChainMonsters thus generating more
function spawnMonster(uint256 _mId, address _owner) external {
require(msg.sender == gameContract);
uint8[8] memory Stats = uint8[8](monsterCreator.getMonsterStats(uint256(_mId)));
uint8[7] memory IVs = uint8[7](monsterCreator.getMonsterIVs());
// important to note that the IV generators do not use Gen0 methods and are Generation 1
// this means there won't be more than the 10,000 Gen0 monsters sold during the development through the marketplace
uint256 monsterId = _createMonster(1, Stats[0], Stats[1], Stats[2], Stats[3], Stats[4], Stats[5], Stats[6], Stats[7], _owner, _mId, true);
monsterIdToTradeable[monsterId] = true;
monsterIdToIVs[monsterId] = IVs;
}
// used to add playable content to the game
// monsters will only spawn in certain areas so some are locked on release
// due to the game being in active development on "launch"
// each monster has a maximum number of 3 areas where it can appear
//
function createArea() public onlyAdmin {
_createArea();
}
function createTrainer(string _username, uint16 _starterId) public {
require(hasLaunched);
// only one trainer/account per ethereum address
require(addressToTrainer[msg.sender].owner == 0);
// valid input check
require(_starterId == 1 || _starterId == 2 || _starterId == 3 );
uint256 mon = _createTrainer(_username, _starterId, msg.sender);
// due to stack limitations we have to assign the IVs here:
uint8[7] memory IVs = uint8[7](monsterCreator.getMonsterIVs());
monsterIdToIVs[mon] = IVs;
}
function changeUsername(string _name) public {
require(addressToTrainer[msg.sender].owner == msg.sender);
addressToTrainer[msg.sender].username = _name;
}
function changeMonsterNickname(uint256 _tokenId, string _name) public {
// users won't be able to rename a monster that is part of an auction
require(_owns(msg.sender, _tokenId));
// some string checks...?
monsterIdToNickname[_tokenId] = _name;
}
function moveToArea(uint16 _newArea) public {
// never allow anyone to move to area 0 or below since this is used
// to determine if a trainer profile exists in another method!
require(_newArea > 0);
// make sure that this area exists yet!
require(areas.length >= _newArea);
// when player is not stuck doing something else he can move freely!
_moveToArea(_newArea, msg.sender);
}
// to be changed to retrieve current stats!
function getMonster(uint256 _id) external view returns (
uint256 birthTime,
uint256 generation,
uint256 hp,
uint256 attack,
uint256 defense,
uint256 spAttack,
uint256 spDefense,
uint256 speed,
uint256 typeOne,
uint256 typeTwo,
uint256 mID,
bool tradeable,
uint256 uID
) {
Monster storage mon = monsters[_id];
birthTime = uint256(mon.birthTime);
generation = 0; // hardcoding due to stack too deep error
hp = uint256(mon.hp);
attack = uint256(mon.attack);
defense = uint256(mon.defense);
spAttack = uint256(mon.spAttack);
spDefense = uint256(mon.spDefense);
speed = uint256(mon.speed);
typeOne = uint256(mon.typeOne);
typeTwo = uint256(mon.typeTwo);
mID = uint256(mon.mID);
tradeable = bool(mon.tradeable);
// hack to overcome solidity's stack limitation in monster struct....
uID = _id;
}
// this method only returns the "base" powerlevel of a monster which will be used
// in more advanced fighting calculations later on
function getMonsterPowerLevel(uint256 _tokenId) external view returns (
uint256 powerlevel
) {
Monster storage mon = monsters[_tokenId];
uint8[7] storage IVs = monsterIdToIVs[_tokenId];
powerlevel = mon.hp + IVs[0] + mon.attack + IVs[1] + mon.defense + IVs[2] + mon.spAttack + IVs[3] + mon.spDefense + IVs[4] + mon.speed + IVs[5];
}
function isTrainer(address _check)
external
view
returns (
bool isTrainer
) {
Trainer storage trainer = addressToTrainer[_check];
if (trainer.currArea > 0)
return true;
else
return false;
}
function withdrawBalance() external onlyOwner {
uint256 balance = this.balance;
owner.transfer(balance);
}
// after we have setup everything we can unlock the game
// for public
function launchGame() external onlyOwner {
hasLaunched = true;
}
}
{
"compilationTarget": {
"ChainMonstersCore.sol": "ChainMonstersCore"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[{"name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_preferredTransport","type":"string"}],"name":"tokenMetadata","outputs":[{"name":"infoUrl","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"promoCreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"getMonsterPowerLevel","outputs":[{"name":"powerlevel","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"createArea","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newArea","type":"uint16"}],"name":"moveToArea","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"launchGame","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_name","type":"string"}],"name":"changeMonsterNickname","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"_isTradeable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"monsterIdToNickname","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_check","type":"address"}],"name":"isTrainer","outputs":[{"name":"isTrainer","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"secs","type":"uint256"}],"name":"setSecondsPerBlock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getMonster","outputs":[{"name":"birthTime","type":"uint256"},{"name":"generation","type":"uint256"},{"name":"hp","type":"uint256"},{"name":"attack","type":"uint256"},{"name":"defense","type":"uint256"},{"name":"spAttack","type":"uint256"},{"name":"spDefense","type":"uint256"},{"name":"speed","type":"uint256"},{"name":"typeOne","type":"uint256"},{"name":"typeTwo","type":"uint256"},{"name":"mID","type":"uint256"},{"name":"tradeable","type":"bool"},{"name":"uID","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"count","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"}],"name":"changeUsername","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"secondsPerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"monsterIndexToApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"name":"monsterIdToIVs","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"name":"ownerTokens","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mId","type":"uint256"},{"name":"_owner","type":"address"}],"name":"createPromoMonster","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"monsterIdToTradeable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_candidateContract","type":"address"}],"name":"setGameLogicContract","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_username","type":"string"},{"name":"_starterId","type":"uint16"}],"name":"createTrainer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"monsterIndexToOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"monsterCreator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"monsterAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"erc721Metadata","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setMonsterAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PROMO_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_contractAddress","type":"address"}],"name":"setMetadataAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setMonsterCreatorAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"addressToTrainer","outputs":[{"name":"birthTime","type":"uint64"},{"name":"username","type":"string"},{"name":"currArea","type":"uint16"},{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mId","type":"uint256"},{"name":"_owner","type":"address"}],"name":"spawnMonster","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"baseStats","outputs":[{"name":"hp","type":"uint16"},{"name":"attack","type":"uint16"},{"name":"defense","type":"uint16"},{"name":"spAttack","type":"uint16"},{"name":"spDefense","type":"uint16"},{"name":"speed","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"monsterIdToGeneration","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"gen0CreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"adminAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mId","type":"uint256"},{"name":"price","type":"uint256"}],"name":"createGen0Auction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"approved","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"}],"name":"ContractUpgrade","type":"event"}]