ETH Price: $2,041.94 (+5.84%)

Transaction Decoder

Block:
5331972 at Mar-27-2018 03:55:23 PM +UTC
Transaction Fee:
0.000129518 ETH $0.26
Gas Used:
64,759 Gas / 2 Gwei

Emitted Events:

75 Fishbank.Attack( attacker=1128, victim=6333, winner=1128, weight=6, ap=432000, vp=103500, random=522010 )

Account State Difference:

  Address   Before After State Difference Code
0x92cC4237...570504e75
0.021306830597 Eth
Nonce: 51
0.021177312597 Eth
Nonce: 52
0.000129518
0xA98Ad92a...e638bc895
(MiningPoolHub: Old Address)
26,725.747608573009735244 Eth26,725.747738091009735244 Eth0.000129518

Execution Trace

Fishbank.attack( _attacker=1128, _victim=6333 )
  • FishbankUtils.getCooldown( speed=75 ) => ( 1522169123 )
    File 1 of 2: Fishbank
    pragma solidity ^0.4.18;
    
    
    /**
     * @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) public onlyOwner {
            require(newOwner != address(0));
            OwnershipTransferred(owner, newOwner);
            owner = newOwner;
        }
    
    }
    
    
    
    
    
    
    
    
    contract Beneficiary is Ownable {
    
        address public beneficiary;
    
        function Beneficiary() public {
            beneficiary = msg.sender;
        }
    
        function setBeneficiary(address _beneficiary) onlyOwner public {
            beneficiary = _beneficiary;
        }
    
    
    }
    
    
    
    contract ChestsStore is Beneficiary {
    
    
        struct chestProduct {
            uint256 price; // Price in wei
            bool isLimited; // is limited sale chest
            uint32 limit; // Sell limit
            uint16 boosters; // count of boosters
            uint24 raiseChance;// in 1/10 of percent
            uint24 raiseStrength;// in 1/10 of percent for params or minutes for timebased boosters
            uint8 onlyBoosterType;//If set chest will produce only this type
            uint8 onlyBoosterStrength;
        }
    
    
        chestProduct[255] public chestProducts;
        FishbankChests chests;
    
    
        function ChestsStore(address _chests) public {
            chests = FishbankChests(_chests);
            //set chests to this address
        }
    
        function initChestsStore() public onlyOwner {
            // Create basic chests types
            setChestProduct(1, 0, 1, false, 0, 0, 0, 0, 0);
            setChestProduct(2, 15 finney, 3, false, 0, 0, 0, 0, 0);
            setChestProduct(3, 20 finney, 5, false, 0, 0, 0, 0, 0);
        }
    
        function setChestProduct(uint16 chestId, uint256 price, uint16 boosters, bool isLimited, uint32 limit, uint24 raiseChance, uint24 raiseStrength, uint8 onlyBoosterType, uint8 onlyBoosterStrength) onlyOwner public {
            chestProduct storage newProduct = chestProducts[chestId];
            newProduct.price = price;
            newProduct.boosters = boosters;
            newProduct.isLimited = isLimited;
            newProduct.limit = limit;
            newProduct.raiseChance = raiseChance;
            newProduct.raiseStrength = raiseStrength;
            newProduct.onlyBoosterType = onlyBoosterType;
            newProduct.onlyBoosterStrength = onlyBoosterStrength;
        }
    
        function setChestPrice(uint16 chestId, uint256 price) onlyOwner public {
            chestProducts[chestId].price = price;
        }
    
        function buyChest(uint16 _chestId) payable public {
            chestProduct memory tmpChestProduct = chestProducts[_chestId];
    
            require(tmpChestProduct.price > 0);
            // only chests with price
            require(msg.value >= tmpChestProduct.price);
            //check if enough ether is send
            require(!tmpChestProduct.isLimited || tmpChestProduct.limit > 0);
            //check limits if they exists
    
            chests.mintChest(msg.sender, tmpChestProduct.boosters, tmpChestProduct.raiseStrength, tmpChestProduct.raiseChance, tmpChestProduct.onlyBoosterType, tmpChestProduct.onlyBoosterStrength);
    
            if (msg.value > chestProducts[_chestId].price) {//send to much ether send some back
                msg.sender.transfer(msg.value - chestProducts[_chestId].price);
            }
    
            beneficiary.transfer(chestProducts[_chestId].price);
            //send paid eth to beneficiary
    
        }
    
    
    }
    
    
    
    
    
    contract FishbankBoosters is Ownable {
    
        struct Booster {
            address owner;
            uint32 duration;
            uint8 boosterType;
            uint24 raiseValue;
            uint8 strength;
            uint32 amount;
        }
    
        Booster[] public boosters;
        bool public implementsERC721 = true;
        string public name = "Fishbank Boosters";
        string public symbol = "FISHB";
        mapping(uint256 => address) public approved;
        mapping(address => uint256) public balances;
        address public fishbank;
        address public chests;
        address public auction;
    
        modifier onlyBoosterOwner(uint256 _tokenId) {
            require(boosters[_tokenId].owner == msg.sender);
            _;
        }
    
        modifier onlyChest() {
            require(chests == msg.sender);
            _;
        }
    
        function FishbankBoosters() public {
            //nothing yet
        }
    
        //mints the boosters can only be called by owner. could be a smart contract
        function mintBooster(address _owner, uint32 _duration, uint8 _type, uint8 _strength, uint32 _amount, uint24 _raiseValue) onlyChest public {
            boosters.length ++;
    
            Booster storage tempBooster = boosters[boosters.length - 1];
    
            tempBooster.owner = _owner;
            tempBooster.duration = _duration;
            tempBooster.boosterType = _type;
            tempBooster.strength = _strength;
            tempBooster.amount = _amount;
            tempBooster.raiseValue = _raiseValue;
    
            Transfer(address(0), _owner, boosters.length - 1);
        }
    
        function setFishbank(address _fishbank) onlyOwner public {
            fishbank = _fishbank;
        }
    
        function setChests(address _chests) onlyOwner public {
            if (chests != address(0)) {
                revert();
            }
            chests = _chests;
        }
    
        function setAuction(address _auction) onlyOwner public {
            auction = _auction;
        }
    
        function getBoosterType(uint256 _tokenId) view public returns (uint8 boosterType) {
            boosterType = boosters[_tokenId].boosterType;
        }
    
        function getBoosterAmount(uint256 _tokenId) view public returns (uint32 boosterAmount) {
            boosterAmount = boosters[_tokenId].amount;
        }
    
        function getBoosterDuration(uint256 _tokenId) view public returns (uint32) {
            if (boosters[_tokenId].boosterType == 4 || boosters[_tokenId].boosterType == 2) {
                return boosters[_tokenId].duration + boosters[_tokenId].raiseValue * 60;
            }
            return boosters[_tokenId].duration;
        }
    
        function getBoosterStrength(uint256 _tokenId) view public returns (uint8 strength) {
            strength = boosters[_tokenId].strength;
        }
    
        function getBoosterRaiseValue(uint256 _tokenId) view public returns (uint24 raiseValue) {
            raiseValue = boosters[_tokenId].raiseValue;
        }
    
        //ERC721 functionality
        //could split this to a different contract but doesn't make it easier to read
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    
        function totalSupply() public view returns (uint256 total) {
            total = boosters.length;
        }
    
        function balanceOf(address _owner) public view returns (uint256 balance){
            balance = balances[_owner];
        }
    
        function ownerOf(uint256 _tokenId) public view returns (address owner){
            owner = boosters[_tokenId].owner;
        }
    
        function _transfer(address _from, address _to, uint256 _tokenId) internal {
            require(boosters[_tokenId].owner == _from);
            //can only transfer if previous owner equals from
            boosters[_tokenId].owner = _to;
            approved[_tokenId] = address(0);
            //reset approved of fish on every transfer
            balances[_from] -= 1;
            //underflow can only happen on 0x
            balances[_to] += 1;
            //overflows only with very very large amounts of fish
            Transfer(_from, _to, _tokenId);
        }
    
        function transfer(address _to, uint256 _tokenId) public
        onlyBoosterOwner(_tokenId) //check if msg.sender is the owner of this fish
        returns (bool)
        {
            _transfer(msg.sender, _to, _tokenId);
            //after master modifier invoke internal transfer
            return true;
        }
    
        function approve(address _to, uint256 _tokenId) public
        onlyBoosterOwner(_tokenId)
        {
            approved[_tokenId] = _to;
            Approval(msg.sender, _to, _tokenId);
        }
    
        function transferFrom(address _from, address _to, uint256 _tokenId) public returns (bool) {
            require(approved[_tokenId] == msg.sender || msg.sender == fishbank || msg.sender == auction);
            //require msg.sender to be approved for this token or to be the fishbank contract
            _transfer(_from, _to, _tokenId);
            //handles event, balances and approval reset
            return true;
        }
    
    
        function takeOwnership(uint256 _tokenId) public {
            require(approved[_tokenId] == msg.sender);
            _transfer(ownerOf(_tokenId), msg.sender, _tokenId);
        }
    
    
    }
    
    
    
    
    
    
    contract FishbankChests is Ownable {
    
        struct Chest {
            address owner;
            uint16 boosters;
            uint16 chestType;
            uint24 raiseChance;//Increace chance to catch bigger chest (1 = 1:10000)
            uint8 onlySpecificType;
            uint8 onlySpecificStrength;
            uint24 raiseStrength;
        }
    
        Chest[] public chests;
        FishbankBoosters public boosterContract;
        mapping(uint256 => address) public approved;
        mapping(address => uint256) public balances;
        mapping(address => bool) public minters;
    
        modifier onlyChestOwner(uint256 _tokenId) {
            require(chests[_tokenId].owner == msg.sender);
            _;
        }
    
        modifier onlyMinters() {
            require(minters[msg.sender]);
            _;
        }
    
        function FishbankChests(address _boosterAddress) public {
            boosterContract = FishbankBoosters(_boosterAddress);
        }
    
        function addMinter(address _minter) onlyOwner public {
            minters[_minter] = true;
        }
    
        function removeMinter(address _minter) onlyOwner public {
            minters[_minter] = false;
        }
    
        //create a chest
    
        function mintChest(address _owner, uint16 _boosters, uint24 _raiseStrength, uint24 _raiseChance, uint8 _onlySpecificType, uint8 _onlySpecificStrength) onlyMinters public {
    
            chests.length++;
            chests[chests.length - 1].owner = _owner;
            chests[chests.length - 1].boosters = _boosters;
            chests[chests.length - 1].raiseStrength = _raiseStrength;
            chests[chests.length - 1].raiseChance = _raiseChance;
            chests[chests.length - 1].onlySpecificType = _onlySpecificType;
            chests[chests.length - 1].onlySpecificStrength = _onlySpecificStrength;
            Transfer(address(0), _owner, chests.length - 1);
        }
    
        function convertChest(uint256 _tokenId) onlyChestOwner(_tokenId) public {
    
            Chest memory chest = chests[_tokenId];
            uint16 numberOfBoosters = chest.boosters;
    
            if (chest.onlySpecificType != 0) {//Specific boosters
                if (chest.onlySpecificType == 1 || chest.onlySpecificType == 3) {
                    boosterContract.mintBooster(msg.sender, 2 days, chest.onlySpecificType, chest.onlySpecificStrength, chest.boosters, chest.raiseStrength);
                } else if (chest.onlySpecificType == 5) {//Instant attack
                    boosterContract.mintBooster(msg.sender, 0, 5, 1, chest.boosters, chest.raiseStrength);
                } else if (chest.onlySpecificType == 2) {//Freeze
                    uint32 freezeTime = 7 days;
                    if (chest.onlySpecificStrength == 2) {
                        freezeTime = 14 days;
                    } else if (chest.onlySpecificStrength == 3) {
                        freezeTime = 30 days;
                    }
                    boosterContract.mintBooster(msg.sender, freezeTime, 5, chest.onlySpecificType, chest.boosters, chest.raiseStrength);
                } else if (chest.onlySpecificType == 4) {//Watch
                    uint32 watchTime = 12 hours;
                    if (chest.onlySpecificStrength == 2) {
                        watchTime = 48 hours;
                    } else if (chest.onlySpecificStrength == 3) {
                        watchTime = 3 days;
                    }
                    boosterContract.mintBooster(msg.sender, watchTime, 4, chest.onlySpecificStrength, chest.boosters, chest.raiseStrength);
                }
    
            } else {//Regular chest
    
                for (uint8 i = 0; i < numberOfBoosters; i ++) {
                    uint24 random = uint16(keccak256(block.coinbase, block.blockhash(block.number - 1), i, chests.length)) % 1000
                    - chest.raiseChance;
                    //get random 0 - 9999 minus raiseChance
    
                    if (random > 850) {
                        boosterContract.mintBooster(msg.sender, 2 days, 1, 1, 1, chest.raiseStrength); //Small Agility Booster
                    } else if (random > 700) {
                        boosterContract.mintBooster(msg.sender, 7 days, 2, 1, 1, chest.raiseStrength); //Small Freezer
                    } else if (random > 550) {
                        boosterContract.mintBooster(msg.sender, 2 days, 3, 1, 1, chest.raiseStrength); //Small Power Booster
                    } else if (random > 400) {
                        boosterContract.mintBooster(msg.sender, 12 hours, 4, 1, 1, chest.raiseStrength); //Tiny Watch
                    } else if (random > 325) {
                        boosterContract.mintBooster(msg.sender, 48 hours, 4, 2, 1, chest.raiseStrength); //Small Watch
                    } else if (random > 250) {
                        boosterContract.mintBooster(msg.sender, 2 days, 1, 2, 1, chest.raiseStrength); //Mid Agility Booster
                    } else if (random > 175) {
                        boosterContract.mintBooster(msg.sender, 14 days, 2, 2, 1, chest.raiseStrength); //Mid Freezer
                    } else if (random > 100) {
                        boosterContract.mintBooster(msg.sender, 2 days, 3, 2, 1, chest.raiseStrength); //Mid Power Booster
                    } else if (random > 80) {
                        boosterContract.mintBooster(msg.sender, 2 days, 1, 3, 1, chest.raiseStrength); //Big Agility Booster
                    } else if (random > 60) {
                        boosterContract.mintBooster(msg.sender, 30 days, 2, 3, 1, chest.raiseStrength); //Big Freezer
                    } else if (random > 40) {
                        boosterContract.mintBooster(msg.sender, 2 days, 3, 3, 1, chest.raiseStrength); //Big Power Booster
                    } else if (random > 20) {
                        boosterContract.mintBooster(msg.sender, 0, 5, 1, 1, 0); //Instant Attack
                    } else {
                        boosterContract.mintBooster(msg.sender, 3 days, 4, 3, 1, 0); //Gold Watch
                    }
                }
            }
    
            _transfer(msg.sender, address(0), _tokenId); //burn chest
        }
    
        //ERC721 functionality
        //could split this to a different contract but doesn't make it easier to read
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    
        function totalSupply() public view returns (uint256 total) {
            total = chests.length;
        }
    
        function balanceOf(address _owner) public view returns (uint256 balance){
            balance = balances[_owner];
        }
    
        function ownerOf(uint256 _tokenId) public view returns (address owner){
            owner = chests[_tokenId].owner;
        }
    
        function _transfer(address _from, address _to, uint256 _tokenId) internal {
            require(chests[_tokenId].owner == _from); //can only transfer if previous owner equals from
            chests[_tokenId].owner = _to;
            approved[_tokenId] = address(0); //reset approved of fish on every transfer
            balances[_from] -= 1; //underflow can only happen on 0x
            balances[_to] += 1; //overflows only with very very large amounts of fish
            Transfer(_from, _to, _tokenId);
        }
    
        function transfer(address _to, uint256 _tokenId) public
        onlyChestOwner(_tokenId) //check if msg.sender is the owner of this fish
        returns (bool)
        {
            _transfer(msg.sender, _to, _tokenId);
            //after master modifier invoke internal transfer
            return true;
        }
    
        function approve(address _to, uint256 _tokenId) public
        onlyChestOwner(_tokenId)
        {
            approved[_tokenId] = _to;
            Approval(msg.sender, _to, _tokenId);
        }
    
        function transferFrom(address _from, address _to, uint256 _tokenId) public returns (bool) {
            require(approved[_tokenId] == msg.sender);
            //require msg.sender to be approved for this token
            _transfer(_from, _to, _tokenId);
            //handles event, balances and approval reset
            return true;
        }
    
    }
    
    
    
    
    
    contract FishbankUtils is Ownable {
    
        uint32[100] cooldowns = [
            720 minutes, 720 minutes, 720 minutes, 720 minutes, 720 minutes, //1-5
            660 minutes, 660 minutes, 660 minutes, 660 minutes, 660 minutes, //6-10
            600 minutes, 600 minutes, 600 minutes, 600 minutes, 600 minutes, //11-15
            540 minutes, 540 minutes, 540 minutes, 540 minutes, 540 minutes, //16-20
            480 minutes, 480 minutes, 480 minutes, 480 minutes, 480 minutes, //21-25
            420 minutes, 420 minutes, 420 minutes, 420 minutes, 420 minutes, //26-30
            360 minutes, 360 minutes, 360 minutes, 360 minutes, 360 minutes, //31-35
            300 minutes, 300 minutes, 300 minutes, 300 minutes, 300 minutes, //36-40
            240 minutes, 240 minutes, 240 minutes, 240 minutes, 240 minutes, //41-45
            180 minutes, 180 minutes, 180 minutes, 180 minutes, 180 minutes, //46-50
            120 minutes, 120 minutes, 120 minutes, 120 minutes, 120 minutes, //51-55
            90 minutes,  90 minutes,  90 minutes,  90 minutes,  90 minutes,  //56-60
            75 minutes,  75 minutes,  75 minutes,  75 minutes,  75 minutes,  //61-65
            60 minutes,  60 minutes,  60 minutes,  60 minutes,  60 minutes,  //66-70
            50 minutes,  50 minutes,  50 minutes,  50 minutes,  50 minutes,  //71-75
            40 minutes,  40 minutes,  40 minutes,  40 minutes,  40 minutes,  //76-80
            30 minutes,  30 minutes,  30 minutes,  30 minutes,  30 minutes,  //81-85
            20 minutes,  20 minutes,  20 minutes,  20 minutes,  20 minutes,  //86-90
            10 minutes,  10 minutes,  10 minutes,  10 minutes,  10 minutes,  //91-95
            5 minutes,   5 minutes,   5 minutes,   5 minutes,   5 minutes    //96-100
        ];
    
    
        function setCooldowns(uint32[100] _cooldowns) onlyOwner public {
            cooldowns = _cooldowns;
        }
    
        function getFishParams(uint256 hashSeed1, uint256 hashSeed2, uint256 fishesLength, address coinbase) external pure returns (uint32[4]) {
    
            bytes32[5] memory hashSeeds;
            hashSeeds[0] = keccak256(hashSeed1 ^ hashSeed2); //xor both seed from owner and user so no one can cheat
            hashSeeds[1] = keccak256(hashSeeds[0], fishesLength);
            hashSeeds[2] = keccak256(hashSeeds[1], coinbase);
            hashSeeds[3] = keccak256(hashSeeds[2], coinbase, fishesLength);
            hashSeeds[4] = keccak256(hashSeeds[1], hashSeeds[2], hashSeeds[0]);
    
            uint24[6] memory seeds = [
                uint24(uint(hashSeeds[3]) % 10e6 + 1), //whale chance
                uint24(uint(hashSeeds[0]) % 420 + 1), //power
                uint24(uint(hashSeeds[1]) % 420 + 1), //agility
                uint24(uint(hashSeeds[2]) % 150 + 1), //speed
                uint24(uint(hashSeeds[4]) % 16 + 1), //whale type
                uint24(uint(hashSeeds[4]) % 5000 + 1) //rarity
            ];
    
            uint32[4] memory fishParams;
    
            if (seeds[0] == 1000000) {//This is a whale 1:1 000 000 chance
    
                if (seeds[4] == 1) {//Orca
                    fishParams = [140 + uint8(seeds[1] / 42), 140 + uint8(seeds[2] / 42), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 140) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 140) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                } else if (seeds[4] < 4) {//Blue whale
                    fishParams = [130 + uint8(seeds[1] / 42), 130 + uint8(seeds[2] / 42), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 130) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 130) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                } else {//Cachalot
                    fishParams = [115 + uint8(seeds[1] / 28), 115 + uint8(seeds[2] / 28), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 115) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 115) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                }
            } else {
                if (seeds[5] == 5000) {//Legendary
                    fishParams = [85 + uint8(seeds[1] / 14), 85 + uint8(seeds[2] / 14), uint8(50 + seeds[3] / 3), uint32(1000)];
                    if(fishParams[0] == 85) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 85) {
                        fishParams[1]++;
                    }
                } else if (seeds[5] > 4899) {//Epic
                    fishParams = [50 + uint8(seeds[1] / 12), 50 + uint8(seeds[2] / 12), uint8(25 + seeds[3] / 2), uint32(300)];
                    if(fishParams[0] == 50) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 50) {
                        fishParams[1]++;
                    }
    
                } else if (seeds[5] > 4000) {//Rare
                    fishParams = [20 + uint8(seeds[1] / 14), 20 + uint8(seeds[2] / 14), uint8(25 + seeds[3] / 3), uint32(100)];
                    if(fishParams[0] == 20) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 20) {
                        fishParams[1]++;
                    }
                } else {//Common
                    fishParams = [uint8(seeds[1] / 21), uint8(seeds[2] / 21), uint8(seeds[3] / 3), uint32(36)];
                    if (fishParams[0] == 0) {
                        fishParams[0] = 1;
                    }
                    if (fishParams[1] == 0) {
                        fishParams[1] = 1;
                    }
                    if (fishParams[2] == 0) {
                        fishParams[2] = 1;
                    }
                }
            }
    
            return fishParams;
        }
    
        function getCooldown(uint16 speed) external view returns (uint64){
            return uint64(now + cooldowns[speed - 1]);
        }
    
        //Ceiling function for fish generator
        function ceil(uint base, uint divider) internal pure returns (uint) {
            return base / divider + ((base % divider > 0) ? 1 : 0);
        }
    }
    
    
    
    
    /// @title Auction contract for any type of erc721 token
    /// @author Fishbank
    
    contract ERC721 {
    
        function implementsERC721() public pure returns (bool);
    
        function totalSupply() public view returns (uint256 total);
    
        function balanceOf(address _owner) public view returns (uint256 balance);
    
        function ownerOf(uint256 _tokenId) public view returns (address owner);
    
        function approve(address _to, uint256 _tokenId) public;
    
        function transferFrom(address _from, address _to, uint256 _tokenId) public returns (bool);
    
        function transfer(address _to, uint256 _tokenId) public returns (bool);
    
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    
        // Optional
        // function name() public view returns (string name);
        // function symbol() public view returns (string symbol);
        // function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId);
        // function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
    }
    
    
    contract ERC721Auction is Beneficiary {
    
        struct Auction {
            address seller;
            uint256 tokenId;
            uint64 auctionBegin;
            uint64 auctionEnd;
            uint256 startPrice;
            uint256 endPrice;
        }
    
        uint32 public auctionDuration = 7 days;
    
        ERC721 public ERC721Contract;
        uint256 public fee = 45000; //in 1 10000th of a percent so 4.5% at the start
        uint256 constant FEE_DIVIDER = 1000000;
        mapping(uint256 => Auction) public auctions;
    
        event AuctionWon(uint256 indexed tokenId, address indexed winner, address indexed seller, uint256 price);
    
        event AuctionStarted(uint256 indexed tokenId, address indexed seller);
    
        event AuctionFinalized(uint256 indexed tokenId, address indexed seller);
    
    
        function startAuction(uint256 _tokenId, uint256 _startPrice, uint256 _endPrice) external {
            require(ERC721Contract.transferFrom(msg.sender, address(this), _tokenId));
            //Prices must be in range from 0.01 Eth and 10 000 Eth
            require(_startPrice <= 10000 ether && _endPrice <= 10000 ether);
            require(_startPrice >= (1 ether / 100) && _endPrice >= (1 ether / 100));
    
            Auction memory auction;
    
            auction.seller = msg.sender;
            auction.tokenId = _tokenId;
            auction.auctionBegin = uint64(now);
            auction.auctionEnd = uint64(now + auctionDuration);
            require(auction.auctionEnd > auction.auctionBegin);
            auction.startPrice = _startPrice;
            auction.endPrice = _endPrice;
    
            auctions[_tokenId] = auction;
    
            AuctionStarted(_tokenId, msg.sender);
        }
    
    
        function buyAuction(uint256 _tokenId) payable external {
            Auction storage auction = auctions[_tokenId];
    
            uint256 price = calculateBid(_tokenId);
            uint256 totalFee = price * fee / FEE_DIVIDER; //safe math needed?
    
            require(price <= msg.value); //revert if not enough ether send
    
            if (price != msg.value) {//send back to much eth
                msg.sender.transfer(msg.value - price);
            }
    
            beneficiary.transfer(totalFee);
    
            auction.seller.transfer(price - totalFee);
    
            if (!ERC721Contract.transfer(msg.sender, _tokenId)) {
                revert();
                //can't complete transfer if this fails
            }
    
            AuctionWon(_tokenId, msg.sender, auction.seller, price);
    
            delete auctions[_tokenId];
            //deletes auction
        }
    
        function saveToken(uint256 _tokenId) external {
            require(auctions[_tokenId].auctionEnd < now);
            //auction must have ended
            require(ERC721Contract.transfer(auctions[_tokenId].seller, _tokenId));
            //transfer fish back to seller
    
            AuctionFinalized(_tokenId, auctions[_tokenId].seller);
    
            delete auctions[_tokenId];
            //delete auction
        }
    
        function ERC721Auction(address _ERC721Contract) public {
            ERC721Contract = ERC721(_ERC721Contract);
        }
    
        function setFee(uint256 _fee) onlyOwner public {
            if (_fee > fee) {
                revert(); //fee can only be set to lower value to prevent attacks by owner
            }
            fee = _fee; // all is well set fee
        }
    
        function calculateBid(uint256 _tokenId) public view returns (uint256) {
            Auction storage auction = auctions[_tokenId];
    
            if (now >= auction.auctionEnd) {//if auction ended return auction end price
                return auction.endPrice;
            }
            //get hours passed
            uint256 hoursPassed = (now - auction.auctionBegin) / 1 hours;
            uint256 currentPrice;
            //get total hours
            uint16 totalHours = uint16(auctionDuration /1 hours) - 1;
    
            if (auction.endPrice > auction.startPrice) {
                currentPrice = auction.startPrice + (hoursPassed * (auction.endPrice - auction.startPrice))/ totalHours;
            } else if(auction.endPrice < auction.startPrice) {
                currentPrice = auction.startPrice - (hoursPassed * (auction.startPrice - auction.endPrice))/ totalHours;
            } else {//start and end are the same
                currentPrice = auction.endPrice;
            }
    
            return uint256(currentPrice);
            //return the price at this very moment
        }
    
        /// return token if case when need to redeploy auction contract
        function returnToken(uint256 _tokenId) onlyOwner public {
            require(ERC721Contract.transfer(auctions[_tokenId].seller, _tokenId));
            //transfer fish back to seller
    
            AuctionFinalized(_tokenId, auctions[_tokenId].seller);
    
            delete auctions[_tokenId];
        }
    }
    
    
    /// @title Core contract of fishbank
    /// @author Fishbank
    
    contract Fishbank is ChestsStore {
    
        struct Fish {
            address owner;
            uint8 activeBooster;
            uint64 boostedTill;
            uint8 boosterStrength;
            uint24 boosterRaiseValue;
            uint64 weight;
            uint16 power;
            uint16 agility;
            uint16 speed;
            bytes16 color;
            uint64 canFightAgain;
            uint64 canBeAttackedAgain;
        }
    
        struct FishingAttempt {
            address fisher;
            uint256 feePaid;
            address affiliate;
            uint256 seed;
            uint64 deadline;//till when does the contract owner have time to resolve;
        }
    
        modifier onlyFishOwner(uint256 _tokenId) {
            require(fishes[_tokenId].owner == msg.sender);
            _;
        }
    
        modifier onlyResolver() {
            require(msg.sender == resolver);
            _;
        }
    
        modifier onlyMinter() {
            require(msg.sender == minter);
            _;
        }
    
        Fish[] public fishes;
        address public resolver;
        address public auction;
        address public minter;
        bool public implementsERC721 = true;
        string public name = "Fishbank";
        string public symbol = "FISH";
        bytes32[] public randomHashes;
        uint256 public hashesUsed;
        uint256 public aquariumCost = 1 ether / 100 * 3;//fee for fishing starts at 0.03 ether
        uint256 public resolveTime = 30 minutes;//how long does the contract owner have to resolve hashes
        uint16 public weightLostPartLimit = 5;
        FishbankBoosters public boosters;
        FishbankChests public chests;
        FishbankUtils private utils;
    
    
        mapping(bytes32 => FishingAttempt) public pendingFishing;//attempts that need solving;
    
        mapping(uint256 => address) public approved;
        mapping(address => uint256) public balances;
        mapping(address => bool) public affiliated;
    
        event AquariumFished(
            bytes32 hash,
            address fisher,
            uint256 feePaid
        ); //event broadcated when someone fishes in aqaurium
    
        event AquariumResolved(bytes32 hash, address fisher);
    
        event Attack(
            uint256 attacker,
            uint256 victim,
            uint256 winner,
            uint64 weight,
            uint256 ap, uint256 vp, uint256 random
        );
    
        event BoosterApplied(uint256 tokenId, uint256 boosterId);
    
        /// @notice Constructor of the contract. Sets resolver, beneficiary, boosters and chests
        /// @param _boosters the address of the boosters smart contract
        /// @param _chests the address of the chests smart contract
    
        function Fishbank(address _boosters, address _chests, address _utils) ChestsStore(_chests) public {
    
            resolver = msg.sender;
            beneficiary = msg.sender;
            boosters = FishbankBoosters(_boosters);
            chests = FishbankChests(_chests);
            utils = FishbankUtils(_utils);
        }
    
        /// @notice Mints fishes according to params can only be called by the owner
        /// @param _owner array of addresses the fishes should be owned by
        /// @param _weight array of weights for the fishes
        /// @param _power array of power levels for the fishes
        /// @param _agility array of agility levels for the fishes
        /// @param _speed array of speed levels for the fishes
        /// @param _color array of color params for the fishes
    
        function mintFish(address[] _owner, uint32[] _weight, uint8[] _power, uint8[] _agility, uint8[] _speed, bytes16[] _color) onlyMinter public {
    
            for (uint i = 0; i < _owner.length; i ++) {
                _mintFish(_owner[i], _weight[i], _power[i], _agility[i], _speed[i], _color[i]);
            }
        }
    
        /// @notice Internal method for minting a fish
        /// @param _owner address of owner for the fish
        /// @param _weight weight param for fish
        /// @param _power power param for fish
        /// @param _agility agility param for the fish
        /// @param _speed speed param for the fish
        /// @param _color color param for the fish
    
        function _mintFish(address _owner, uint32 _weight, uint8 _power, uint8 _agility, uint8 _speed, bytes16 _color) internal {
    
            fishes.length += 1;
            uint256 newFishId = fishes.length - 1;
    
            Fish storage newFish = fishes[newFishId];
    
            newFish.owner = _owner;
            newFish.weight = _weight;
            newFish.power = _power;
            newFish.agility = _agility;
            newFish.speed = _speed;
            newFish.color = _color;
    
            balances[_owner] ++;
    
            Transfer(address(0), _owner, newFishId);
        }
    
        function setWeightLostPartLimit(uint8 _weightPart) onlyOwner public {
            weightLostPartLimit = _weightPart;
        }
    
        /// @notice Sets the cost for fishing in the aquarium
        /// @param _fee new fee for fishing in wei
        function setAquariumCost(uint256 _fee) onlyOwner public {
            aquariumCost = _fee;
        }
    
        /// @notice Sets address that resolves hashes for fishing can only be called by the owner
        /// @param _resolver address of the resolver
        function setResolver(address _resolver) onlyOwner public {
            resolver = _resolver;
        }
    
    
        /// @notice Sets the address getting the proceedings from fishing in the aquarium
        /// @param _beneficiary address of the new beneficiary
        function setBeneficiary(address _beneficiary) onlyOwner public {
            beneficiary = _beneficiary;
        }
    
        function setAuction(address _auction) onlyOwner public {
            auction = _auction;
        }
    
        function setBoosters(address _boosters) onlyOwner public {
            boosters = FishbankBoosters(_boosters);
        }
    
        function setMinter(address _minter) onlyOwner public {
            minter = _minter;
        }
    
        function setUtils(address _utils) onlyOwner public {
            utils = FishbankUtils(_utils);
        }
    
        /// batch fishing from 1 to 10 times
        function batchFishAquarium(uint256[] _seeds, address _affiliate) payable public {
            require(_seeds.length > 0 && _seeds.length <= 10);
            require(msg.value >= aquariumCost * _seeds.length);
            //must send enough ether to cover costs
            require(randomHashes.length > hashesUsed + _seeds.length);
            //there needs to be a hash left
    
            if (msg.value > aquariumCost * _seeds.length) {
                msg.sender.transfer(msg.value - aquariumCost * _seeds.length);
                //send to much ether back
            }
    
            for (uint256 i = 0; i < _seeds.length; i ++) {
                _fishAquarium(_seeds[i]);
            }
    
            if(_affiliate != address(0)) {
                pendingFishing[randomHashes[hashesUsed - 1]].affiliate = _affiliate;
            }
        }
    
        function _fishAquarium(uint256 _seed) internal {
            //this loop prevents from using the same hash as another fishing attempt if the owner submits the same hash multiple times
            while (pendingFishing[randomHashes[hashesUsed]].fisher != address(0)) {
                hashesUsed++;
                //increase hashesUsed and try next one
            }
    
            FishingAttempt storage newAttempt = pendingFishing[randomHashes[hashesUsed]];
    
            newAttempt.fisher = msg.sender;
            newAttempt.feePaid = aquariumCost;
            //set the fee paid so it can be returned if the hash doesn't get resolved fast enough
            newAttempt.seed = _seed;
            //sets the seed that gets combined with the random seed of the owner
            newAttempt.deadline = uint64(now + resolveTime);
            //saves deadline after which the fisher can redeem his fishing fee
    
            hashesUsed++;
            //increase hashes used so it cannot be used again
    
            AquariumFished(randomHashes[hashesUsed - 1], msg.sender, aquariumCost);
            //broadcast event
        }
    
        /// @notice Call this to resolve hashes and generate fish/chests
        /// @param _seed seed that corresponds to the hash
        function _resolveAquarium(uint256 _seed) internal {
            bytes32 tempHash = keccak256(_seed);
            FishingAttempt storage tempAttempt = pendingFishing[tempHash];
    
            require(tempAttempt.fisher != address(0));
            //attempt must be set so we look if fisher is set
    
            if (tempAttempt.affiliate != address(0) && !affiliated[tempAttempt.fisher]) {//if affiliate is set
                chests.mintChest(tempAttempt.affiliate, 1, 0, 0, 0, 0);
                //Chest with one random booster
                affiliated[tempAttempt.fisher] = true;
            }
    
            uint32[4] memory fishParams = utils.getFishParams(_seed, tempAttempt.seed, fishes.length, block.coinbase);
    
            _mintFish(tempAttempt.fisher, fishParams[3], uint8(fishParams[0]), uint8(fishParams[1]), uint8(fishParams[2]), bytes16(keccak256(_seed ^ tempAttempt.seed)));
    
            beneficiary.transfer(tempAttempt.feePaid);
            AquariumResolved(tempHash, tempAttempt.fisher);
            //broadcast event
    
            delete pendingFishing[tempHash];
            //delete fishing attempt
        }
    
        /// @notice Batch resolve fishing attempts
        /// @param _seeds array of seeds that correspond to hashes that need resolving
        function batchResolveAquarium(uint256[] _seeds) onlyResolver public {
            for (uint256 i = 0; i < _seeds.length; i ++) {
                _resolveAquarium(_seeds[i]);
            }
        }
    
    
        /// @notice Adds an array of hashes to be used for resolving
        /// @param _hashes array of hashes to add
        function addHash(bytes32[] _hashes) onlyResolver public {
            for (uint i = 0; i < _hashes.length; i ++) {
                randomHashes.push(_hashes[i]);
            }
        }
    
        /// @notice Call this function to attack another fish
        /// @param _attacker ID of fish that is attacking
        /// @param _victim ID of fish to attack
        function attack(uint256 _attacker, uint256 _victim) onlyFishOwner(_attacker) public {
    
            Fish memory attacker = fishes[_attacker];
            Fish memory victim = fishes[_victim];
    
            //check if attacker is sleeping
            if (attacker.activeBooster == 2 && attacker.boostedTill > now) {//if your fish is sleeping auto awake it
                fishes[_attacker].activeBooster = 0;
                attacker.boostedTill = uint64(now);
                //set booster to invalid one so it has no effect
            }
    
            //check if victim has active sleeping booster
            require(!((victim.activeBooster == 2) && victim.boostedTill >= now));
            //cannot attack a sleeping fish
            require(now >= attacker.canFightAgain);
            //check if attacking fish is cooled down
            require(now >= victim.canBeAttackedAgain);
            //check if victim fish can be attacked again
    
    
            if (msg.sender == victim.owner) {
                uint64 weight = attacker.weight < victim.weight ? attacker.weight : victim.weight;
                fishes[_attacker].weight += weight;
                fishes[_victim].weight -= weight;
                fishes[_attacker].canFightAgain = uint64(utils.getCooldown(attacker.speed));
    
                if (fishes[_victim].weight == 0) {
                    _transfer(msg.sender, address(0), _victim);
                    balances[fishes[_victim].owner] --;
                    //burn token
                } else {
                    fishes[_victim].canBeAttackedAgain = uint64(now + 1 hours);
                    //set victim cooldown 1 hour
                }
    
                Attack(_attacker, _victim, _attacker, weight, 0, 0, 0);
                return;
            }
    
            if (victim.weight < 2 || attacker.weight < 2) {
                revert();
                //revert if one of the fish is below fighting weight
            }
    
            uint256 AP = getFightingAmounts(attacker, true);
            // get attacker power
            uint256 VP = getFightingAmounts(victim, false);
            // get victim power
    
            bytes32 randomHash = keccak256(block.coinbase, block.blockhash(block.number - 1), fishes.length);
    
            uint256 max = AP > VP ? AP : VP;
            uint256 attackRange = max * 2;
            uint256 random = uint256(randomHash) % attackRange + 1;
    
            uint64 weightLost;
    
            if (random <= (max + AP - VP)) {
                weightLost = _handleWin(_attacker, _victim);
                Attack(_attacker, _victim, _attacker, weightLost, AP, VP, random);
            } else {
                weightLost = _handleWin(_victim, _attacker);
                Attack(_attacker, _victim, _victim, weightLost, AP, VP, random);
                //broadcast event
            }
    
            fishes[_attacker].canFightAgain = uint64(utils.getCooldown(attacker.speed));
            fishes[_victim].canBeAttackedAgain = uint64(now + 1 hours);
            //set victim cooldown 1 hour
        }
    
        /// @notice Handles lost gained weight after fight
        /// @param _winner the winner of the fight
        /// @param _loser the loser of the fight
        function _handleWin(uint256 _winner, uint256 _loser) internal returns (uint64) {
            Fish storage winner = fishes[_winner];
            Fish storage loser = fishes[_loser];
    
            uint64 fullWeightLost = loser.weight / sqrt(winner.weight);
            uint64 maxWeightLost = loser.weight / weightLostPartLimit;
    
            uint64 weightLost = maxWeightLost < fullWeightLost ? maxWeightLost : fullWeightLost;
    
            if (weightLost < 1) {
                weightLost = 1;
                // Minimum 1
            }
    
            winner.weight += weightLost;
            loser.weight -= weightLost;
    
            return weightLost;
        }
    
        /// @notice get attack and defence from fish
        /// @param _fish is Fish token
        /// @param _is_attacker true if fish is attacker otherwise false
        function getFightingAmounts(Fish _fish, bool _is_attacker) internal view returns (uint256){
            return (getFishPower(_fish) * (_is_attacker ? 60 : 40) + getFishAgility(_fish) * (_is_attacker ? 40 : 60)) * _fish.weight;
        }
    
        /// @notice Apply a booster to a fish
        /// @param _tokenId the fish the booster should be applied to
        /// @param _booster the Id of the booster the token should be applied to
        function applyBooster(uint256 _tokenId, uint256 _booster) onlyFishOwner(_tokenId) public {
            require(msg.sender == boosters.ownerOf(_booster));
            //only owner can do this
            require(boosters.getBoosterAmount(_booster) >= 1);
            Fish storage tempFish = fishes[_tokenId];
            uint8 boosterType = uint8(boosters.getBoosterType(_booster));
    
            if (boosterType == 1 || boosterType == 2 || boosterType == 3) {//if booster is attack or agility or sleep
                tempFish.boosterStrength = boosters.getBoosterStrength(_booster);
                tempFish.activeBooster = boosterType;
                tempFish.boostedTill = boosters.getBoosterDuration(_booster) * boosters.getBoosterAmount(_booster) + uint64(now);
                tempFish.boosterRaiseValue = boosters.getBoosterRaiseValue(_booster);
            }
            else if (boosterType == 4) {//watch booster
                require(tempFish.boostedTill > uint64(now));
                //revert on using watch on booster that has passed;
                tempFish.boosterStrength = boosters.getBoosterStrength(_booster);
                tempFish.boostedTill += boosters.getBoosterDuration(_booster) * boosters.getBoosterAmount(_booster);
                //add time to booster
            }
            else if (boosterType == 5) {//Instant attack
                require(boosters.getBoosterAmount(_booster) == 1);
                //Can apply only one instant attack booster
                tempFish.canFightAgain = 0;
            }
    
            require(boosters.transferFrom(msg.sender, address(0), _booster));
            //burn booster
    
            BoosterApplied(_tokenId, _booster);
        }
    
        /// @notice square root function used for weight gain/loss
        /// @param x uint64 to get square root from
        function sqrt(uint64 x) pure internal returns (uint64 y) {
            uint64 z = (x + 1) / 2;
            y = x;
            while (z < y) {
                y = z;
                z = (x / z + z) / 2;
            }
        }
    
        //utlitiy function for easy testing can be removed later
        function doKeccak256(uint256 _input) pure public returns (bytes32) {
            return keccak256(_input);
        }
    
        function getFishPower(Fish _fish) internal view returns (uint24 power) {
            power = _fish.power;
            if (_fish.activeBooster == 1 && _fish.boostedTill > now) {// check if booster active
                uint24 boosterPower = (10 * _fish.boosterStrength + _fish.boosterRaiseValue + 100) * power / 100 - power;
                if (boosterPower < 1 && _fish.boosterStrength == 1) {
                    power += 1;
                } else if (boosterPower < 3 && _fish.boosterStrength == 2) {
                    power += 3;
                } else if (boosterPower < 5 && _fish.boosterStrength == 3) {
                    power += 5;
                } else {
                    power = boosterPower + power;
                }
            }
        }
    
        function getFishAgility(Fish _fish) internal view returns (uint24 agility) {
            agility = _fish.agility;
            if (_fish.activeBooster == 3 && _fish.boostedTill > now) {// check if booster active
                uint24 boosterPower = (10 * _fish.boosterStrength + _fish.boosterRaiseValue + 100) * agility / 100 - agility;
                if (boosterPower < 1 && _fish.boosterStrength == 1) {
                    agility += 1;
                } else if (boosterPower < 3 && _fish.boosterStrength == 2) {
                    agility += 3;
                } else if (boosterPower < 5 && _fish.boosterStrength == 3) {
                    agility += 5;
                } else {
                    agility = boosterPower + agility;
                }
            }
        }
    
    
        //ERC721 functionality
        //could split this to a different contract but doesn't make it easier to read
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    
        function totalSupply() public view returns (uint256 total) {
            total = fishes.length;
        }
    
        function balanceOf(address _owner) public view returns (uint256 balance){
            balance = balances[_owner];
        }
    
        function ownerOf(uint256 _tokenId) public view returns (address owner){
            owner = fishes[_tokenId].owner;
        }
    
        function _transfer(address _from, address _to, uint256 _tokenId) internal {
            require(fishes[_tokenId].owner == _from);
            //can only transfer if previous owner equals from
            fishes[_tokenId].owner = _to;
            approved[_tokenId] = address(0);
            //reset approved of fish on every transfer
            balances[_from] -= 1;
            //underflow can only happen on 0x
            balances[_to] += 1;
            //overflows only with very very large amounts of fish
            Transfer(_from, _to, _tokenId);
        }
    
        function transfer(address _to, uint256 _tokenId) public
        onlyFishOwner(_tokenId) //check if msg.sender is the owner of this fish
        returns (bool)
        {
            _transfer(msg.sender, _to, _tokenId);
            //after master modifier invoke internal transfer
            return true;
        }
    
        function approve(address _to, uint256 _tokenId) public
        onlyFishOwner(_tokenId)
        {
            approved[_tokenId] = _to;
            Approval(msg.sender, _to, _tokenId);
        }
    
        function transferFrom(address _from, address _to, uint256 _tokenId) public returns (bool) {
            require(approved[_tokenId] == msg.sender || msg.sender == auction);
            Fish storage fish = fishes[_tokenId];
    
            if (msg.sender == auction) {
                fish.activeBooster = 2;
                //Freeze for auction
                fish.boostedTill = uint64(now + 7 days);
                fish.boosterStrength = 1;
            }
            //require msg.sender to be approved for this token
            _transfer(_from, _to, _tokenId);
            //handles event, balances and approval reset
            return true;
        }
    
        function takeOwnership(uint256 _tokenId) public {
            require(approved[_tokenId] == msg.sender);
            _transfer(ownerOf(_tokenId), msg.sender, _tokenId);
        }
    
    }

    File 2 of 2: FishbankUtils
    pragma solidity ^0.4.18;
    
    
    /**
     * @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) public onlyOwner {
            require(newOwner != address(0));
            OwnershipTransferred(owner, newOwner);
            owner = newOwner;
        }
    
    }
    
    
    
    
    contract FishbankUtils is Ownable {
    
        uint32[100] cooldowns = [
            720 minutes, 720 minutes, 720 minutes, 720 minutes, 720 minutes, //1-5
            660 minutes, 660 minutes, 660 minutes, 660 minutes, 660 minutes, //6-10
            600 minutes, 600 minutes, 600 minutes, 600 minutes, 600 minutes, //11-15
            540 minutes, 540 minutes, 540 minutes, 540 minutes, 540 minutes, //16-20
            480 minutes, 480 minutes, 480 minutes, 480 minutes, 480 minutes, //21-25
            420 minutes, 420 minutes, 420 minutes, 420 minutes, 420 minutes, //26-30
            360 minutes, 360 minutes, 360 minutes, 360 minutes, 360 minutes, //31-35
            300 minutes, 300 minutes, 300 minutes, 300 minutes, 300 minutes, //36-40
            240 minutes, 240 minutes, 240 minutes, 240 minutes, 240 minutes, //41-45
            180 minutes, 180 minutes, 180 minutes, 180 minutes, 180 minutes, //46-50
            120 minutes, 120 minutes, 120 minutes, 120 minutes, 120 minutes, //51-55
            90 minutes,  90 minutes,  90 minutes,  90 minutes,  90 minutes,  //56-60
            75 minutes,  75 minutes,  75 minutes,  75 minutes,  75 minutes,  //61-65
            60 minutes,  60 minutes,  60 minutes,  60 minutes,  60 minutes,  //66-70
            50 minutes,  50 minutes,  50 minutes,  50 minutes,  50 minutes,  //71-75
            40 minutes,  40 minutes,  40 minutes,  40 minutes,  40 minutes,  //76-80
            30 minutes,  30 minutes,  30 minutes,  30 minutes,  30 minutes,  //81-85
            20 minutes,  20 minutes,  20 minutes,  20 minutes,  20 minutes,  //86-90
            10 minutes,  10 minutes,  10 minutes,  10 minutes,  10 minutes,  //91-95
            5 minutes,   5 minutes,   5 minutes,   5 minutes,   5 minutes    //96-100
        ];
    
    
        function setCooldowns(uint32[100] _cooldowns) onlyOwner public {
            cooldowns = _cooldowns;
        }
    
        function getFishParams(uint256 hashSeed1, uint256 hashSeed2, uint256 fishesLength, address coinbase) external pure returns (uint32[4]) {
    
            bytes32[5] memory hashSeeds;
            hashSeeds[0] = keccak256(hashSeed1 ^ hashSeed2); //xor both seed from owner and user so no one can cheat
            hashSeeds[1] = keccak256(hashSeeds[0], fishesLength);
            hashSeeds[2] = keccak256(hashSeeds[1], coinbase);
            hashSeeds[3] = keccak256(hashSeeds[2], coinbase, fishesLength);
            hashSeeds[4] = keccak256(hashSeeds[1], hashSeeds[2], hashSeeds[0]);
    
            uint24[6] memory seeds = [
                uint24(uint(hashSeeds[3]) % 10e6 + 1), //whale chance
                uint24(uint(hashSeeds[0]) % 420 + 1), //power
                uint24(uint(hashSeeds[1]) % 420 + 1), //agility
                uint24(uint(hashSeeds[2]) % 150 + 1), //speed
                uint24(uint(hashSeeds[4]) % 16 + 1), //whale type
                uint24(uint(hashSeeds[4]) % 5000 + 1) //rarity
            ];
    
            uint32[4] memory fishParams;
    
            if (seeds[0] == 1000000) {//This is a whale 1:1 000 000 chance
    
                if (seeds[4] == 1) {//Orca
                    fishParams = [140 + uint8(seeds[1] / 42), 140 + uint8(seeds[2] / 42), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 140) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 140) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                } else if (seeds[4] < 4) {//Blue whale
                    fishParams = [130 + uint8(seeds[1] / 42), 130 + uint8(seeds[2] / 42), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 130) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 130) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                } else {//Cachalot
                    fishParams = [115 + uint8(seeds[1] / 28), 115 + uint8(seeds[2] / 28), 75 + uint8(seeds[3] / 6), uint32(500000)];
                    if(fishParams[0] == 115) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 115) {
                        fishParams[1]++;
                    }
                    if(fishParams[2] == 75) {
                        fishParams[2]++;
                    }
                }
            } else {
                if (seeds[5] == 5000) {//Legendary
                    fishParams = [85 + uint8(seeds[1] / 14), 85 + uint8(seeds[2] / 14), uint8(50 + seeds[3] / 3), uint32(1000)];
                    if(fishParams[0] == 85) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 85) {
                        fishParams[1]++;
                    }
                } else if (seeds[5] > 4899) {//Epic
                    fishParams = [50 + uint8(seeds[1] / 12), 50 + uint8(seeds[2] / 12), uint8(25 + seeds[3] / 2), uint32(300)];
                    if(fishParams[0] == 50) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 50) {
                        fishParams[1]++;
                    }
    
                } else if (seeds[5] > 4000) {//Rare
                    fishParams = [20 + uint8(seeds[1] / 14), 20 + uint8(seeds[2] / 14), uint8(25 + seeds[3] / 3), uint32(100)];
                    if(fishParams[0] == 20) {
                        fishParams[0]++;
                    }
                    if(fishParams[1] == 20) {
                        fishParams[1]++;
                    }
                } else {//Common
                    fishParams = [uint8(seeds[1] / 21), uint8(seeds[2] / 21), uint8(seeds[3] / 3), uint32(36)];
                    if (fishParams[0] == 0) {
                        fishParams[0] = 1;
                    }
                    if (fishParams[1] == 0) {
                        fishParams[1] = 1;
                    }
                    if (fishParams[2] == 0) {
                        fishParams[2] = 1;
                    }
                }
            }
    
            return fishParams;
        }
    
        function getCooldown(uint16 speed) external view returns (uint64){
            return uint64(now + cooldowns[speed - 1]);
        }
    
        //Ceiling function for fish generator
        function ceil(uint base, uint divider) internal pure returns (uint) {
            return base / divider + ((base % divider > 0) ? 1 : 0);
        }
    }