Transaction Hash:
Block:
6471856 at Oct-07-2018 06:22:25 PM +UTC
Transaction Fee:
0.001102205 ETH
$2.34
Gas Used:
84,785 Gas / 13 Gwei
Emitted Events:
| 36 |
RarePackFour.PacksPurchased( id=16306, user=[Sender] 0xc6a7463a7ee700d035aff7bfd1ee198d680a4164, count=100 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x005e288D...8688C7223
Miner
| (xnpool) | 474.832305757390384626 Eth | 474.833407962390384626 Eth | 0.001102205 | |
| 0x0777F76D...fCd286919 | (GU: Rare Pack Four) | ||||
| 0x91B9d283...Bd04fd689 | (GU: Capped Vault) | 3,607.949870999999999 Eth | 3,609.149870999999999 Eth | 1.2 | |
| 0xC6A7463A...D680A4164 |
2.622261812288725168 Eth
Nonce: 60
|
1.421159607288725168 Eth
Nonce: 61
| 1.201102205 |
Execution Trace
purchase[CardPackFour (ln:531)]
File 1 of 2: RarePackFour
File 2 of 2: CappedVault
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);
}
}File 2 of 2: CappedVault
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;
}
}