Transaction Hash:
Block:
5441025 at Apr-14-2018 08:33:10 PM +UTC
Transaction Fee:
0.0001886528 ETH
$0.49
Gas Used:
117,908 Gas / 1.6 Gwei
Emitted Events:
80 |
DungeonRunCore.LogAttack( timestamp=1523737990, player=[Sender] 0x51852d9eafd93dd7f973607cc96e32ef122b0cb6, heroId=743, monsterLevel=5, damageByHero=19, damageByMonster=0, isMonsterDefeated=True, rewards=20000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x0E6a831c...D29bE1B71 | 0.252279162406921387 Eth | 0.232279162406921387 Eth | 0.02 | ||
0x51852d9E...f122B0CB6 |
0.08157724196577595 Eth
Nonce: 423
|
0.10138858916577595 Eth
Nonce: 424
| 0.0198113472 | ||
0xb2930B35...e543a0347
Miner
| (MiningPoolHub: Old Address) | 31,345.431377265230547516 Eth | 31,345.431565918030547516 Eth | 0.0001886528 |
Execution Trace
DungeonRunCore.attack( _heroId=743 )
EDCoreVersion1.getHeroDetails( _id=743 ) => ( creationTime=1521058332, cooldownStartTime=1523307415, cooldownIndex=7, genes=571568317159269739227999781191649967890866351706782881611075168902752077, owner=0x51852d9EAFd93dD7F973607Cc96E32ef122B0CB6, isReady=True, cooldownRemainingTime=0 )
-
HeroTokenAuction.CALL( )
-
HeroTokenAuction.heroes( 743 ) => ( creationTime=1521058332, cooldownStartTime=1523307415, cooldownIndex=7, genes=571568317159269739227999781191649967890866351706782881611075168902752077 )
-
HeroTokenAuction.ownerOf( _tokenId=743 ) => ( 0x51852d9EAFd93dD7F973607Cc96E32ef122B0CB6 )
-
HeroTokenAuction.heroes( 743 ) => ( creationTime=1521058332, cooldownStartTime=1523307415, cooldownIndex=7, genes=571568317159269739227999781191649967890866351706782881611075168902752077 )
-
-
0x51852d9eafd93dd7f973607cc96e32ef122b0cb6.CALL( )
-
EDCoreVersion1.getHeroPower( _genes=571568317159269739227999781191649967890866351706782881611075168902752077, _dungeonDifficulty=3 ) => ( totalPower=297, equipmentPower=216, statsPower=81, isSuper=False, superRank=0, superBoost=0 )
- ETH 0.02
0x51852d9eafd93dd7f973607cc96e32ef122b0cb6.CALL( )
attack[DungeonRunCore (ln:410)]
getHeroDetails[DungeonRunCore (ln:413)]
Monster[DungeonRunCore (ln:437)]
transfer[DungeonRunCore (ln:446)]
transfer[DungeonRunCore (ln:472)]
_attack[DungeonRunCore (ln:477)]
getHeroPower[DungeonRunCore (ln:531)]
_getRandomNumber[DungeonRunCore (ln:539)]
blockhash[DungeonRunCore (ln:626)]
Monster[DungeonRunCore (ln:548)]
transfer[DungeonRunCore (ln:568)]
_getRandomNumber[DungeonRunCore (ln:582)]
blockhash[DungeonRunCore (ln:626)]
Monster[DungeonRunCore (ln:612)]
LogAttack[DungeonRunCore (ln:619)]
File 1 of 3: DungeonRunCore
File 2 of 3: EDCoreVersion1
File 3 of 3: HeroTokenAuction
pragma solidity 0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @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) onlyOwner public { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ 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; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } /** * @title Destructible * @dev Base contract that can be destroyed by owner. All funds in contract will be sent to the owner. */ contract Destructible is Ownable { function Destructible() public payable { } /** * @dev Transfers the current balance to the owner and terminates the contract. */ function destroy() onlyOwner public { selfdestruct(owner); } function destroyAndSend(address _recipient) onlyOwner public { selfdestruct(_recipient); } } /// @dev Interface to the Core Contract of Ether Dungeon. contract EDCoreInterface { /// @dev The external function to get all the game settings in one call. function getGameSettings() external view returns ( uint _recruitHeroFee, uint _transportationFeeMultiplier, uint _noviceDungeonId, uint _consolationRewardsRequiredFaith, uint _challengeFeeMultiplier, uint _dungeonPreparationTime, uint _trainingFeeMultiplier, uint _equipmentTrainingFeeMultiplier, uint _preparationPeriodTrainingFeeMultiplier, uint _preparationPeriodEquipmentTrainingFeeMultiplier ); /** * @dev The external function to get all the relevant information about a specific player by its address. * @param _address The address of the player. */ function getPlayerDetails(address _address) external view returns ( uint dungeonId, uint payment, uint dungeonCount, uint heroCount, uint faith, bool firstHeroRecruited ); /** * @dev The external function to get all the relevant information about a specific dungeon by its ID. * @param _id The ID of the dungeon. */ function getDungeonDetails(uint _id) external view returns ( uint creationTime, uint status, uint difficulty, uint capacity, address owner, bool isReady, uint playerCount ); /** * @dev Split floor related details out of getDungeonDetails, just to avoid Stack Too Deep error. * @param _id The ID of the dungeon. */ function getDungeonFloorDetails(uint _id) external view returns ( uint floorNumber, uint floorCreationTime, uint rewards, uint seedGenes, uint floorGenes ); /** * @dev The external function to get all the relevant information about a specific hero by its ID. * @param _id The ID of the hero. */ function getHeroDetails(uint _id) external view returns ( uint creationTime, uint cooldownStartTime, uint cooldownIndex, uint genes, address owner, bool isReady, uint cooldownRemainingTime ); /// @dev Get the attributes (equipments + stats) of a hero from its gene. function getHeroAttributes(uint _genes) public pure returns (uint[]); /// @dev Calculate the power of a hero from its gene, it calculates the equipment power, stats power, and super hero boost. function getHeroPower(uint _genes, uint _dungeonDifficulty) public pure returns ( uint totalPower, uint equipmentPower, uint statsPower, bool isSuper, uint superRank, uint superBoost ); /// @dev Calculate the power of a dungeon floor. function getDungeonPower(uint _genes) public pure returns (uint); /** * @dev Calculate the sum of top 5 heroes power a player owns. * The gas usage increased with the number of heroes a player owned, roughly 500 x hero count. * This is used in transport function only to calculate the required tranport fee. */ function calculateTop5HeroesPower(address _address, uint _dungeonId) public view returns (uint); } /** * @title Core Contract of "Dungeon Run" event game of the ED (Ether Dungeon) Platform. * @dev Dungeon Run is a single-player game mode added to the Ether Dungeon platform. * The objective of Dungeon Run is to defeat as many monsters as possible. */ contract DungeonRunCore is Pausable, Destructible { /*================================= = STRUCTS = =================================*/ struct Monster { uint64 creationTime; uint8 level; uint16 initialHealth; uint16 health; } /*================================= = CONTRACTS = =================================*/ /// @dev The address of the EtherDungeonCore contract. EDCoreInterface public edCoreContract = EDCoreInterface(0xf7eD56c1AC4d038e367a987258b86FC883b960a1); /*================================= = CONSTANTS = =================================*/ /// @dev By defeating the checkPointLevel, half of the entranceFee is refunded. uint8 public constant checkpointLevel = 5; /// @dev By defeating the breakevenLevel, another half of the entranceFee is refunded. uint8 public constant breakevenLevel = 10; /// @dev By defeating the jackpotLevel, the player win the entire jackpot. uint8 public constant jackpotLevel = 12; /// @dev Dungeon difficulty to be used when calculating super hero power boost, 3 is 64 power boost. uint public constant dungeonDifficulty = 3; /// @dev The health of a monster is level * monsterHealth; uint16 public monsterHealth = 10; /// @dev When a monster flees, the hero health is reduced by monster level + monsterStrength. uint public monsterStrength = 4; /// @dev After a certain period of time, the monster will attack the hero and flee. uint64 public monsterFleeTime = 8 minutes; /*================================= = SETTINGS = =================================*/ /// @dev To start a run, a player need to pay an entrance fee. uint public entranceFee = 0.04 ether; /// @dev Fee required to reset a run for a given hero, the fee will go to the jackpot. uint public reviveFee = 0.02 ether; /// @dev 0.1 ether is provided as the initial jackpot. uint public jackpot = 0.1 ether; /** * @dev The dungeon run entrance fee will first be deposited to a pool first, when the hero is * defeated by a monster, then the fee will be added to the jackpot. */ uint public entranceFeePool; /// @dev Private seed for the PRNG used for calculating damage amount. uint _seed; /*================================= = STATE VARIABLES = =================================*/ /// @dev A mapping from hero ID to the current run monster, a 0 value indicates no current run. mapping(uint => Monster) public heroIdToMonster; /// @dev A mapping from hero ID to its current health. mapping(uint => uint) public heroIdToHealth; /// @dev A mapping from hero ID to the refunded fee. mapping(uint => uint) public heroIdToRefundedFee; /*============================== = EVENTS = ==============================*/ /// @dev The LogAttack event is fired whenever a hero attack a monster. event LogAttack(uint timestamp, address indexed player, uint indexed heroId, uint indexed monsterLevel, uint damageByHero, uint damageByMonster, bool isMonsterDefeated, uint rewards); function DungeonRunAlpha() public payable {} /*======================================= = PUBLIC/EXTERNAL FUNCTIONS = =======================================*/ /// @dev The external function to get all the game settings in one call. function getGameSettings() external view returns ( uint _checkpointLevel, uint _breakevenLevel, uint _jackpotLevel, uint _dungeonDifficulty, uint _monsterHealth, uint _monsterStrength, uint _monsterFleeTime, uint _entranceFee, uint _reviveFee ) { _checkpointLevel = checkpointLevel; _breakevenLevel = breakevenLevel; _jackpotLevel = jackpotLevel; _dungeonDifficulty = dungeonDifficulty; _monsterHealth = monsterHealth; _monsterStrength = monsterStrength; _monsterFleeTime = monsterFleeTime; _entranceFee = entranceFee; _reviveFee = reviveFee; } /// @dev The external function to get the dungeon run details in one call. function getRunDetails(uint _heroId) external view returns ( uint _heroPower, uint _heroStrength, uint _heroInitialHealth, uint _heroHealth, uint _monsterCreationTime, uint _monsterLevel, uint _monsterInitialHealth, uint _monsterHealth, uint _gameState // 0: NotStarted | 1: NewMonster | 2: Active | 3: RunEnded ) { uint genes; address owner; (,,, genes, owner,,) = edCoreContract.getHeroDetails(_heroId); (_heroPower,,,,) = edCoreContract.getHeroPower(genes, dungeonDifficulty); _heroStrength = (genes / (32 ** 8)) % 32 + 1; _heroInitialHealth = (genes / (32 ** 12)) % 32 + 1; _heroHealth = heroIdToHealth[_heroId]; Monster memory monster = heroIdToMonster[_heroId]; _monsterCreationTime = monster.creationTime; // Dungeon run is ended if either hero is defeated (health exhausted), // or hero failed to damage a monster before it flee. bool _dungeonRunEnded = monster.level > 0 && ( _heroHealth == 0 || now > _monsterCreationTime + monsterFleeTime * 2 || (monster.health == monster.initialHealth && now > monster.creationTime + monsterFleeTime) ); // Calculate hero and monster stats based on different game state. if (monster.level == 0) { // Dungeon run not started yet. _heroHealth = _heroInitialHealth; _monsterLevel = 1; _monsterInitialHealth = monsterHealth; _monsterHealth = _monsterInitialHealth; _gameState = 0; } else if (_dungeonRunEnded) { // Dungeon run ended. _monsterLevel = monster.level; _monsterInitialHealth = monster.initialHealth; _monsterHealth = monster.health; _gameState = 3; } else if (now > _monsterCreationTime + monsterFleeTime) { // Previous monster just fled, new monster awaiting. if (monster.level + monsterStrength > _heroHealth) { _heroHealth = 0; _monsterLevel = monster.level; _monsterInitialHealth = monster.initialHealth; _monsterHealth = monster.health; _gameState = 2; } else { _heroHealth -= monster.level + monsterStrength; _monsterCreationTime += monsterFleeTime; _monsterLevel = monster.level + 1; _monsterInitialHealth = _monsterLevel * monsterHealth; _monsterHealth = _monsterInitialHealth; _gameState = 1; } } else { // Active monster. _monsterLevel = monster.level; _monsterInitialHealth = monster.initialHealth; _monsterHealth = monster.health; _gameState = 2; } } /** * @dev To start a dungeon run, player need to call the attack function with an entranceFee. * Future attcks required no fee, player just need to send a free transaction * to the contract, before the monster flee. The lower the gas price, the larger the damage. * This function is prevented from being called by a contract, using the onlyHumanAddress modifier. * Note that each hero can only perform one dungeon run. */ function attack(uint _heroId) whenNotPaused onlyHumanAddress external payable { uint genes; address owner; (,,, genes, owner,,) = edCoreContract.getHeroDetails(_heroId); // Throws if the hero is not owned by the player. require(msg.sender == owner); // Get the health and strength of the hero. uint heroInitialHealth = (genes / (32 ** 12)) % 32 + 1; uint heroStrength = (genes / (32 ** 8)) % 32 + 1; // Get the current monster and hero current health. Monster memory monster = heroIdToMonster[_heroId]; uint currentLevel = monster.level; uint heroCurrentHealth = heroIdToHealth[_heroId]; // A flag determine whether the dungeon run has ended. bool dungeonRunEnded; // To start a run, the player need to pay an entrance fee. if (currentLevel == 0) { // Throws if not enough fee, and any exceeding fee will be transferred back to the player. require(msg.value >= entranceFee); entranceFeePool += entranceFee; // Create level 1 monster, initial health is 1 * monsterHealth. heroIdToMonster[_heroId] = Monster(uint64(now), 1, monsterHealth, monsterHealth); monster = heroIdToMonster[_heroId]; // Set the hero initial health to storage. heroIdToHealth[_heroId] = heroInitialHealth; heroCurrentHealth = heroInitialHealth; // Refund exceeding fee. if (msg.value > entranceFee) { msg.sender.transfer(msg.value - entranceFee); } } else { // If the hero health is 0, the dungeon run has ended. require(heroCurrentHealth > 0); // If a hero failed to damage a monster before it flee, the dungeon run ends, // regardless of the remaining hero health. dungeonRunEnded = now > monster.creationTime + monsterFleeTime * 2 || (monster.health == monster.initialHealth && now > monster.creationTime + monsterFleeTime); if (dungeonRunEnded) { // Add the non-refunded fee to jackpot. uint addToJackpot = entranceFee - heroIdToRefundedFee[_heroId]; if (addToJackpot > 0) { jackpot += addToJackpot; entranceFeePool -= addToJackpot; heroIdToRefundedFee[_heroId] += addToJackpot; } // Sanity check. assert(addToJackpot <= entranceFee); } // Future attack do not require any fee, so refund all ether sent with the transaction. msg.sender.transfer(msg.value); } if (!dungeonRunEnded) { // All pre-conditions passed, call the internal attack function. _attack(_heroId, genes, heroStrength, heroCurrentHealth); } } /** * @dev Reset a dungeon run for a given hero. */ function revive(uint _heroId) whenNotPaused external payable { // Throws if not enough fee, and any exceeding fee will be transferred back to the player. require(msg.value >= reviveFee); // The revive fee will do directly to jackpot. jackpot += reviveFee; // Reset the dungeon run. delete heroIdToHealth[_heroId]; delete heroIdToMonster[_heroId]; delete heroIdToRefundedFee[_heroId]; // Refund exceeding fee. if (msg.value > reviveFee) { msg.sender.transfer(msg.value - reviveFee); } } /*======================================= = SETTER FUNCTIONS = =======================================*/ function setEdCoreContract(address _newEdCoreContract) onlyOwner external { edCoreContract = EDCoreInterface(_newEdCoreContract); } function setEntranceFee(uint _newEntranceFee) onlyOwner external { entranceFee = _newEntranceFee; } function setReviveFee(uint _newReviveFee) onlyOwner external { reviveFee = _newReviveFee; } /*======================================= = INTERNAL/PRIVATE FUNCTIONS = =======================================*/ /// @dev Internal function of attack, assume all parameter checking is done. function _attack(uint _heroId, uint _genes, uint _heroStrength, uint _heroCurrentHealth) internal { Monster storage monster = heroIdToMonster[_heroId]; uint8 currentLevel = monster.level; // Get the hero power. uint heroPower; (heroPower,,,,) = edCoreContract.getHeroPower(_genes, dungeonDifficulty); uint damageByMonster; uint damageByHero; // Calculate the damage by hero first. // The damage formula is (strength + power / (10 * rand)) / gasprice, // where rand is a random integer from 1 to 5. damageByHero = (_heroStrength * 1e9 + heroPower * 1e9 / (10 * (1 + _getRandomNumber(5)))) / (tx.gasprice >= 0.5 * 1e9 ? tx.gasprice : 0.5 * 1e9); bool isMonsterDefeated = damageByHero >= monster.health; if (isMonsterDefeated) { uint rewards; // Monster is defeated, game continues with a new monster. // Create next level monster. uint8 newLevel = currentLevel + 1; heroIdToMonster[_heroId] = Monster(uint64(now), newLevel, newLevel * monsterHealth, newLevel * monsterHealth); monster = heroIdToMonster[_heroId]; // Determine the rewards based on current level. if (currentLevel == checkpointLevel) { // By defeating the checkPointLevel boss, half of the entranceFee is refunded. rewards = entranceFee / 2; heroIdToRefundedFee[_heroId] += rewards; entranceFeePool -= rewards; } else if (currentLevel == breakevenLevel) { // By defeating the breakevenLevel boss, another half of the entranceFee is refunded. rewards = entranceFee / 2; heroIdToRefundedFee[_heroId] += rewards; entranceFeePool -= rewards; } else if (currentLevel == jackpotLevel) { // By defeating the jackpotLevel, the player win the entire jackpot. rewards = jackpot / 2; jackpot -= rewards; } msg.sender.transfer(rewards); } else { // Monster is damanged but not defeated, hurry up! monster.health -= uint8(damageByHero); // Calculate the damage by monster only if it is not defeated. // Determine if the monster has fled due to hero failed to attack within flee period. if (now > monster.creationTime + monsterFleeTime) { // When a monster flees, the monster will attack the hero and flee. // The damage is calculated by monster level + monsterStrength. damageByMonster = currentLevel + monsterStrength; } else { // When a monster attack back the hero, the damage will be less than monster level / 2. if (currentLevel >= 2) { damageByMonster = _getRandomNumber(currentLevel / 2); } } } // Check if hero is defeated. if (damageByMonster >= _heroCurrentHealth) { // Hero is defeated, the dungeon run ends. heroIdToHealth[_heroId] = 0; // Add the non-refunded fee to jackpot. uint addToJackpot = entranceFee - heroIdToRefundedFee[_heroId]; if (addToJackpot > 0) { jackpot += addToJackpot; entranceFeePool -= addToJackpot; heroIdToRefundedFee[_heroId] += addToJackpot; } // Sanity check. assert(addToJackpot <= entranceFee); } else { // Hero is damanged but didn't defeated, game continues with a new monster. if (damageByMonster > 0) { heroIdToHealth[_heroId] -= damageByMonster; } // If monser fled, create next level monster. if (now > monster.creationTime + monsterFleeTime) { currentLevel++; heroIdToMonster[_heroId] = Monster(uint64(monster.creationTime + monsterFleeTime), currentLevel, currentLevel * monsterHealth, currentLevel * monsterHealth); monster = heroIdToMonster[_heroId]; } } // Emit LogAttack event. LogAttack(now, msg.sender, _heroId, currentLevel, damageByHero, damageByMonster, isMonsterDefeated, rewards); } /// @dev Return a pseudo random uint smaller than _upper bounds. function _getRandomNumber(uint _upper) private returns (uint) { _seed = uint(keccak256( _seed, block.blockhash(block.number - 1), block.coinbase, block.difficulty )); return _seed % _upper; } /*============================== = MODIFIERS = ==============================*/ /// @dev Throws if the caller address is a contract. modifier onlyHumanAddress() { address addr = msg.sender; uint size; assembly { size := extcodesize(addr) } require(size == 0); _; } }
File 2 of 3: EDCoreVersion1
pragma solidity ^0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @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) onlyOwner public { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } /** * @title EjectableOwnable * @dev The EjectableOwnable contract provides the function to remove the ownership of the contract. */ contract EjectableOwnable is Ownable { /** * @dev Remove the ownership by setting the owner address to null, * after calling this function, all onlyOwner function will be be able to be called by anyone anymore, * the contract will achieve truly decentralisation. */ function removeOwnership() onlyOwner public { owner = 0x0; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ 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; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title PullPayment * @dev Base contract supporting async send for pull payments. Inherit from this * contract and use asyncSend instead of send. */ contract PullPayment { using SafeMath for uint256; mapping(address => uint256) public payments; uint256 public totalPayments; /** * @dev withdraw accumulated balance, called by payee. */ function withdrawPayments() public { address payee = msg.sender; uint256 payment = payments[payee]; require(payment != 0); require(this.balance >= payment); totalPayments = totalPayments.sub(payment); payments[payee] = 0; assert(payee.send(payment)); } /** * @dev Called by the payer to store the sent amount as credit to be pulled. * @param dest The destination address of the funds. * @param amount The amount to transfer. */ function asyncSend(address dest, uint256 amount) internal { payments[dest] = payments[dest].add(amount); totalPayments = totalPayments.add(amount); } } /** * @title Destructible * @dev Base contract that can be destroyed by owner. All funds in contract will be sent to the owner. */ contract Destructible is Ownable { function Destructible() public payable { } /** * @dev Transfers the current balance to the owner and terminates the contract. */ function destroy() onlyOwner public { selfdestruct(owner); } function destroyAndSend(address _recipient) onlyOwner public { selfdestruct(_recipient); } } contract EDStructs { /** * @dev The main Dungeon struct. Every dungeon in the game is represented by this structure. * A dungeon is consists of an unlimited number of floors for your heroes to challenge, * the power level of a dungeon is encoded in the floorGenes. Some dungeons are in fact more "challenging" than others, * the secret formula for that is left for user to find out. * * Each dungeon also has a "training area", heroes can perform trainings and upgrade their stat, * and some dungeons are more effective in the training, which is also a secret formula! * * When player challenge or do training in a dungeon, the fee will be collected as the dungeon rewards, * which will be rewarded to the player who successfully challenged the current floor. * * Each dungeon fits in fits into three 256-bit words. */ struct Dungeon { // Each dungeon has an ID which is the index in the storage array. // The timestamp of the block when this dungeon is created. uint32 creationTime; // The status of the dungeon, each dungeon can have 5 status, namely: // 0: Active | 1: Transport Only | 2: Challenge Only | 3: Train Only | 4: InActive uint8 status; // The dungeon's difficulty, the higher the difficulty, // normally, the "rarer" the seedGenes, the higher the diffculty, // and the higher the contribution fee it is to challenge, train, and transport to the dungeon, // the formula for the contribution fee is in DungeonChallenge and DungeonTraining contracts. // A dungeon's difficulty never change. uint8 difficulty; // The dungeon's capacity, maximum number of players allowed to stay on this dungeon. // The capacity of the newbie dungeon (Holyland) is set at 0 (which is infinity). // Using 16-bit unsigned integers can have a maximum of 65535 in capacity. // A dungeon's capacity never change. uint16 capacity; // The current floor number, a dungeon is consists of an umlimited number of floors, // when there is heroes successfully challenged a floor, the next floor will be // automatically generated. Using 32-bit unsigned integer can have a maximum of 4 billion floors. uint32 floorNumber; // The timestamp of the block when the current floor is generated. uint32 floorCreationTime; // Current accumulated rewards, successful challenger will get a large proportion of it. uint128 rewards; // The seed genes of the dungeon, it is used as the base gene for first floor, // some dungeons are rarer and some are more common, the exact details are, // of course, top secret of the game! // A dungeon's seedGenes never change. uint seedGenes; // The genes for current floor, it encodes the difficulty level of the current floor. // We considered whether to store the entire array of genes for all floors, but // in order to save some precious gas we're willing to sacrifice some functionalities with that. uint floorGenes; } /** * @dev The main Hero struct. Every hero in the game is represented by this structure. */ struct Hero { // Each hero has an ID which is the index in the storage array. // The timestamp of the block when this dungeon is created. uint64 creationTime; // The timestamp of the block where a challenge is performed, used to calculate when a hero is allowed to engage in another challenge. uint64 cooldownStartTime; // Every time a hero challenge a dungeon, its cooldown index will be incremented by one. uint32 cooldownIndex; // The seed of the hero, the gene encodes the power level of the hero. // This is another top secret of the game! Hero's gene can be upgraded via // training in a dungeon. uint genes; } } /** * @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens. */ contract ERC721 { // Events event Transfer(address indexed from, address indexed to, uint indexed tokenId); event Approval(address indexed owner, address indexed approved, uint indexed tokenId); // ERC20 compatible functions. // function name() public constant returns (string); // function symbol() public constant returns (string); function totalSupply() public view returns (uint); function balanceOf(address _owner) public view returns (uint); // Functions that define ownership. function ownerOf(uint _tokenId) external view returns (address); function transfer(address _to, uint _tokenId) external; // Approval related functions, mainly used in auction contracts. function approve(address _to, uint _tokenId) external; function approvedFor(uint _tokenId) external view returns (address); function transferFrom(address _from, address _to, uint _tokenId) external; /** * @dev Each non-fungible token owner can own more than one token at one time. * Because each token is referenced by its unique ID, however, * it can get difficult to keep track of the individual tokens that a user may own. * To do this, the contract keeps a record of the IDs of each token that each user owns. */ mapping(address => uint[]) public ownerTokens; } contract DungeonTokenInterface is ERC721, EDStructs { /** * @notice Limits the number of dungeons the contract owner can ever create. */ uint public constant DUNGEON_CREATION_LIMIT = 1024; /** * @dev Name of token. */ string public constant name = "Dungeon"; /** * @dev Symbol of token. */ string public constant symbol = "DUNG"; /** * @dev An array containing the Dungeon struct, which contains all the dungeons in existance. * The ID for each dungeon is the index of this array. */ Dungeon[] public dungeons; /** * @dev The external function that creates a new dungeon and stores it, only contract owners * can create new token, and will be restricted by the DUNGEON_CREATION_LIMIT. * Will generate a Mint event, a NewDungeonFloor event, and a Transfer event. */ function createDungeon(uint _difficulty, uint _capacity, uint _floorNumber, uint _seedGenes, uint _floorGenes, address _owner) external returns (uint); /** * @dev The external function to set dungeon status by its ID, * refer to DungeonStructs for more information about dungeon status. * Only contract owners can alter dungeon state. */ function setDungeonStatus(uint _id, uint _newStatus) external; /** * @dev The external function to add additional dungeon rewards by its ID, * only contract owners can alter dungeon state. */ function addDungeonRewards(uint _id, uint _additinalRewards) external; /** * @dev The external function to add another dungeon floor by its ID, * only contract owners can alter dungeon state. */ function addDungeonNewFloor(uint _id, uint _newRewards, uint _newFloorGenes) external; } contract HeroTokenInterface is ERC721, EDStructs { /** * @dev Name of token. */ string public constant name = "Hero"; /** * @dev Symbol of token. */ string public constant symbol = "HERO"; /** * @dev An array containing the Hero struct, which contains all the heroes in existance. * The ID for each hero is the index of this array. */ Hero[] public heroes; /** * @dev An external function that creates a new hero and stores it, * only contract owners can create new token. * method doesn't do any checking and should only be called when the * input data is known to be valid. * @param _genes The gene of the new hero. * @param _owner The inital owner of this hero. * @return The hero ID of the new hero. */ function createHero(uint _genes, address _owner) external returns (uint); /** * @dev The external function to set the hero genes by its ID, * only contract owners can alter hero state. */ function setHeroGenes(uint _id, uint _newGenes) external; /** * @dev Set the cooldownStartTime for the given hero. Also increments the cooldownIndex. */ function triggerCooldown(uint _id) external; } /** * SECRET */ contract ChallengeFormulaInterface { /** * @dev given genes of current floor and dungeon seed, return a genetic combination - may have a random factor. * @param _floorGenes Genes of floor. * @param _seedGenes Seed genes of dungeon. * @return The resulting genes. */ function calculateResult(uint _floorGenes, uint _seedGenes) external returns (uint); } /** * SECRET */ contract TrainingFormulaInterface { /** * @dev given genes of hero and current floor, return a genetic combination - may have a random factor. * @param _heroGenes Genes of hero. * @param _floorGenes Genes of current floor. * @param _equipmentId Equipment index to train for, 0 is train all attributes. * @return The resulting genes. */ function calculateResult(uint _heroGenes, uint _floorGenes, uint _equipmentId) external returns (uint); } /** * @title EDBase * @dev Base contract for Ether Dungeon. It implements all necessary sub-classes, * holds all the contracts, constants, game settings, storage variables, events, and some commonly used functions. */ contract EDBase is EjectableOwnable, Pausable, PullPayment, EDStructs { /* ======== CONTRACTS ======== */ /// @dev The address of the ERC721 token contract managing all Dungeon tokens. DungeonTokenInterface public dungeonTokenContract; /// @dev The address of the ERC721 token contract managing all Hero tokens. HeroTokenInterface public heroTokenContract; /// @dev The address of the ChallengeFormula contract that handles the floor generation mechanics after challenge success. ChallengeFormulaInterface challengeFormulaContract; /// @dev The address of the TrainingFormula contract that handles the hero training mechanics. TrainingFormulaInterface trainingFormulaContract; /* ======== CONSTANTS / GAME SETTINGS (all variables are set to constant in order to save gas) ======== */ // 1 finney = 0.001 ether // 1 szabo = 0.001 finney /// @dev Super Hero (full set of same-themed Rare Equipments, there are 8 in total) uint public constant SUPER_HERO_MULTIPLIER = 32; /// @dev Ultra Hero (full set of same-themed Epic Equipments, there are 4 in total) uint public constant ULTRA_HERO_MULTIPLIER = 64; /** * @dev Mega Hero (full set of same-themed Legendary Equipments, there are 2 in total) * There are also 2 Ultimate Hero/Demon, Pangu and Chaos, which will use the MEGA_HERO_MULTIPLIER. */ uint public constant MEGA_HERO_MULTIPLIER = 96; /// @dev The fee for recruiting a hero. The payment is accumulated to the rewards of the origin dungeon. uint public recruitHeroFee = 2 finney; /** * @dev The actual fee contribution required to call transport() is calculated by this feeMultiplier, * times the dungeon difficulty of destination dungeon. The payment is accumulated to the rewards of the origin dungeon, * and a large proportion will be claimed by whoever successfully challenged the floor. */ uint public transportationFeeMultiplier = 250 szabo; ///@dev All hero starts in the novice dungeon, also hero can only be recruited in novice dungoen. uint public noviceDungeonId = 31; // < dungeon ID 31 = Abyss /// @dev Amount of faith required to claim a portion of the grandConsolationRewards. uint public consolationRewardsRequiredFaith = 100; /// @dev The percentage for which when a player can get from the grandConsolationRewards when meeting the faith requirement. uint public consolationRewardsClaimPercent = 50; /** * @dev The actual fee contribution required to call challenge() is calculated by this feeMultiplier, * times the dungeon difficulty. The payment is accumulated to the dungeon rewards, * and a large proportion will be claimed by whoever successfully challenged the floor. */ uint public constant challengeFeeMultiplier = 1 finney; /** * @dev The percentage for which successful challenger be rewarded of the dungeons' accumulated rewards. * The remaining rewards subtract dungeon master rewards and consolation rewards will be used as the base rewards for new floor. */ uint public constant challengeRewardsPercent = 45; /** * @dev The developer fee for dungeon master (owner of the dungeon token). * Note that when Ether Dungeon becomes truly decentralised, contract ownership will be ejected, * and the master rewards will be rewarded to the dungeon owner (Dungeon Masters). */ uint public constant masterRewardsPercent = 8; /// @dev The percentage for which the challenge rewards is added to the grandConsolationRewards. uint public consolationRewardsPercent = 2; /// @dev The preparation time period where a new dungeon is created, before it can be challenged. uint public dungeonPreparationTime = 60 minutes; /// @dev The challenge rewards percentage used right after the preparation period. uint public constant rushTimeChallengeRewardsPercent = 22; /// @dev The number of floor in which the rushTimeChallengeRewardsPercent be applied. uint public constant rushTimeFloorCount = 30; /** * @dev The actual fee contribution required to call trainX() is calculated by this feeMultiplier, * times the dungeon difficulty, times training times. The payment is accumulated to the dungeon rewards, * and a large proportion will be claimed by whoever successfully challenged the floor. */ uint public trainingFeeMultiplier = 2 finney; /** * @dev The actual fee contribution required to call trainEquipment() is calculated by this feeMultiplier, * times the dungeon difficulty. The payment is accumulated to the dungeon rewards. * (No preparation period discount on equipment training.) */ uint public equipmentTrainingFeeMultiplier = 8 finney; /// @dev The discounted training fee multiplier to be used during preparation period. uint public constant preparationPeriodTrainingFeeMultiplier = 1600 szabo; /// @dev The discounted equipment training fee multiplier to be used during preparation period. uint public constant preparationPeriodEquipmentTrainingFeeMultiplier = 6400 szabo; /* ======== STATE VARIABLES ======== */ /** * @dev After each successful training, do not update Hero immediately to avoid exploit. * The hero power will be auto updated during next challenge/training for any player. * Or calling the setTempHeroPower() public function directly. */ mapping(address => uint) playerToLastActionBlockNumber; uint tempSuccessTrainingHeroId; uint tempSuccessTrainingNewHeroGenes = 1; // value 1 is used as no pending update /// @dev The total accumulated consolidation jackpot / rewards amount. uint public grandConsolationRewards = 168203010964693559; // < migrated from previous contract /// @dev A mapping from token IDs to the address that owns them, the value can get by getPlayerDetails. mapping(address => uint) playerToDungeonID; /// @dev A mapping from player address to the player's faith value, the value can get by getPlayerDetails. mapping(address => uint) playerToFaith; /** * @dev A mapping from owner address to a boolean flag of whether the player recruited the first hero. * Note that transferring a hero from other address do not count, the value can get by getPlayerDetails. */ mapping(address => bool) playerToFirstHeroRecruited; /// @dev A mapping from owner address to count of tokens that address owns, the value can get by getDungeonDetails. mapping(uint => uint) dungeonIdToPlayerCount; /* ======== EVENTS ======== */ /// @dev The PlayerTransported event is fired when user transported to another dungeon. event PlayerTransported(uint timestamp, address indexed playerAddress, uint indexed originDungeonId, uint indexed destinationDungeonId); /// @dev The DungeonChallenged event is fired when user finished a dungeon challenge. event DungeonChallenged(uint timestamp, address indexed playerAddress, uint indexed dungeonId, uint indexed heroId, uint heroGenes, uint floorNumber, uint floorGenes, bool success, uint newFloorGenes, uint successRewards, uint masterRewards); /// @dev The DungeonChallenged event is fired when user finished a dungeon challenge. event ConsolationRewardsClaimed(uint timestamp, address indexed playerAddress, uint consolationRewards); /// @dev The HeroTrained event is fired when user finished a training. event HeroTrained(uint timestamp, address indexed playerAddress, uint indexed dungeonId, uint indexed heroId, uint heroGenes, uint floorNumber, uint floorGenes, bool success, uint newHeroGenes); /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev Get the attributes (equipments + stats) of a hero from its gene. */ function getHeroAttributes(uint _genes) public pure returns (uint[]) { uint[] memory attributes = new uint[](12); for (uint i = 0; i < 12; i++) { attributes[11 - i] = _genes % 32; _genes /= 32 ** 4; } return attributes; } /** * @dev Calculate the power of a hero from its gene, * it calculates the equipment power, stats power, and super hero boost. */ function getHeroPower(uint _genes, uint _dungeonDifficulty) public pure returns ( uint totalPower, uint equipmentPower, uint statsPower, bool isSuper, uint superRank, uint superBoost ) { // Individual power of each equipment. // DUPLICATE CODE with _getDungeonPower: Constant array variable is not yet implemented, // so need to put it here in order to save gas. uint16[32] memory EQUIPMENT_POWERS = [ 1, 2, 4, 5, 16, 17, 32, 33, // [Holy] Normal Equipments 8, 16, 16, 32, 32, 48, 64, 96, // [Myth] Normal Equipments 4, 16, 32, 64, // [Holy] Rare Equipments 32, 48, 80, 128, // [Myth] Rare Equipments 32, 96, // [Holy] Epic Equipments 80, 192, // [Myth] Epic Equipments 192, // [Holy] Legendary Equipments 288, // [Myth] Legendary Equipments // Pangu / Chaos Legendary Equipments are reserved for far future use. // Their existence is still a mystery. 384, // [Pangu] Legendary Equipments 512 // [Chaos] Legendary Equipments ]; uint[] memory attributes = getHeroAttributes(_genes); // Calculate total equipment power. superRank = attributes[0]; for (uint i = 0; i < 8; i++) { uint equipment = attributes[i]; equipmentPower += EQUIPMENT_POWERS[equipment]; // If any equipment is of difference index, set superRank to 0. if (superRank != equipment) { superRank = 0; } } // Calculate total stats power. for (uint j = 8; j < 12; j++) { // Stat power is gene number + 1. statsPower += attributes[j] + 1; } // Calculate Super/Ultra/Mega Power Boost. isSuper = superRank >= 16; if (superRank >= 28) { // Mega Hero superBoost = (_dungeonDifficulty - 1) * MEGA_HERO_MULTIPLIER; } else if (superRank >= 24) { // Ultra Hero superBoost = (_dungeonDifficulty - 1) * ULTRA_HERO_MULTIPLIER; } else if (superRank >= 16) { // Super Hero superBoost = (_dungeonDifficulty - 1) * SUPER_HERO_MULTIPLIER; } totalPower = statsPower + equipmentPower + superBoost; } /** * @dev Calculate the power of a dungeon floor. */ function getDungeonPower(uint _genes) public pure returns (uint) { // Individual power of each equipment. // DUPLICATE CODE with getHeroPower uint16[32] memory EQUIPMENT_POWERS = [ 1, 2, 4, 5, 16, 17, 32, 33, // [Holy] Normal Equipments 8, 16, 16, 32, 32, 48, 64, 96, // [Myth] Normal Equipments 4, 16, 32, 64, // [Holy] Rare Equipments 32, 48, 80, 128, // [Myth] Rare Equipments 32, 96, // [Holy] Epic Equipments 80, 192, // [Myth] Epic Equipments 192, // [Holy] Legendary Equipments 288, // [Myth] Legendary Equipments // Pangu / Chaos Legendary Equipments are reserved for far future use. // Their existence is still a mystery. 384, // [Pangu] Legendary Equipments 512 // [Chaos] Legendary Equipments ]; // Calculate total dungeon power. uint dungeonPower; for (uint j = 0; j < 12; j++) { dungeonPower += EQUIPMENT_POWERS[_genes % 32]; _genes /= 32 ** 4; } return dungeonPower; } /** * @dev Calculate the sum of top 5 heroes power a player owns. * The gas usage increased with the number of heroes a player owned, roughly 500 x hero count. * This is used in transport function only to calculate the required tranport fee. */ function calculateTop5HeroesPower(address _address, uint _dungeonId) public view returns (uint) { uint heroCount = heroTokenContract.balanceOf(_address); if (heroCount == 0) { return 0; } // Get the dungeon difficulty to factor in the super power boost when calculating hero power. uint difficulty; (,, difficulty,,,,,,) = dungeonTokenContract.dungeons(_dungeonId); // Compute all hero powers for further calculation. uint[] memory heroPowers = new uint[](heroCount); for (uint i = 0; i < heroCount; i++) { uint heroId = heroTokenContract.ownerTokens(_address, i); uint genes; (,,, genes) = heroTokenContract.heroes(heroId); (heroPowers[i],,,,,) = getHeroPower(genes, difficulty); } // Calculate the top 5 heroes power. uint result; uint curMax; uint curMaxIndex; for (uint j; j < 5; j++) { for (uint k = 0; k < heroPowers.length; k++) { if (heroPowers[k] > curMax) { curMax = heroPowers[k]; curMaxIndex = k; } } result += curMax; heroPowers[curMaxIndex] = 0; curMax = 0; curMaxIndex = 0; } return result; } /// @dev Set the previously temp stored upgraded hero genes. Can only be called by contract owner. function setTempHeroPower() onlyOwner public { _setTempHeroPower(); } /* ======== SETTER FUNCTIONS ======== */ /// @dev Set the address of the dungeon token contract. function setDungeonTokenContract(address _newDungeonTokenContract) onlyOwner external { dungeonTokenContract = DungeonTokenInterface(_newDungeonTokenContract); } /// @dev Set the address of the hero token contract. function setHeroTokenContract(address _newHeroTokenContract) onlyOwner external { heroTokenContract = HeroTokenInterface(_newHeroTokenContract); } /// @dev Set the address of the secret dungeon challenge formula contract. function setChallengeFormulaContract(address _newChallengeFormulaAddress) onlyOwner external { challengeFormulaContract = ChallengeFormulaInterface(_newChallengeFormulaAddress); } /// @dev Set the address of the secret hero training formula contract. function setTrainingFormulaContract(address _newTrainingFormulaAddress) onlyOwner external { trainingFormulaContract = TrainingFormulaInterface(_newTrainingFormulaAddress); } /// @dev Updates the fee for calling recruitHero(). function setRecruitHeroFee(uint _newRecruitHeroFee) onlyOwner external { recruitHeroFee = _newRecruitHeroFee; } /// @dev Updates the fee contribution multiplier required for calling transport(). function setTransportationFeeMultiplier(uint _newTransportationFeeMultiplier) onlyOwner external { transportationFeeMultiplier = _newTransportationFeeMultiplier; } /// @dev Updates the novice dungeon ID. function setNoviceDungeonId(uint _newNoviceDungeonId) onlyOwner external { noviceDungeonId = _newNoviceDungeonId; } /// @dev Updates the required amount of faith to get a portion of the consolation rewards. function setConsolationRewardsRequiredFaith(uint _newConsolationRewardsRequiredFaith) onlyOwner external { consolationRewardsRequiredFaith = _newConsolationRewardsRequiredFaith; } /// @dev Updates the percentage portion of consolation rewards a player get when meeting the faith requirement. function setConsolationRewardsClaimPercent(uint _newConsolationRewardsClaimPercent) onlyOwner external { consolationRewardsClaimPercent = _newConsolationRewardsClaimPercent; } /// @dev Updates the consolation rewards percentage. function setConsolationRewardsPercent(uint _newConsolationRewardsPercent) onlyOwner external { consolationRewardsPercent = _newConsolationRewardsPercent; } /// @dev Updates the challenge cooldown time. function setDungeonPreparationTime(uint _newDungeonPreparationTime) onlyOwner external { dungeonPreparationTime = _newDungeonPreparationTime; } /// @dev Updates the fee contribution multiplier required for calling trainX(). function setTrainingFeeMultiplier(uint _newTrainingFeeMultiplier) onlyOwner external { trainingFeeMultiplier = _newTrainingFeeMultiplier; } /// @dev Updates the fee contribution multiplier required for calling trainEquipment(). function setEquipmentTrainingFeeMultiplier(uint _newEquipmentTrainingFeeMultiplier) onlyOwner external { equipmentTrainingFeeMultiplier = _newEquipmentTrainingFeeMultiplier; } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /** * @dev Internal function to set the previously temp stored upgraded hero genes. * Every challenge/training will first call this function. */ function _setTempHeroPower() internal { // Genes of 1 is used as no pending update. if (tempSuccessTrainingNewHeroGenes != 1) { // ** STORAGE UPDATE ** heroTokenContract.setHeroGenes(tempSuccessTrainingHeroId, tempSuccessTrainingNewHeroGenes); // Reset the variables to indicate no pending update. tempSuccessTrainingNewHeroGenes = 1; } } /* ======== MODIFIERS ======== */ /** * @dev Throws if _dungeonId is not created yet. */ modifier dungeonExists(uint _dungeonId) { require(_dungeonId < dungeonTokenContract.totalSupply()); _; } } contract EDTransportation is EDBase { /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /// @dev Recruit a new novice hero with no attributes (gene = 0). function recruitHero() whenNotPaused external payable returns (uint) { // Only allow recruiting hero in the novice dungeon, or first time recruiting hero. require(playerToDungeonID[msg.sender] == noviceDungeonId || !playerToFirstHeroRecruited[msg.sender]); // Checks for payment, any exceeding funds will be transferred back to the player. require(msg.value >= recruitHeroFee); // ** STORAGE UPDATE ** // Increment the accumulated rewards for the dungeon, // since player can only recruit hero in the novice dungeon, rewards is added there. dungeonTokenContract.addDungeonRewards(noviceDungeonId, recruitHeroFee); // Calculate any excess funds and make it available to be withdrawed by the player. asyncSend(msg.sender, msg.value - recruitHeroFee); // If it is the first time recruiting a hero, set the player's location to the novice dungeon. if (!playerToFirstHeroRecruited[msg.sender]) { // ** STORAGE UPDATE ** dungeonIdToPlayerCount[noviceDungeonId]++; playerToDungeonID[msg.sender] = noviceDungeonId; playerToFirstHeroRecruited[msg.sender] = true; } return heroTokenContract.createHero(0, msg.sender); } /** * @dev The main external function to call when a player transport to another dungeon. * Will generate a PlayerTransported event. * Player must have at least one hero in order to perform */ function transport(uint _destinationDungeonId) whenNotPaused dungeonCanTransport(_destinationDungeonId) playerAllowedToTransport() external payable { uint originDungeonId = playerToDungeonID[msg.sender]; // Disallow transport to the same dungeon. require(_destinationDungeonId != originDungeonId); // Get the dungeon details from the token contract. uint difficulty; (,, difficulty,,,,,,) = dungeonTokenContract.dungeons(_destinationDungeonId); // Disallow weaker user to transport to "difficult" dungeon. uint top5HeroesPower = calculateTop5HeroesPower(msg.sender, _destinationDungeonId); require(top5HeroesPower >= difficulty * 12); // Checks for payment, any exceeding funds will be transferred back to the player. // The transportation fee is calculated by a base fee from transportationFeeMultiplier, // plus an additional fee increased with the total power of top 5 heroes owned. uint baseFee = difficulty * transportationFeeMultiplier; uint additionalFee = top5HeroesPower / 64 * transportationFeeMultiplier; uint requiredFee = baseFee + additionalFee; require(msg.value >= requiredFee); // ** STORAGE UPDATE ** // Increment the accumulated rewards for the dungeon. dungeonTokenContract.addDungeonRewards(originDungeonId, requiredFee); // Calculate any excess funds and make it available to be withdrawed by the player. asyncSend(msg.sender, msg.value - requiredFee); _transport(originDungeonId, _destinationDungeonId); } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /// @dev Internal function to assigns location of a player. function _transport(uint _originDungeonId, uint _destinationDungeonId) internal { // ** STORAGE UPDATE ** // Update the dungeons' player count. // Normally the player count of original dungeon will already be > 0, // perform checking to avoid unexpected overflow if (dungeonIdToPlayerCount[_originDungeonId] > 0) { dungeonIdToPlayerCount[_originDungeonId]--; } dungeonIdToPlayerCount[_destinationDungeonId]++; // ** STORAGE UPDATE ** // Update player location. playerToDungeonID[msg.sender] = _destinationDungeonId; // Emit the DungeonChallenged event. PlayerTransported(now, msg.sender, _originDungeonId, _destinationDungeonId); } /* ======== MODIFIERS ======== */ /** * @dev Throws if dungeon status do not allow transportation, also check for dungeon existence. * Also check if the capacity of the destination dungeon is reached. */ modifier dungeonCanTransport(uint _destinationDungeonId) { require(_destinationDungeonId < dungeonTokenContract.totalSupply()); uint status; uint capacity; (, status,, capacity,,,,,) = dungeonTokenContract.dungeons(_destinationDungeonId); require(status == 0 || status == 1); // Check if the capacity of the destination dungeon is reached. // Capacity 0 = Infinity require(capacity == 0 || dungeonIdToPlayerCount[_destinationDungeonId] < capacity); _; } /// @dev Throws if player did recruit first hero yet. modifier playerAllowedToTransport() { // Note that we check playerToFirstHeroRecruited instead of heroTokenContract.balanceOf // in order to prevent "capacity attack". require(playerToFirstHeroRecruited[msg.sender]); _; } } contract EDChallenge is EDTransportation { /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev The main external function to call when a player challenge a dungeon, * it determines whether if the player successfully challenged the current floor. * Will generate a DungeonChallenged event. */ function challenge(uint _dungeonId, uint _heroId) whenNotPaused dungeonCanChallenge(_dungeonId) heroAllowedToChallenge(_heroId) external payable { // Set the last action block number, disallow player to perform another train or challenge in the same block. playerToLastActionBlockNumber[msg.sender] = block.number; // Set the previously temp stored upgraded hero genes. _setTempHeroPower(); // Get the dungeon details from the token contract. uint difficulty; uint seedGenes; (,, difficulty,,,,, seedGenes,) = dungeonTokenContract.dungeons(_dungeonId); // Checks for payment, any exceeding funds will be transferred back to the player. uint requiredFee = difficulty * challengeFeeMultiplier; require(msg.value >= requiredFee); // ** STORAGE UPDATE ** // Increment the accumulated rewards for the dungeon. dungeonTokenContract.addDungeonRewards(_dungeonId, requiredFee); // Calculate any excess funds and make it available to be withdrawed by the player. asyncSend(msg.sender, msg.value - requiredFee); // Split the challenge function into multiple parts because of stack too deep error. _challengePart2(_dungeonId, difficulty, _heroId); } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /// @dev Compute the remaining time for which the hero can perform a challenge again. function _computeCooldownRemainingTime(uint _heroId) internal view returns (uint) { uint cooldownStartTime; uint cooldownIndex; (, cooldownStartTime, cooldownIndex,) = heroTokenContract.heroes(_heroId); // Cooldown period is FLOOR(challenge count / 2) ^ 2 minutes uint cooldownPeriod = (cooldownIndex / 2) ** 2 * 1 minutes; if (cooldownPeriod > 100 minutes) { cooldownPeriod = 100 minutes; } uint cooldownEndTime = cooldownStartTime + cooldownPeriod; if (cooldownEndTime <= now) { return 0; } else { return cooldownEndTime - now; } } /// @dev Split the challenge function into multiple parts because of stack too deep error. function _challengePart2(uint _dungeonId, uint _dungeonDifficulty, uint _heroId) private { uint floorNumber; uint rewards; uint floorGenes; (,,,, floorNumber,, rewards,, floorGenes) = dungeonTokenContract.dungeons(_dungeonId); // Get the hero gene. uint heroGenes; (,,, heroGenes) = heroTokenContract.heroes(_heroId); bool success = _getChallengeSuccess(heroGenes, _dungeonDifficulty, floorGenes); uint newFloorGenes; uint masterRewards; uint consolationRewards; uint successRewards; uint newRewards; // Whether a challenge is success or not is determined by a simple comparison between hero power and floor power. if (success) { newFloorGenes = _getNewFloorGene(_dungeonId); masterRewards = rewards * masterRewardsPercent / 100; consolationRewards = rewards * consolationRewardsPercent / 100; if (floorNumber < rushTimeFloorCount) { // rush time right after prepration period successRewards = rewards * rushTimeChallengeRewardsPercent / 100; // The dungeon rewards for new floor as total rewards - challenge rewards - devleoper fee. newRewards = rewards * (100 - rushTimeChallengeRewardsPercent - masterRewardsPercent - consolationRewardsPercent) / 100; } else { successRewards = rewards * challengeRewardsPercent / 100; newRewards = rewards * (100 - challengeRewardsPercent - masterRewardsPercent - consolationRewardsPercent) / 100; } // TRIPLE CONFIRM sanity check. require(successRewards + masterRewards + consolationRewards + newRewards <= rewards); // ** STORAGE UPDATE ** // Add the consolation rewards to grandConsolationRewards. grandConsolationRewards += consolationRewards; // Add new floor with the new floor genes and new rewards. dungeonTokenContract.addDungeonNewFloor(_dungeonId, newRewards, newFloorGenes); // Mark the challenge rewards available to be withdrawed by the player. asyncSend(msg.sender, successRewards); // Mark the master rewards available to be withdrawed by the dungeon master. asyncSend(dungeonTokenContract.ownerOf(_dungeonId), masterRewards); } // ** STORAGE UPDATE ** // Trigger the cooldown for the hero. heroTokenContract.triggerCooldown(_heroId); // Emit the DungeonChallenged event. DungeonChallenged(now, msg.sender, _dungeonId, _heroId, heroGenes, floorNumber, floorGenes, success, newFloorGenes, successRewards, masterRewards); } /// @dev Split the challenge function into multiple parts because of stack too deep error. function _getChallengeSuccess(uint _heroGenes, uint _dungeonDifficulty, uint _floorGenes) private pure returns (bool) { // Determine if the player challenge successfuly the dungeon or not. uint heroPower; (heroPower,,,,,) = getHeroPower(_heroGenes, _dungeonDifficulty); uint floorPower = getDungeonPower(_floorGenes); return heroPower > floorPower; } /// @dev Split the challenge function into multiple parts because of stack too deep error. function _getNewFloorGene(uint _dungeonId) private returns (uint) { uint seedGenes; uint floorGenes; (,,,,,, seedGenes, floorGenes) = dungeonTokenContract.dungeons(_dungeonId); // Calculate the new floor gene. uint floorPower = getDungeonPower(floorGenes); // Call the external closed source secret function that determines the resulting floor "genes". uint newFloorGenes = challengeFormulaContract.calculateResult(floorGenes, seedGenes); uint newFloorPower = getDungeonPower(newFloorGenes); // If the power decreased, rollback to the current floor genes. if (newFloorPower < floorPower) { newFloorGenes = floorGenes; } return newFloorGenes; } /* ======== MODIFIERS ======== */ /** * @dev Throws if dungeon status do not allow challenge, also check for dungeon existence. * Also check if the user is in the dungeon. * Also check if the dungeon is not in preparation period. */ modifier dungeonCanChallenge(uint _dungeonId) { require(_dungeonId < dungeonTokenContract.totalSupply()); uint creationTime; uint status; (creationTime, status,,,,,,,) = dungeonTokenContract.dungeons(_dungeonId); require(status == 0 || status == 2); // Check if the user is in the dungeon. require(playerToDungeonID[msg.sender] == _dungeonId); // Check if the dungeon is not in preparation period. require(creationTime + dungeonPreparationTime <= now); _; } /** * @dev Throws if player does not own the hero, or the hero is still in cooldown period, * and no pending power update. */ modifier heroAllowedToChallenge(uint _heroId) { // You can only challenge with your own hero. require(heroTokenContract.ownerOf(_heroId) == msg.sender); // Hero must not be in cooldown period uint cooldownRemainingTime = _computeCooldownRemainingTime(_heroId); require(cooldownRemainingTime == 0); // Prevent player to perform training and challenge in the same block to avoid bot exploit. require(block.number > playerToLastActionBlockNumber[msg.sender]); _; } } contract EDTraining is EDChallenge { /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev The external function to call when a hero train with a dungeon, * it determines whether whether a training is successfully, and the resulting genes. * Will generate a DungeonChallenged event. */ function train1(uint _dungeonId, uint _heroId) whenNotPaused dungeonCanTrain(_dungeonId) heroAllowedToTrain(_heroId) external payable { _train(_dungeonId, _heroId, 0, 1); } function train2(uint _dungeonId, uint _heroId) whenNotPaused dungeonCanTrain(_dungeonId) heroAllowedToTrain(_heroId) external payable { _train(_dungeonId, _heroId, 0, 2); } function train3(uint _dungeonId, uint _heroId) whenNotPaused dungeonCanTrain(_dungeonId) heroAllowedToTrain(_heroId) external payable { _train(_dungeonId, _heroId, 0, 3); } /** * @dev The external function to call when a hero train a particular equipment with a dungeon, * it determines whether whether a training is successfully, and the resulting genes. * Will generate a DungeonChallenged event. * _equipmentIndex is the index of equipment: 0 is train all attributes, including equipments and stats. * 1: weapon | 2: shield | 3: armor | 4: shoe | 5: helmet | 6: gloves | 7: belt | 8: shawl */ function trainEquipment(uint _dungeonId, uint _heroId, uint _equipmentIndex) whenNotPaused dungeonCanTrain(_dungeonId) heroAllowedToTrain(_heroId) external payable { require(_equipmentIndex <= 8); _train(_dungeonId, _heroId, _equipmentIndex, 1); } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /** * @dev An internal function of a hero train with dungeon, * it determines whether whether a training is successfully, and the resulting genes. * Will generate a DungeonChallenged event. */ function _train(uint _dungeonId, uint _heroId, uint _equipmentIndex, uint _trainingTimes) private { // Set the last action block number, disallow player to perform another train or challenge in the same block. playerToLastActionBlockNumber[msg.sender] = block.number; // Set the previously temp stored upgraded hero genes. _setTempHeroPower(); // Get the dungeon details from the token contract. uint creationTime; uint difficulty; uint floorNumber; uint rewards; uint seedGenes; uint floorGenes; (creationTime,, difficulty,, floorNumber,, rewards, seedGenes, floorGenes) = dungeonTokenContract.dungeons(_dungeonId); // Check for _trainingTimes abnormality, we probably won't have any feature that train a hero 10 times with a single call. require(_trainingTimes < 10); // Checks for payment, any exceeding funds will be transferred back to the player. uint requiredFee; // Calculate the required training fee. if (now < creationTime + dungeonPreparationTime) { // Apply preparation period discount. if (_equipmentIndex > 0) { // train specific equipments requiredFee = difficulty * preparationPeriodEquipmentTrainingFeeMultiplier * _trainingTimes; } else { // train all attributes requiredFee = difficulty * preparationPeriodTrainingFeeMultiplier * _trainingTimes; } } else { if (_equipmentIndex > 0) { // train specific equipments requiredFee = difficulty * equipmentTrainingFeeMultiplier * _trainingTimes; } else { // train all attributes requiredFee = difficulty * trainingFeeMultiplier * _trainingTimes; } } require(msg.value >= requiredFee); // Get the hero gene. uint heroGenes; (,,, heroGenes) = heroTokenContract.heroes(_heroId); // ** STORAGE UPDATE ** // Increment the accumulated rewards for the dungeon. dungeonTokenContract.addDungeonRewards(_dungeonId, requiredFee); // Calculate any excess funds and make it available to be withdrawed by the player. asyncSend(msg.sender, msg.value - requiredFee); // Split the _train function into multiple parts because of stack too deep error. _trainPart2(_dungeonId, _heroId, _equipmentIndex, _trainingTimes, difficulty, floorNumber, floorGenes, heroGenes); } /// @dev Split the _train function into multiple parts because of Stack Too Deep error. function _trainPart2( uint _dungeonId, uint _heroId, uint _equipmentIndex, uint _trainingTimes, uint _dungeonDifficulty, uint _floorNumber, uint _floorGenes, uint _heroGenes ) private { // Determine if the hero training is successful or not, and the resulting genes. uint heroPower; bool isSuper; (heroPower,,, isSuper,,) = getHeroPower(_heroGenes, _dungeonDifficulty); uint newHeroGenes; uint newHeroPower; (newHeroGenes, newHeroPower) = _calculateNewHeroPower(_dungeonDifficulty, _heroGenes, _equipmentIndex, _trainingTimes, heroPower, isSuper, _floorGenes); // Set the new hero genes if updated (sometimes there is no power increase during equipment forging). if (newHeroGenes != _heroGenes) { if (newHeroPower >= 256) { // Do not update immediately to prevent deterministic training exploit. tempSuccessTrainingHeroId = _heroId; tempSuccessTrainingNewHeroGenes = newHeroGenes; } else { // Immediately update the genes for small power hero. // ** STORAGE UPDATE ** heroTokenContract.setHeroGenes(_heroId, newHeroGenes); } } // Training is successful only when power increase, changing another equipment with same power is considered failure // and faith will be given accordingly. bool success = newHeroPower > heroPower; if (!success) { // Handle training failure - consolation rewards mechanics. _handleTrainingFailure(_equipmentIndex, _trainingTimes, _dungeonDifficulty); } // Emit the HeroTrained event. HeroTrained(now, msg.sender, _dungeonId, _heroId, _heroGenes, _floorNumber, _floorGenes, success, newHeroGenes); } /// @dev Determine if the hero training is successful or not, and the resulting genes and power. function _calculateNewHeroPower( uint _dungeonDifficulty, uint _heroGenes, uint _equipmentIndex, uint _trainingTimes, uint _heroPower, bool _isSuper, uint _floorGenes ) private returns (uint newHeroGenes, uint newHeroPower) { newHeroGenes = _heroGenes; newHeroPower = _heroPower; bool newIsSuper = _isSuper; // Train the hero multiple times according to _trainingTimes, // each time if the resulting power is larger, update new hero power. for (uint i = 0; i < _trainingTimes; i++) { // Call the external closed source secret function that determines the resulting hero "genes". uint tmpHeroGenes = trainingFormulaContract.calculateResult(newHeroGenes, _floorGenes, _equipmentIndex); uint tmpHeroPower; bool tmpIsSuper; (tmpHeroPower,,, tmpIsSuper,,) = getHeroPower(tmpHeroGenes, _dungeonDifficulty); if (tmpHeroPower > newHeroPower) { // Prevent Super Hero downgrade. if (!(newIsSuper && !tmpIsSuper)) { newHeroGenes = tmpHeroGenes; newHeroPower = tmpHeroPower; } } else if (_equipmentIndex > 0 && tmpHeroPower == newHeroPower && tmpHeroGenes != newHeroGenes) { // Allow Equipment Forging to replace current requipemnt with a same power equipment. // The training is considered failed (faith will be given, but the equipment will change). newHeroGenes = tmpHeroGenes; newHeroPower = tmpHeroPower; } } } /// @dev Calculate and assign the appropriate faith value to the player. function _handleTrainingFailure(uint _equipmentIndex, uint _trainingTimes, uint _dungeonDifficulty) private { // Failed training in a dungeon will add to player's faith value. uint faith = playerToFaith[msg.sender]; uint faithEarned; if (_equipmentIndex == 0) { // Hero Training // The faith earned is proportional to the training fee, i.e. _difficulty * _trainingTimes. faithEarned = _dungeonDifficulty * _trainingTimes; } else { // Equipment Forging // Equipment Forging faith earned is only 2 times normal training, not proportional to forging fee. faithEarned = _dungeonDifficulty * _trainingTimes * 2; } uint newFaith = faith + faithEarned; // Hitting the required amount in faith will get a proportion of grandConsolationRewards if (newFaith >= consolationRewardsRequiredFaith) { uint consolationRewards = grandConsolationRewards * consolationRewardsClaimPercent / 100; // ** STORAGE UPDATE ** grandConsolationRewards -= consolationRewards; // Mark the consolation rewards available to be withdrawed by the player. asyncSend(msg.sender, consolationRewards); // Reset the faith value. newFaith -= consolationRewardsRequiredFaith; ConsolationRewardsClaimed(now, msg.sender, consolationRewards); } // ** STORAGE UPDATE ** playerToFaith[msg.sender] = newFaith; } /* ======== MODIFIERS ======== */ /** * @dev Throws if dungeon status do not allow training, also check for dungeon existence. * Also check if the user is in the dungeon. */ modifier dungeonCanTrain(uint _dungeonId) { require(_dungeonId < dungeonTokenContract.totalSupply()); uint status; (,status,,,,,,,) = dungeonTokenContract.dungeons(_dungeonId); require(status == 0 || status == 3); // Also check if the user is in the dungeon. require(playerToDungeonID[msg.sender] == _dungeonId); _; } /** * @dev Throws if player does not own the hero, and no pending power update. */ modifier heroAllowedToTrain(uint _heroId) { require(heroTokenContract.ownerOf(_heroId) == msg.sender); // Prevent player to perform training and challenge in the same block to avoid bot exploit. require(block.number > playerToLastActionBlockNumber[msg.sender]); _; } } /** * @title EDCoreVersion1 * @dev Core Contract of Ether Dungeon. * When Version 2 launches, EDCoreVersion2 contract will be deployed and EDCoreVersion1 will be destroyed. * Since all dungeons and heroes are stored as tokens in external contracts, they remains immutable. */ contract EDCoreVersion1 is Destructible, EDTraining { /** * Initialize the EDCore contract with all the required contract addresses. */ function EDCoreVersion1( address _dungeonTokenAddress, address _heroTokenAddress, address _challengeFormulaAddress, address _trainingFormulaAddress ) public payable { dungeonTokenContract = DungeonTokenInterface(_dungeonTokenAddress); heroTokenContract = HeroTokenInterface(_heroTokenAddress); challengeFormulaContract = ChallengeFormulaInterface(_challengeFormulaAddress); trainingFormulaContract = TrainingFormulaInterface(_trainingFormulaAddress); } /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /// @dev The external function to get all the game settings in one call. function getGameSettings() external view returns ( uint _recruitHeroFee, uint _transportationFeeMultiplier, uint _noviceDungeonId, uint _consolationRewardsRequiredFaith, uint _challengeFeeMultiplier, uint _dungeonPreparationTime, uint _trainingFeeMultiplier, uint _equipmentTrainingFeeMultiplier, uint _preparationPeriodTrainingFeeMultiplier, uint _preparationPeriodEquipmentTrainingFeeMultiplier ) { _recruitHeroFee = recruitHeroFee; _transportationFeeMultiplier = transportationFeeMultiplier; _noviceDungeonId = noviceDungeonId; _consolationRewardsRequiredFaith = consolationRewardsRequiredFaith; _challengeFeeMultiplier = challengeFeeMultiplier; _dungeonPreparationTime = dungeonPreparationTime; _trainingFeeMultiplier = trainingFeeMultiplier; _equipmentTrainingFeeMultiplier = equipmentTrainingFeeMultiplier; _preparationPeriodTrainingFeeMultiplier = preparationPeriodTrainingFeeMultiplier; _preparationPeriodEquipmentTrainingFeeMultiplier = preparationPeriodEquipmentTrainingFeeMultiplier; } /** * @dev The external function to get all the relevant information about a specific player by its address. * @param _address The address of the player. */ function getPlayerDetails(address _address) external view returns ( uint dungeonId, uint payment, uint dungeonCount, uint heroCount, uint faith, bool firstHeroRecruited ) { payment = payments[_address]; dungeonCount = dungeonTokenContract.balanceOf(_address); heroCount = heroTokenContract.balanceOf(_address); faith = playerToFaith[_address]; firstHeroRecruited = playerToFirstHeroRecruited[_address]; // If a player didn't recruit any hero yet, consider the player is in novice dungeon if (firstHeroRecruited) { dungeonId = playerToDungeonID[_address]; } else { dungeonId = noviceDungeonId; } } /** * @dev The external function to get all the relevant information about a specific dungeon by its ID. * @param _id The ID of the dungeon. */ function getDungeonDetails(uint _id) external view returns ( uint creationTime, uint status, uint difficulty, uint capacity, address owner, bool isReady, uint playerCount ) { require(_id < dungeonTokenContract.totalSupply()); // Didn't get the "floorCreationTime" because of Stack Too Deep error. (creationTime, status, difficulty, capacity,,,,,) = dungeonTokenContract.dungeons(_id); // Dungeon is ready to be challenged (not in preparation mode). owner = dungeonTokenContract.ownerOf(_id); isReady = creationTime + dungeonPreparationTime <= now; playerCount = dungeonIdToPlayerCount[_id]; } /** * @dev Split floor related details out of getDungeonDetails, just to avoid Stack Too Deep error. * @param _id The ID of the dungeon. */ function getDungeonFloorDetails(uint _id) external view returns ( uint floorNumber, uint floorCreationTime, uint rewards, uint seedGenes, uint floorGenes ) { require(_id < dungeonTokenContract.totalSupply()); // Didn't get the "floorCreationTime" because of Stack Too Deep error. (,,,, floorNumber, floorCreationTime, rewards, seedGenes, floorGenes) = dungeonTokenContract.dungeons(_id); } /** * @dev The external function to get all the relevant information about a specific hero by its ID. * @param _id The ID of the hero. */ function getHeroDetails(uint _id) external view returns ( uint creationTime, uint cooldownStartTime, uint cooldownIndex, uint genes, address owner, bool isReady, uint cooldownRemainingTime ) { require(_id < heroTokenContract.totalSupply()); (creationTime, cooldownStartTime, cooldownIndex, genes) = heroTokenContract.heroes(_id); // Hero is ready to challenge (not in cooldown mode). owner = heroTokenContract.ownerOf(_id); cooldownRemainingTime = _computeCooldownRemainingTime(_id); isReady = cooldownRemainingTime == 0; } /* ======== MIGRATION FUNCTIONS ======== */ /** * @dev Since the DungeonToken contract is re-deployed due to optimization. * We need to migrate all dungeons from Beta token contract to Version 1. */ function migrateDungeon(uint _id, uint _playerCount) external { // Migration will be finished before maintenance period ends, tx.origin is used within a short period only. require(now < 1520694000 && tx.origin == 0x47169f78750Be1e6ec2DEb2974458ac4F8751714); dungeonIdToPlayerCount[_id] = _playerCount; } /** * @dev We need to migrate all player location from Beta token contract to Version 1. */ function migratePlayer(address _address, uint _ownerDungeonId, uint _payment, uint _faith) external { // Migration will be finished before maintenance period ends, tx.origin is used within a short period only. require(now < 1520694000 && tx.origin == 0x47169f78750Be1e6ec2DEb2974458ac4F8751714); playerToDungeonID[_address] = _ownerDungeonId; if (_payment > 0) { asyncSend(_address, _payment); } if (_faith > 0) { playerToFaith[_address] = _faith; } playerToFirstHeroRecruited[_address] = true; } }
File 3 of 3: HeroTokenAuction
pragma solidity ^0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @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) onlyOwner public { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } /** * @title JointOwnable * @dev Extension for the Ownable contract, where the owner can assign at most 2 other addresses * to manage some functions of the contract, using the eitherOwner modifier. * Note that onlyOwner modifier would still be accessible only for the original owner. */ contract JointOwnable is Ownable { event AnotherOwnerAssigned(address indexed anotherOwner); address public anotherOwner1; address public anotherOwner2; /** * @dev Throws if called by any account other than the owner or anotherOwner. */ modifier eitherOwner() { require(msg.sender == owner || msg.sender == anotherOwner1 || msg.sender == anotherOwner2); _; } /** * @dev Allows the current owner to assign another owner. * @param _anotherOwner The address to another owner. */ function assignAnotherOwner1(address _anotherOwner) onlyOwner public { require(_anotherOwner != 0); AnotherOwnerAssigned(_anotherOwner); anotherOwner1 = _anotherOwner; } /** * @dev Allows the current owner to assign another owner. * @param _anotherOwner The address to another owner. */ function assignAnotherOwner2(address _anotherOwner) onlyOwner public { require(_anotherOwner != 0); AnotherOwnerAssigned(_anotherOwner); anotherOwner2 = _anotherOwner; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ 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; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } /** * @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens. */ contract ERC721 { // Events event Transfer(address indexed from, address indexed to, uint indexed tokenId); event Approval(address indexed owner, address indexed approved, uint indexed tokenId); // ERC20 compatible functions. // function name() public constant returns (string); // function symbol() public constant returns (string); function totalSupply() public view returns (uint); function balanceOf(address _owner) public view returns (uint); // Functions that define ownership. function ownerOf(uint _tokenId) external view returns (address); function transfer(address _to, uint _tokenId) external; // Approval related functions, mainly used in auction contracts. function approve(address _to, uint _tokenId) external; function approvedFor(uint _tokenId) external view returns (address); function transferFrom(address _from, address _to, uint _tokenId) external; /** * @dev Each non-fungible token owner can own more than one token at one time. * Because each token is referenced by its unique ID, however, * it can get difficult to keep track of the individual tokens that a user may own. * To do this, the contract keeps a record of the IDs of each token that each user owns. */ mapping(address => uint[]) public ownerTokens; } /** * @title The ERC-721 compliance token contract. */ contract ERC721Token is ERC721, Pausable { /* ======== STATE VARIABLES ======== */ /** * @dev A mapping from token IDs to the address that owns them. */ mapping(uint => address) tokenIdToOwner; /** * @dev A mapping from token ids to an address that has been approved to call * transferFrom(). Each token can only have one approved address for transfer * at any time. A zero value means no approval is outstanding. */ mapping (uint => address) tokenIdToApproved; /** * @dev A mapping from token ID to index of the ownerTokens' tokens list. */ mapping(uint => uint) tokenIdToOwnerTokensIndex; /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev Returns the number of tokens owned by a specific address. * @param _owner The owner address to check. */ function balanceOf(address _owner) public view returns (uint) { return ownerTokens[_owner].length; } /** * @dev Returns the address currently assigned ownership of a given token. */ function ownerOf(uint _tokenId) external view returns (address) { require(tokenIdToOwner[_tokenId] != address(0)); return tokenIdToOwner[_tokenId]; } /** * @dev Returns the approved address of a given token. */ function approvedFor(uint _tokenId) external view returns (address) { return tokenIdToApproved[_tokenId]; } /** * @dev Get an array of IDs of each token that an user owns. */ function getOwnerTokens(address _owner) external view returns(uint[]) { return ownerTokens[_owner]; } /** * @dev External function to transfers a token to another address. * @param _to The address of the recipient, can be a user or contract. * @param _tokenId The ID of the token to transfer. */ function transfer(address _to, uint _tokenId) whenNotPaused external { // Safety check to prevent against an unexpected 0x0 default. require(_to != address(0)); // Disallow transfers to this contract to prevent accidental misuse. require(_to != address(this)); // You can only send your own token. require(_owns(msg.sender, _tokenId)); // Reassign ownership, clear pending approvals, emit Transfer event. _transfer(msg.sender, _to, _tokenId); } /** * @dev Grant another address the right to transfer a specific Kitty 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 Kitty that can be transferred if this call succeeds. */ function approve(address _to, uint _tokenId) whenNotPaused 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); } /** * @dev Transfer a Kitty 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 Kitty to be transfered. * @param _to The address that should take ownership of the Kitty. Can be any address, * including the caller. * @param _tokenId The ID of the Kitty to be transferred. */ function transferFrom(address _from, address _to, uint _tokenId) whenNotPaused external { // Safety check to prevent against an unexpected 0x0 default. require(_to != address(0)); // Check for approval and valid ownership require(tokenIdToApproved[_tokenId] == msg.sender); require(_owns(_from, _tokenId)); // Reassign ownership (also clears pending approvals and emits Transfer event). _transfer(_from, _to, _tokenId); } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /** * @dev Assigns ownership of a specific token to an address. */ function _transfer(address _from, address _to, uint _tokenId) internal { // Step 1: Remove token from _form address. // When creating new token, _from is 0x0. if (_from != address(0)) { uint[] storage fromTokens = ownerTokens[_from]; uint tokenIndex = tokenIdToOwnerTokensIndex[_tokenId]; // Put the last token to the transferred token index and update its index in ownerTokensIndexes. uint lastTokenId = fromTokens[fromTokens.length - 1]; // Do nothing if the transferring token is the last item. if (_tokenId != lastTokenId) { fromTokens[tokenIndex] = lastTokenId; tokenIdToOwnerTokensIndex[lastTokenId] = tokenIndex; } fromTokens.length--; } // Step 2: Add token to _to address. // Transfer ownership. tokenIdToOwner[_tokenId] = _to; // Add the _tokenId to ownerTokens[_to] and remember the index in ownerTokensIndexes. tokenIdToOwnerTokensIndex[_tokenId] = ownerTokens[_to].length; ownerTokens[_to].push(_tokenId); // Emit the Transfer event. Transfer(_from, _to, _tokenId); } /** * @dev Marks an address as being approved for transferFrom(), overwriting any previous * approval. Setting _approved to address(0) clears all transfer approval. */ function _approve(uint _tokenId, address _approved) internal { tokenIdToApproved[_tokenId] = _approved; } /* ======== MODIFIERS ======== */ /** * @dev Throws if _dungeonId is not created yet. */ modifier tokenExists(uint _tokenId) { require(_tokenId < totalSupply()); _; } /** * @dev Checks if a given address is the current owner of a particular token. * @param _claimant The address we are validating against. * @param _tokenId Token ID */ function _owns(address _claimant, uint _tokenId) internal view returns (bool) { return tokenIdToOwner[_tokenId] == _claimant; } } contract EDStructs { /** * @dev The main Dungeon struct. Every dungeon in the game is represented by this structure. * A dungeon is consists of an unlimited number of floors for your heroes to challenge, * the power level of a dungeon is encoded in the floorGenes. Some dungeons are in fact more "challenging" than others, * the secret formula for that is left for user to find out. * * Each dungeon also has a "training area", heroes can perform trainings and upgrade their stat, * and some dungeons are more effective in the training, which is also a secret formula! * * When player challenge or do training in a dungeon, the fee will be collected as the dungeon rewards, * which will be rewarded to the player who successfully challenged the current floor. * * Each dungeon fits in fits into three 256-bit words. */ struct Dungeon { // Each dungeon has an ID which is the index in the storage array. // The timestamp of the block when this dungeon is created. uint32 creationTime; // The status of the dungeon, each dungeon can have 5 status, namely: // 0: Active | 1: Transport Only | 2: Challenge Only | 3: Train Only | 4: InActive uint8 status; // The dungeon's difficulty, the higher the difficulty, // normally, the "rarer" the seedGenes, the higher the diffculty, // and the higher the contribution fee it is to challenge, train, and transport to the dungeon, // the formula for the contribution fee is in DungeonChallenge and DungeonTraining contracts. // A dungeon's difficulty never change. uint8 difficulty; // The dungeon's capacity, maximum number of players allowed to stay on this dungeon. // The capacity of the newbie dungeon (Holyland) is set at 0 (which is infinity). // Using 16-bit unsigned integers can have a maximum of 65535 in capacity. // A dungeon's capacity never change. uint16 capacity; // The current floor number, a dungeon is consists of an umlimited number of floors, // when there is heroes successfully challenged a floor, the next floor will be // automatically generated. Using 32-bit unsigned integer can have a maximum of 4 billion floors. uint32 floorNumber; // The timestamp of the block when the current floor is generated. uint32 floorCreationTime; // Current accumulated rewards, successful challenger will get a large proportion of it. uint128 rewards; // The seed genes of the dungeon, it is used as the base gene for first floor, // some dungeons are rarer and some are more common, the exact details are, // of course, top secret of the game! // A dungeon's seedGenes never change. uint seedGenes; // The genes for current floor, it encodes the difficulty level of the current floor. // We considered whether to store the entire array of genes for all floors, but // in order to save some precious gas we're willing to sacrifice some functionalities with that. uint floorGenes; } /** * @dev The main Hero struct. Every hero in the game is represented by this structure. */ struct Hero { // Each hero has an ID which is the index in the storage array. // The timestamp of the block when this dungeon is created. uint64 creationTime; // The timestamp of the block where a challenge is performed, used to calculate when a hero is allowed to engage in another challenge. uint64 cooldownStartTime; // Every time a hero challenge a dungeon, its cooldown index will be incremented by one. uint32 cooldownIndex; // The seed of the hero, the gene encodes the power level of the hero. // This is another top secret of the game! Hero's gene can be upgraded via // training in a dungeon. uint genes; } } contract HeroTokenInterface is ERC721, EDStructs { /** * @dev Name of token. */ string public constant name = "Hero"; /** * @dev Symbol of token. */ string public constant symbol = "HERO"; /** * @dev An array containing the Hero struct, which contains all the heroes in existance. * The ID for each hero is the index of this array. */ Hero[] public heroes; /** * @dev An external function that creates a new hero and stores it, * only contract owners can create new token. * method doesn't do any checking and should only be called when the * input data is known to be valid. * @param _genes The gene of the new hero. * @param _owner The inital owner of this hero. * @return The hero ID of the new hero. */ function createHero(uint _genes, address _owner) external returns (uint); /** * @dev The external function to set the hero genes by its ID, * only contract owners can alter hero state. */ function setHeroGenes(uint _id, uint _newGenes) external; /** * @dev Set the cooldownStartTime for the given hero. Also increments the cooldownIndex. */ function triggerCooldown(uint _id) external; } /** * @title The ERC-721 compliance token contract for the Hero tokens. * @dev See the DungeonStructs contract to see the details of the Hero token data structure. */ contract HeroToken is HeroTokenInterface, ERC721Token, JointOwnable { /* ======== EVENTS ======== */ /** * @dev The Mint event is fired whenever a new hero is created. */ event Mint(address indexed owner, uint newTokenId, uint genes); /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev Returns the total number of tokens currently in existence. */ function totalSupply() public view returns (uint) { return heroes.length; } /** * @dev An external function that creates a new hero and stores it, * only contract owners can create new token. * method doesn't do any checking and should only be called when the * input data is known to be valid. * @param _genes The gene of the new hero. * @param _owner The inital owner of this hero. * @return The hero ID of the new hero. */ function createHero(uint _genes, address _owner) eitherOwner external returns (uint) { return _createHero(_genes, _owner); } /** * @dev The external function to set the hero genes by its ID, * only contract owners can alter hero state. */ function setHeroGenes(uint _id, uint _newGenes) eitherOwner tokenExists(_id) external { heroes[_id].genes = _newGenes; } /** * @dev Set the cooldownStartTime for the given hero. Also increments the cooldownIndex. */ function triggerCooldown(uint _id) eitherOwner tokenExists(_id) external { Hero storage hero = heroes[_id]; hero.cooldownStartTime = uint64(now); hero.cooldownIndex++; } /* ======== PRIVATE/INTERNAL FUNCTIONS ======== */ function _createHero(uint _genes, address _owner) private returns (uint) { // ** STORAGE UPDATE ** // Create a new hero. heroes.push(Hero(uint64(now), 0, 0, _genes)); // Token id is the index in the storage array. uint newTokenId = heroes.length - 1; // Emit the token mint event. Mint(_owner, newTokenId, _genes); // This will assign ownership, and also emit the Transfer event. _transfer(0, _owner, newTokenId); return newTokenId; } /* ======== MIGRATION FUNCTIONS ======== */ /** * @dev Since the HeroToken contract is re-deployed due to optimization. * We need to migrate all heroes from Beta token contract to Version 1. */ function migrateHero(uint _genes, address _owner) external { // Migration will be finished before maintenance period ends, tx.origin is used within a short period only. require(now < 1520694000 && tx.origin == 0x47169f78750Be1e6ec2DEb2974458ac4F8751714); _createHero(_genes, _owner); } } /** * @title ERC721DutchAuction * @dev Dutch auction / Decreasing clock auction for ERC721 tokens. */ contract ERC721DutchAuction is Ownable, Pausable { /* ======== STRUCTS/ENUMS ======== */ // Represents an auction of an ERC721 token. struct Auction { // Current owner of the ERC721 token. address seller; // Price (in wei) at beginning of auction. uint128 startingPrice; // Price (in wei) at end of auction. uint128 endingPrice; // Duration (in seconds) of auction. uint64 duration; // Time when auction started. // NOTE: 0 if this auction has been concluded. uint64 startedAt; } /* ======== CONTRACTS ======== */ // Reference to contract tracking ERC721 token ownership. ERC721 public nonFungibleContract; /* ======== STATE VARIABLES ======== */ // Cut owner takes on each auction, measured in basis points (1/100 of a percent). // Values 0-10,000 map to 0%-100% uint public ownerCut; // Map from token ID to their corresponding auction. mapping (uint => Auction) tokenIdToAuction; /* ======== EVENTS ======== */ event AuctionCreated(uint timestamp, address indexed seller, uint indexed tokenId, uint startingPrice, uint endingPrice, uint duration); event AuctionSuccessful(uint timestamp, address indexed seller, uint indexed tokenId, uint totalPrice, address winner); event AuctionCancelled(uint timestamp, address indexed seller, uint indexed tokenId); /** * @dev Constructor creates a reference to the ERC721 token ownership contract and verifies the owner cut is in the valid range. * @param _tokenAddress - address of a deployed contract implementing the Nonfungible Interface. * @param _ownerCut - percent cut the owner takes on each auction, must be between 0-10,000. */ function ERC721DutchAuction(address _tokenAddress, uint _ownerCut) public { require(_ownerCut <= 10000); nonFungibleContract = ERC721(_tokenAddress); ownerCut = _ownerCut; } /* ======== PUBLIC/EXTERNAL FUNCTIONS ======== */ /** * @dev Bids on an open auction, completing the auction and transferring * ownership of the token if enough Ether is supplied. * @param _tokenId - ID of token to bid on. */ function bid(uint _tokenId) whenNotPaused external payable { // _bid will throw if the bid or funds transfer fails. _bid(_tokenId, msg.value); // Transfers the token owned by this contract to another address. It will throw if transfer fails. nonFungibleContract.transfer(msg.sender, _tokenId); } /** * @dev Cancels an auction that hasn't been won yet. Returns the token to original owner. * @notice This is a state-modifying function that can be called while the contract is paused. * @param _tokenId - ID of token on auction */ function cancelAuction(uint _tokenId) external { Auction storage auction = tokenIdToAuction[_tokenId]; require(_isOnAuction(auction)); address seller = auction.seller; require(msg.sender == seller); _cancelAuction(_tokenId, seller); } /** * @dev Cancels an auction when the contract is paused. * Only the owner may do this, and tokens are returned to * the seller. This should only be used in emergencies. * @param _tokenId - ID of the token on auction to cancel. */ function cancelAuctionWhenPaused(uint _tokenId) whenPaused onlyOwner external { Auction storage auction = tokenIdToAuction[_tokenId]; require(_isOnAuction(auction)); _cancelAuction(_tokenId, auction.seller); } /** * @dev Remove all Ether from the contract, which is the owner's cuts * as well as any Ether sent directly to the contract address. */ function withdrawBalance() onlyOwner external { msg.sender.transfer(this.balance); } /** * @dev Returns auction info for an token on auction. * @param _tokenId - ID of token on auction. */ function getAuction(uint _tokenId) external view returns ( address seller, uint startingPrice, uint endingPrice, uint duration, uint startedAt ) { Auction storage auction = tokenIdToAuction[_tokenId]; require(_isOnAuction(auction)); return ( auction.seller, auction.startingPrice, auction.endingPrice, auction.duration, auction.startedAt ); } /** * @dev Returns the current price of an auction. * @param _tokenId - ID of the token price we are checking. */ function getCurrentPrice(uint _tokenId) external view returns (uint) { Auction storage auction = tokenIdToAuction[_tokenId]; require(_isOnAuction(auction)); return _computeCurrentPrice(auction); } /* ======== INTERNAL/PRIVATE FUNCTIONS ======== */ /** * @dev Creates and begins a new auction. Perform all the checkings necessary. * @param _tokenId - ID of token to auction, sender must be owner. * @param _startingPrice - Price of item (in wei) at beginning of auction. * @param _endingPrice - Price of item (in wei) at end of auction. * @param _duration - Length of time to move between starting * price and ending price (in seconds). * @param _seller - Seller, if not the message sender */ function _createAuction( uint _tokenId, uint _startingPrice, uint _endingPrice, uint _duration, address _seller ) internal { // Sanity check that no inputs overflow how many bits we've allocated to store them in the auction struct. require(_startingPrice == uint(uint128(_startingPrice))); require(_endingPrice == uint(uint128(_endingPrice))); require(_duration == uint(uint64(_duration))); // If the token is already on any auction, this will throw // because it will be owned by the auction contract. require(nonFungibleContract.ownerOf(_tokenId) == msg.sender); // Throw if the _endingPrice is larger than _startingPrice. require(_startingPrice >= _endingPrice); // Require that all auctions have a duration of at least one minute. require(_duration >= 1 minutes); // Transfer the token from its owner to this contract. It will throw if transfer fails. nonFungibleContract.transferFrom(msg.sender, this, _tokenId); Auction memory auction = Auction( _seller, uint128(_startingPrice), uint128(_endingPrice), uint64(_duration), uint64(now) ); _addAuction(_tokenId, auction); } /** * @dev Adds an auction to the list of open auctions. Also fires the * AuctionCreated event. * @param _tokenId The ID of the token to be put on auction. * @param _auction Auction to add. */ function _addAuction(uint _tokenId, Auction _auction) internal { tokenIdToAuction[_tokenId] = _auction; AuctionCreated( now, _auction.seller, _tokenId, _auction.startingPrice, _auction.endingPrice, _auction.duration ); } /** * @dev Computes the price and transfers winnings. * Does NOT transfer ownership of token. */ function _bid(uint _tokenId, uint _bidAmount) internal returns (uint) { // Get a reference to the auction struct Auction storage auction = tokenIdToAuction[_tokenId]; // Explicitly check that this auction is currently live. // (Because of how Ethereum mappings work, we can't just count // on the lookup above failing. An invalid _tokenId will just // return an auction object that is all zeros.) require(_isOnAuction(auction)); // Check that the bid is greater than or equal to the current price uint price = _computeCurrentPrice(auction); require(_bidAmount >= price); // Grab a reference to the seller before the auction struct // gets deleted. address seller = auction.seller; // The bid is good! Remove the auction before sending the fees // to the sender so we can't have a reentrancy attack. _removeAuction(_tokenId); // Transfer proceeds to seller (if there are any!) if (price > 0) { // Calculate the auctioneer's cut. uint auctioneerCut = price * ownerCut / 10000; uint sellerProceeds = price - auctioneerCut; 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. uint 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(now, seller, _tokenId, price, msg.sender); return price; } /** * @dev Cancels an auction unconditionally. */ function _cancelAuction(uint _tokenId, address _seller) internal { _removeAuction(_tokenId); // Transfers the token owned by this contract to its original owner. It will throw if transfer fails. nonFungibleContract.transfer(_seller, _tokenId); AuctionCancelled(now, _seller, _tokenId); } /** * @dev Removes an auction from the list of open auctions. * @param _tokenId - ID of token on auction. */ function _removeAuction(uint _tokenId) internal { delete tokenIdToAuction[_tokenId]; } /** * @dev Returns current price of an token on auction. Broken into two * functions (this one, that computes the duration from the auction * structure, and the other that does the price computation) so we * can easily test that the price computation works correctly. */ function _computeCurrentPrice(Auction storage _auction) internal view returns (uint) { uint secondsPassed = 0; // A bit of insurance against negative values (or wraparound). // Probably not necessary (since Ethereum guarnatees that the // now variable doesn't ever go backwards). if (now > _auction.startedAt) { secondsPassed = now - _auction.startedAt; } if (secondsPassed >= _auction.duration) { // We've reached the end of the dynamic pricing portion // of the auction, just return the end price. return _auction.endingPrice; } else { // Starting price can be higher than ending price (and often is!), so // this delta can be negative. int totalPriceChange = int(_auction.endingPrice) - int(_auction.startingPrice); // This multiplication can't overflow, _secondsPassed will easily fit within // 64-bits, and totalPriceChange will easily fit within 128-bits, their product // will always fit within 256-bits. int currentPriceChange = totalPriceChange * int(secondsPassed) / int(_auction.duration); // currentPriceChange can be negative, but if so, will have a magnitude // less that startingPrice. Thus, this result will always end up positive. int currentPrice = int(_auction.startingPrice) + currentPriceChange; return uint(currentPrice); } } /* ======== MODIFIERS ======== */ /** * @dev Returns true if the token is on auction. * @param _auction - Auction to check. */ function _isOnAuction(Auction storage _auction) internal view returns (bool) { return (_auction.startedAt > 0); } } contract HeroTokenAuction is HeroToken, ERC721DutchAuction { function HeroTokenAuction(uint _ownerCut) ERC721DutchAuction(this, _ownerCut) public { } /** * @dev Creates and begins a new auction. * @param _tokenId - ID of token to auction, sender must be owner. * @param _startingPrice - Price of item (in wei) at beginning of auction. * @param _endingPrice - Price of item (in wei) at end of auction. * @param _duration - Length of time to move between starting price and ending price (in seconds). */ function createAuction( uint _tokenId, uint _startingPrice, uint _endingPrice, uint _duration ) whenNotPaused external { _approve(_tokenId, this); // This will perform all the checkings necessary. _createAuction(_tokenId, _startingPrice, _endingPrice, _duration, msg.sender); } }