pragma solidity 0.4.24; contract Ownable { address public owner; constructor() public { owner = msg.sender; } function setOwner(address _owner) public onlyOwner { owner = _owner; } modifier onlyOwner { require(msg.sender == owner); _; } } contract Vault is Ownable { function () public payable { } function getBalance() public view returns (uint) { return address(this).balance; } function withdraw(uint amount) public onlyOwner { require(address(this).balance >= amount); owner.transfer(amount); } function withdrawAll() public onlyOwner { withdraw(address(this).balance); } } contract CappedVault is Vault { uint public limit; uint withdrawn = 0; constructor() public { limit = 33333 ether; } function () public payable { require(total() + msg.value <= limit); } function total() public view returns(uint) { return getBalance() + withdrawn; } function withdraw(uint amount) public onlyOwner { require(address(this).balance >= amount); owner.transfer(amount); withdrawn += amount; } } contract PreviousInterface { function ownerOf(uint id) public view returns (address); function getCard(uint id) public view returns (uint16, uint16); function totalSupply() public view returns (uint); function burnCount() public view returns (uint); } contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; emit Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; emit Unpause(); } } contract Governable { event Pause(); event Unpause(); address public governor; bool public paused = false; constructor() public { governor = msg.sender; } function setGovernor(address _gov) public onlyGovernor { governor = _gov; } modifier onlyGovernor { require(msg.sender == governor); _; } /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyGovernor whenNotPaused public { paused = true; emit Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyGovernor whenPaused public { paused = false; emit Unpause(); } } contract CardBase is Governable { struct Card { uint16 proto; uint16 purity; } function getCard(uint id) public view returns (uint16 proto, uint16 purity) { Card memory card = cards[id]; return (card.proto, card.purity); } function getShine(uint16 purity) public pure returns (uint8) { return uint8(purity / 1000); } Card[] public cards; } contract CardProto is CardBase { event NewProtoCard( uint16 id, uint8 season, uint8 god, Rarity rarity, uint8 mana, uint8 attack, uint8 health, uint8 cardType, uint8 tribe, bool packable ); struct Limit { uint64 limit; bool exists; } // limits for mythic cards mapping(uint16 => Limit) public limits; // can only set limits once function setLimit(uint16 id, uint64 limit) public onlyGovernor { Limit memory l = limits[id]; require(!l.exists); limits[id] = Limit({ limit: limit, exists: true }); } function getLimit(uint16 id) public view returns (uint64 limit, bool set) { Limit memory l = limits[id]; return (l.limit, l.exists); } // could make these arrays to save gas // not really necessary - will be update a very limited no of times mapping(uint8 => bool) public seasonTradable; mapping(uint8 => bool) public seasonTradabilityLocked; uint8 public currentSeason; function makeTradable(uint8 season) public onlyGovernor { seasonTradable[season] = true; } function makeUntradable(uint8 season) public onlyGovernor { require(!seasonTradabilityLocked[season]); seasonTradable[season] = false; } function makePermanantlyTradable(uint8 season) public onlyGovernor { require(seasonTradable[season]); seasonTradabilityLocked[season] = true; } function isTradable(uint16 proto) public view returns (bool) { return seasonTradable[protos[proto].season]; } function nextSeason() public onlyGovernor { //Seasons shouldn't go to 0 if there is more than the uint8 should hold, the governor should know this ¯\_(ツ)_/¯ -M require(currentSeason <= 255); currentSeason++; mythic.length = 0; legendary.length = 0; epic.length = 0; rare.length = 0; common.length = 0; } enum Rarity { Common, Rare, Epic, Legendary, Mythic } uint8 constant SPELL = 1; uint8 constant MINION = 2; uint8 constant WEAPON = 3; uint8 constant HERO = 4; struct ProtoCard { bool exists; uint8 god; uint8 season; uint8 cardType; Rarity rarity; uint8 mana; uint8 attack; uint8 health; uint8 tribe; } // there is a particular design decision driving this: // need to be able to iterate over mythics only for card generation // don't store 5 different arrays: have to use 2 ids // better to bear this cost (2 bytes per proto card) // rather than 1 byte per instance uint16 public protoCount; mapping(uint16 => ProtoCard) protos; uint16[] public mythic; uint16[] public legendary; uint16[] public epic; uint16[] public rare; uint16[] public common; function addProtos( uint16[] externalIDs, uint8[] gods, Rarity[] rarities, uint8[] manas, uint8[] attacks, uint8[] healths, uint8[] cardTypes, uint8[] tribes, bool[] packable ) public onlyGovernor returns(uint16) { for (uint i = 0; i < externalIDs.length; i++) { ProtoCard memory card = ProtoCard({ exists: true, god: gods[i], season: currentSeason, cardType: cardTypes[i], rarity: rarities[i], mana: manas[i], attack: attacks[i], health: healths[i], tribe: tribes[i] }); _addProto(externalIDs[i], card, packable[i]); } } function addProto( uint16 externalID, uint8 god, Rarity rarity, uint8 mana, uint8 attack, uint8 health, uint8 cardType, uint8 tribe, bool packable ) public onlyGovernor returns(uint16) { ProtoCard memory card = ProtoCard({ exists: true, god: god, season: currentSeason, cardType: cardType, rarity: rarity, mana: mana, attack: attack, health: health, tribe: tribe }); _addProto(externalID, card, packable); } function addWeapon( uint16 externalID, uint8 god, Rarity rarity, uint8 mana, uint8 attack, uint8 durability, bool packable ) public onlyGovernor returns(uint16) { ProtoCard memory card = ProtoCard({ exists: true, god: god, season: currentSeason, cardType: WEAPON, rarity: rarity, mana: mana, attack: attack, health: durability, tribe: 0 }); _addProto(externalID, card, packable); } function addSpell(uint16 externalID, uint8 god, Rarity rarity, uint8 mana, bool packable) public onlyGovernor returns(uint16) { ProtoCard memory card = ProtoCard({ exists: true, god: god, season: currentSeason, cardType: SPELL, rarity: rarity, mana: mana, attack: 0, health: 0, tribe: 0 }); _addProto(externalID, card, packable); } function addMinion( uint16 externalID, uint8 god, Rarity rarity, uint8 mana, uint8 attack, uint8 health, uint8 tribe, bool packable ) public onlyGovernor returns(uint16) { ProtoCard memory card = ProtoCard({ exists: true, god: god, season: currentSeason, cardType: MINION, rarity: rarity, mana: mana, attack: attack, health: health, tribe: tribe }); _addProto(externalID, card, packable); } function _addProto(uint16 externalID, ProtoCard memory card, bool packable) internal { require(!protos[externalID].exists); card.exists = true; protos[externalID] = card; protoCount++; emit NewProtoCard( externalID, currentSeason, card.god, card.rarity, card.mana, card.attack, card.health, card.cardType, card.tribe, packable ); if (packable) { Rarity rarity = card.rarity; if (rarity == Rarity.Common) { common.push(externalID); } else if (rarity == Rarity.Rare) { rare.push(externalID); } else if (rarity == Rarity.Epic) { epic.push(externalID); } else if (rarity == Rarity.Legendary) { legendary.push(externalID); } else if (rarity == Rarity.Mythic) { mythic.push(externalID); } else { require(false); } } } function getProto(uint16 id) public view returns( bool exists, uint8 god, uint8 season, uint8 cardType, Rarity rarity, uint8 mana, uint8 attack, uint8 health, uint8 tribe ) { ProtoCard memory proto = protos[id]; return ( proto.exists, proto.god, proto.season, proto.cardType, proto.rarity, proto.mana, proto.attack, proto.health, proto.tribe ); } function getRandomCard(Rarity rarity, uint16 random) public view returns (uint16) { // modulo bias is fine - creates rarity tiers etc // will obviously revert is there are no cards of that type: this is expected - should never happen if (rarity == Rarity.Common) { return common[random % common.length]; } else if (rarity == Rarity.Rare) { return rare[random % rare.length]; } else if (rarity == Rarity.Epic) { return epic[random % epic.length]; } else if (rarity == Rarity.Legendary) { return legendary[random % legendary.length]; } else if (rarity == Rarity.Mythic) { // make sure a mythic is available uint16 id; uint64 limit; bool set; for (uint i = 0; i < mythic.length; i++) { id = mythic[(random + i) % mythic.length]; (limit, set) = getLimit(id); if (set && limit > 0){ return id; } } // if not, they get a legendary :( return legendary[random % legendary.length]; } require(false); return 0; } // can never adjust tradable cards // each season gets a 'balancing beta' // totally immutable: season, rarity function replaceProto( uint16 index, uint8 god, uint8 cardType, uint8 mana, uint8 attack, uint8 health, uint8 tribe ) public onlyGovernor { ProtoCard memory pc = protos[index]; require(!seasonTradable[pc.season]); protos[index] = ProtoCard({ exists: true, god: god, season: pc.season, cardType: cardType, rarity: pc.rarity, mana: mana, attack: attack, health: health, tribe: tribe }); } } contract MigrationInterface { function createCard(address user, uint16 proto, uint16 purity) public returns (uint); function getRandomCard(CardProto.Rarity rarity, uint16 random) public view returns (uint16); function migrate(uint id) public; } contract CardPackFour { MigrationInterface public migration; uint public creationBlock; constructor(MigrationInterface _core) public payable { migration = _core; creationBlock = 5939061 + 2000; // set to creation block of first contracts + 8 hours for down time } event Referral(address indexed referrer, uint value, address purchaser); /** * purchase 'count' of this type of pack */ function purchase(uint16 packCount, address referrer) public payable; // store purity and shine as one number to save users gas function _getPurity(uint16 randOne, uint16 randTwo) internal pure returns (uint16) { if (randOne >= 998) { return 3000 + randTwo; } else if (randOne >= 988) { return 2000 + randTwo; } else if (randOne >= 938) { return 1000 + randTwo; } else { return randTwo; } } } contract FirstPheonix is Pausable { MigrationInterface core; constructor(MigrationInterface _core) public { core = _core; } address[] public approved; uint16 PHEONIX_PROTO = 380; mapping(address => bool) public claimed; function approvePack(address toApprove) public onlyOwner { approved.push(toApprove); } function isApproved(address test) public view returns (bool) { for (uint i = 0; i < approved.length; i++) { if (approved[i] == test) { return true; } } return false; } // pause once cards become tradable function claimPheonix(address user) public returns (bool){ require(isApproved(msg.sender)); if (claimed[user] || paused){ return false; } claimed[user] = true; core.createCard(user, PHEONIX_PROTO, 0); return true; } } contract PresalePackFour is CardPackFour, Pausable { CappedVault public vault; Purchase[] public purchases; function getPurchaseCount() public view returns (uint) { return purchases.length; } struct Purchase { uint16 current; uint16 count; address user; uint randomness; uint64 commit; } event PacksPurchased(uint indexed id, address indexed user, uint16 count); event PackOpened(uint indexed id, uint16 startIndex, address indexed user, uint[] cardIDs); event RandomnessReceived(uint indexed id, address indexed user, uint16 count, uint randomness); event Recommit(uint indexed id); constructor(MigrationInterface _core, CappedVault _vault) public payable CardPackFour(_core) { vault = _vault; } function basePrice() public returns (uint); function getCardDetails(uint16 packIndex, uint8 cardIndex, uint result) public view returns (uint16 proto, uint16 purity); function packSize() public view returns (uint8) { return 5; } uint16 public perClaim = 15; function setPacksPerClaim(uint16 _perClaim) public onlyOwner { perClaim = _perClaim; } function packsPerClaim() public view returns (uint16) { return perClaim; } // start in bytes, length in bytes function extract(uint num, uint length, uint start) internal pure returns (uint) { return (((1 << (length * 8)) - 1) & (num >> ((start * 8) - 1))); } function purchaseFor(address user, uint16 packCount, address referrer) whenNotPaused public payable { _purchase(user, packCount, referrer); } function purchase(uint16 packCount, address referrer) whenNotPaused public payable { _purchase(msg.sender, packCount, referrer); } function _purchase(address user, uint16 packCount, address referrer) internal { require(packCount > 0); require(referrer != user); uint price = calculatePrice(basePrice(), packCount); require(msg.value >= price); Purchase memory p = Purchase({ user: user, count: packCount, commit: uint64(block.number), randomness: 0, current: 0 }); uint id = purchases.push(p) - 1; emit PacksPurchased(id, user, packCount); if (referrer != address(0)) { uint commission = price / 10; referrer.transfer(commission); price -= commission; emit Referral(referrer, commission, user); } address(vault).transfer(price); } // can recommit // this gives you more chances // if no-one else sends the callback (should never happen) // still only get a random extra chance function recommit(uint id) public { Purchase storage p = purchases[id]; require(p.randomness == 0); require(block.number >= p.commit + 256); p.commit = uint64(block.number); emit Recommit(id); } // can be called by anybody // can miners withhold blocks --> not really // giving up block reward for extra chance --> still really low function callback(uint id) public { Purchase storage p = purchases[id]; require(p.randomness == 0); // must be within last 256 blocks, otherwise recommit require(block.number - 256 < p.commit); // can't callback on the original block require(uint64(block.number) != p.commit); bytes32 bhash = blockhash(p.commit); // will get the same on every block // only use properties which can't be altered by the user uint random = uint(keccak256(abi.encodePacked(bhash, p.user, address(this), p.count))); require(uint(bhash) != 0); p.randomness = random; emit RandomnessReceived(id, p.user, p.count, p.randomness); } function claim(uint id) public { Purchase storage p = purchases[id]; require(canClaim); uint16 proto; uint16 purity; uint16 count = p.count; uint result = p.randomness; uint8 size = packSize(); address user = p.user; uint16 current = p.current; require(result != 0); // have to wait for the callback // require(user == msg.sender); // not needed require(count > 0); uint[] memory ids = new uint[](size); uint16 end = current + packsPerClaim() > count ? count : current + packsPerClaim(); require(end > current); for (uint16 i = current; i < end; i++) { for (uint8 j = 0; j < size; j++) { (proto, purity) = getCardDetails(i, j, result); ids[j] = migration.createCard(user, proto, purity); } emit PackOpened(id, (i * size), user, ids); } p.current += (end - current); } function predictPacks(uint id) external view returns (uint16[] protos, uint16[] purities) { Purchase memory p = purchases[id]; uint16 proto; uint16 purity; uint16 count = p.count; uint result = p.randomness; uint8 size = packSize(); purities = new uint16[](size * count); protos = new uint16[](size * count); for (uint16 i = 0; i < count; i++) { for (uint8 j = 0; j < size; j++) { (proto, purity) = getCardDetails(i, j, result); purities[(i * size) + j] = purity; protos[(i * size) + j] = proto; } } return (protos, purities); } function calculatePrice(uint base, uint16 packCount) public view returns (uint) { // roughly 6k blocks per day uint difference = block.number - creationBlock; uint numDays = difference / 6000; if (20 > numDays) { return (base - (((20 - numDays) * base) / 100)) * packCount; } return base * packCount; } function _getCommonPlusRarity(uint32 rand) internal pure returns (CardProto.Rarity) { if (rand == 999999) { return CardProto.Rarity.Mythic; } else if (rand >= 998345) { return CardProto.Rarity.Legendary; } else if (rand >= 986765) { return CardProto.Rarity.Epic; } else if (rand >= 924890) { return CardProto.Rarity.Rare; } else { return CardProto.Rarity.Common; } } function _getRarePlusRarity(uint32 rand) internal pure returns (CardProto.Rarity) { if (rand == 999999) { return CardProto.Rarity.Mythic; } else if (rand >= 981615) { return CardProto.Rarity.Legendary; } else if (rand >= 852940) { return CardProto.Rarity.Epic; } else { return CardProto.Rarity.Rare; } } function _getEpicPlusRarity(uint32 rand) internal pure returns (CardProto.Rarity) { if (rand == 999999) { return CardProto.Rarity.Mythic; } else if (rand >= 981615) { return CardProto.Rarity.Legendary; } else { return CardProto.Rarity.Epic; } } function _getLegendaryPlusRarity(uint32 rand) internal pure returns (CardProto.Rarity) { if (rand == 999999) { return CardProto.Rarity.Mythic; } else { return CardProto.Rarity.Legendary; } } bool public canClaim = true; function setCanClaim(bool claim) public onlyOwner { canClaim = claim; } function getComponents( uint16 i, uint8 j, uint rand ) internal returns ( uint random, uint32 rarityRandom, uint16 purityOne, uint16 purityTwo, uint16 protoRandom ) { random = uint(keccak256(abi.encodePacked(i, rand, j))); rarityRandom = uint32(extract(random, 4, 10) % 1000000); purityOne = uint16(extract(random, 2, 4) % 1000); purityTwo = uint16(extract(random, 2, 6) % 1000); protoRandom = uint16(extract(random, 2, 8) % (2**16-1)); return (random, rarityRandom, purityOne, purityTwo, protoRandom); } function withdraw() public onlyOwner { owner.transfer(address(this).balance); } } contract PackFourMultiplier is PresalePackFour { address[] public packs; uint16 public multiplier = 3; FirstPheonix pheonix; PreviousInterface old; uint16 public packLimit = 5; constructor(PreviousInterface _old, address[] _packs, MigrationInterface _core, CappedVault vault, FirstPheonix _pheonix) public PresalePackFour(_core, vault) { packs = _packs; pheonix = _pheonix; old = _old; } function getCardCount() internal view returns (uint) { return old.totalSupply() + old.burnCount(); } function isPriorPack(address test) public view returns(bool) { for (uint i = 0; i < packs.length; i++) { if (packs[i] == test) { return true; } } return false; } event Status(uint before, uint aft); function claimMultiple(address pack, uint purchaseID) public returns (uint16, address) { require(isPriorPack(pack)); uint length = getCardCount(); PresalePackFour(pack).claim(purchaseID); uint lengthAfter = getCardCount(); require(lengthAfter > length); uint16 cardDifference = uint16(lengthAfter - length); require(cardDifference % 5 == 0); uint16 packCount = cardDifference / 5; uint16 extra = packCount * multiplier; address lastCardOwner = old.ownerOf(lengthAfter - 1); Purchase memory p = Purchase({ user: lastCardOwner, count: extra, commit: uint64(block.number), randomness: 0, current: 0 }); uint id = purchases.push(p) - 1; emit PacksPurchased(id, lastCardOwner, extra); // try to give them a first pheonix pheonix.claimPheonix(lastCardOwner); emit Status(length, lengthAfter); if (packCount <= packLimit) { for (uint i = 0; i < cardDifference; i++) { migration.migrate(lengthAfter - 1 - i); } } return (extra, lastCardOwner); } function setPackLimit(uint16 limit) public onlyOwner { packLimit = limit; } } contract RarePackFour is PackFourMultiplier { function basePrice() public returns (uint) { return 12 finney; } constructor(PreviousInterface _old, address[] _packs, MigrationInterface _core, CappedVault vault, FirstPheonix _pheonix) public PackFourMultiplier(_old, _packs, _core, vault, _pheonix) { } function getCardDetails(uint16 packIndex, uint8 cardIndex, uint result) public view returns (uint16 proto, uint16 purity) { uint random; uint32 rarityRandom; uint16 protoRandom; uint16 purityOne; uint16 purityTwo; CardProto.Rarity rarity; (random, rarityRandom, purityOne, purityTwo, protoRandom) = getComponents(packIndex, cardIndex, result); if (cardIndex == 4) { rarity = _getRarePlusRarity(rarityRandom); } else { rarity = _getCommonPlusRarity(rarityRandom); } purity = _getPurity(purityOne, purityTwo); proto = migration.getRandomCard(rarity, protoRandom); return (proto, purity); } }
0.4.18