GPTLens / data_full /CVE /2018-12885.sol
Aishwarya Solanki
initial commit
ee7776a
/**
*Submitted for verification at Etherscan.io on 2018-05-14
*/
pragma solidity 0.4.23;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Subtracts 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 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 internal contractOwner;
constructor () internal {
if(contractOwner == address(0)){
contractOwner = msg.sender;
}
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == contractOwner);
_;
}
/**
* @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));
contractOwner = newOwner;
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
bool private paused = false;
/**
* @dev Modifier to allow actions only when the contract IS paused
@dev If is paused msg.value is send back
*/
modifier whenNotPaused() {
if(paused == true && msg.value > 0){
msg.sender.transfer(msg.value);
}
require(!paused);
_;
}
/**
* @dev Called by the owner to pause, triggers stopped state
*/
function triggerPause() onlyOwner external {
paused = !paused;
}
}
/// @title A contract for creating new champs and making withdrawals
contract ChampFactory is Pausable{
event NewChamp(uint256 champID, address owner);
using SafeMath for uint; //SafeMath for overflow prevention
/*
* Variables
*/
struct Champ {
uint256 id; //same as position in Champ[]
uint256 attackPower;
uint256 defencePower;
uint256 cooldownTime; //how long does it take to be ready attack again
uint256 readyTime; //if is smaller than block.timestamp champ is ready to fight
uint256 winCount;
uint256 lossCount;
uint256 position; //position in leaderboard. subtract 1 and you got position in leaderboard[]
uint256 price; //selling price
uint256 withdrawCooldown; //if you one of the 800 best champs and withdrawCooldown is less as block.timestamp then you get ETH reward
uint256 eq_sword;
uint256 eq_shield;
uint256 eq_helmet;
bool forSale; //is champ for sale?
}
struct AddressInfo {
uint256 withdrawal;
uint256 champsCount;
uint256 itemsCount;
string name;
}
//Item struct
struct Item {
uint8 itemType; // 1 - Sword | 2 - Shield | 3 - Helmet
uint8 itemRarity; // 1 - Common | 2 - Uncommon | 3 - Rare | 4 - Epic | 5 - Legendery | 6 - Forged
uint256 attackPower;
uint256 defencePower;
uint256 cooldownReduction;
uint256 price;
uint256 onChampId; //can not be used to decide if item is on champ, because champ's id can be 0, 'bool onChamp' solves it.
bool onChamp;
bool forSale; //is item for sale?
}
mapping (address => AddressInfo) public addressInfo;
mapping (uint256 => address) public champToOwner;
mapping (uint256 => address) public itemToOwner;
mapping (uint256 => string) public champToName;
Champ[] public champs;
Item[] public items;
uint256[] public leaderboard;
uint256 internal createChampFee = 5 finney;
uint256 internal lootboxFee = 5 finney;
uint256 internal pendingWithdrawal = 0;
uint256 private randNonce = 0; //being used in generating random numbers
uint256 public champsForSaleCount;
uint256 public itemsForSaleCount;
/*
* Modifiers
*/
/// @dev Checks if msg.sender is owner of champ
modifier onlyOwnerOfChamp(uint256 _champId) {
require(msg.sender == champToOwner[_champId]);
_;
}
/// @dev Checks if msg.sender is NOT owner of champ
modifier onlyNotOwnerOfChamp(uint256 _champId) {
require(msg.sender != champToOwner[_champId]);
_;
}
/// @notice Checks if amount was sent
modifier isPaid(uint256 _price){
require(msg.value >= _price);
_;
}
/// @notice People are allowed to withdraw only if min. balance (0.01 gwei) is reached
modifier contractMinBalanceReached(){
require( (address(this).balance).sub(pendingWithdrawal) > 1000000 );
_;
}
/// @notice Checks if withdraw cooldown passed
modifier isChampWithdrawReady(uint256 _id){
require(champs[_id].withdrawCooldown < block.timestamp);
_;
}
/// @notice Distribute input funds between contract owner and players
modifier distributeInput(address _affiliateAddress){
//contract owner
uint256 contractOwnerWithdrawal = (msg.value / 100) * 50; // 50%
addressInfo[contractOwner].withdrawal += contractOwnerWithdrawal;
pendingWithdrawal += contractOwnerWithdrawal;
//affiliate
//checks if _affiliateAddress is set & if affiliate address is not buying player
if(_affiliateAddress != address(0) && _affiliateAddress != msg.sender){
uint256 affiliateBonus = (msg.value / 100) * 25; //provision is 25%
addressInfo[_affiliateAddress].withdrawal += affiliateBonus;
pendingWithdrawal += affiliateBonus;
}
_;
}
/*
* View
*/
/// @notice Gets champs by address
/// @param _owner Owner address
function getChampsByOwner(address _owner) external view returns(uint256[]) {
uint256[] memory result = new uint256[](addressInfo[_owner].champsCount);
uint256 counter = 0;
for (uint256 i = 0; i < champs.length; i++) {
if (champToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
/// @notice Gets total champs count
function getChampsCount() external view returns(uint256){
return champs.length;
}
/// @notice Gets champ's reward in wei
function getChampReward(uint256 _position) public view returns(uint256) {
if(_position <= 800){
//percentageMultipier = 10,000
//maxReward = 2000 = .2% * percentageMultipier
//subtractPerPosition = 2 = .0002% * percentageMultipier
//2000 - (2 * (_position - 1))
uint256 rewardPercentage = uint256(2000).sub(2 * (_position - 1));
//available funds are all funds - already pending
uint256 availableWithdrawal = address(this).balance.sub(pendingWithdrawal);
//calculate reward for champ's position
//1000000 = percentageMultipier * 100
return availableWithdrawal / 1000000 * rewardPercentage;
}else{
return uint256(0);
}
}
/*
* Internal
*/
/// @notice Generates random modulus
/// @param _modulus Max random modulus
function randMod(uint256 _modulus) internal returns(uint256) {
randNonce++;
return uint256(keccak256(randNonce, blockhash(block.number - 1))) % _modulus;
}
/*
* External
*/
/// @notice Creates new champ
/// @param _affiliateAddress Affiliate address (optional)
function createChamp(address _affiliateAddress) external payable
whenNotPaused
isPaid(createChampFee)
distributeInput(_affiliateAddress)
{
/*
Champ memory champ = Champ({
id: 0,
attackPower: 2 + randMod(4),
defencePower: 1 + randMod(4),
cooldownTime: uint256(1 days) - uint256(randMod(9) * 1 hours),
readyTime: 0,
winCount: 0,
lossCount: 0,
position: leaderboard.length + 1, //Last place in leaderboard is new champ's position. Used in new champ struct bellow. +1 to avoid zero position.
price: 0,
withdrawCooldown: uint256(block.timestamp),
eq_sword: 0,
eq_shield: 0,
eq_helmet: 0,
forSale: false
});
*/
// This line bellow is about 30k gas cheaper than lines above. They are the same. Lines above are just more readable.
uint256 id = champs.push(Champ(0, 2 + randMod(4), 1 + randMod(4), uint256(1 days) - uint256(randMod(9) * 1 hours), 0, 0, 0, leaderboard.length + 1, 0, uint256(block.timestamp), 0,0,0, false)) - 1;
champs[id].id = id; //sets id in Champ struct
leaderboard.push(id); //push champ on the last place in leaderboard
champToOwner[id] = msg.sender; //sets owner of this champ - msg.sender
addressInfo[msg.sender].champsCount++;
emit NewChamp(id, msg.sender);
}
/// @notice Change "CreateChampFee". If ETH price will grow up it can expensive to create new champ.
/// @param _fee New "CreateChampFee"
/// @dev Only owner of contract can change "CreateChampFee"
function setCreateChampFee(uint256 _fee) external onlyOwner {
createChampFee = _fee;
}
/// @notice Change champ's name
function changeChampsName(uint _champId, string _name) external
onlyOwnerOfChamp(_champId){
champToName[_champId] = _name;
}
/// @notice Change players's name
function changePlayersName(string _name) external {
addressInfo[msg.sender].name = _name;
}
/// @notice Withdraw champ's reward
/// @param _id Champ id
/// @dev Move champ reward to pending withdrawal to his wallet.
function withdrawChamp(uint _id) external
onlyOwnerOfChamp(_id)
contractMinBalanceReached
isChampWithdrawReady(_id)
whenNotPaused {
Champ storage champ = champs[_id];
require(champ.position <= 800);
champ.withdrawCooldown = block.timestamp + 1 days; //one withdrawal 1 per day
uint256 withdrawal = getChampReward(champ.position);
addressInfo[msg.sender].withdrawal += withdrawal;
pendingWithdrawal += withdrawal;
}
/// @dev Send all pending funds of caller's address
function withdrawToAddress(address _address) external
whenNotPaused {
address playerAddress = _address;
if(playerAddress == address(0)){ playerAddress = msg.sender; }
uint256 share = addressInfo[playerAddress].withdrawal; //gets pending funds
require(share > 0); //is it more than 0?
//first sets players withdrawal pending to 0 and subtract amount from playerWithdrawals then transfer funds to avoid reentrancy
addressInfo[playerAddress].withdrawal = 0; //set player's withdrawal pendings to 0
pendingWithdrawal = pendingWithdrawal.sub(share); //subtract share from total pendings
playerAddress.transfer(share); //transfer
}
}
/// @title Moderates items and creates new ones
contract Items is ChampFactory {
event NewItem(uint256 itemID, address owner);
constructor () internal {
//item -> nothing
items.push(Item(0, 0, 0, 0, 0, 0, 0, false, false));
}
/*
* Modifiers
*/
/// @notice Checks if sender is owner of item
modifier onlyOwnerOfItem(uint256 _itemId) {
require(_itemId != 0);
require(msg.sender == itemToOwner[_itemId]);
_;
}
/// @notice Checks if sender is NOT owner of item
modifier onlyNotOwnerOfItem(uint256 _itemId) {
require(msg.sender != itemToOwner[_itemId]);
_;
}
/*
* View
*/
///@notice Check if champ has something on
///@param _type Sword, shield or helmet
function hasChampSomethingOn(uint _champId, uint8 _type) internal view returns(bool){
Champ storage champ = champs[_champId];
if(_type == 1){
return (champ.eq_sword == 0) ? false : true;
}
if(_type == 2){
return (champ.eq_shield == 0) ? false : true;
}
if(_type == 3){
return (champ.eq_helmet == 0) ? false : true;
}
}
/// @notice Gets items by address
/// @param _owner Owner address
function getItemsByOwner(address _owner) external view returns(uint256[]) {
uint256[] memory result = new uint256[](addressInfo[_owner].itemsCount);
uint256 counter = 0;
for (uint256 i = 0; i < items.length; i++) {
if (itemToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
/*
* Public
*/
///@notice Takes item off champ
function takeOffItem(uint _champId, uint8 _type) public
onlyOwnerOfChamp(_champId) {
uint256 itemId;
Champ storage champ = champs[_champId];
if(_type == 1){
itemId = champ.eq_sword; //Get item ID
if (itemId > 0) { //0 = nothing
champ.eq_sword = 0; //take off sword
}
}
if(_type == 2){
itemId = champ.eq_shield; //Get item ID
if(itemId > 0) {//0 = nothing
champ.eq_shield = 0; //take off shield
}
}
if(_type == 3){
itemId = champ.eq_helmet; //Get item ID
if(itemId > 0) { //0 = nothing
champ.eq_helmet = 0; //take off
}
}
if(itemId > 0){
items[itemId].onChamp = false; //item is free to use, is not on champ
}
}
/*
* External
*/
///@notice Puts item on champ
function putOn(uint256 _champId, uint256 _itemId) external
onlyOwnerOfChamp(_champId)
onlyOwnerOfItem(_itemId) {
Champ storage champ = champs[_champId];
Item storage item = items[_itemId];
//checks if items is on some other champ
if(item.onChamp){
takeOffItem(item.onChampId, item.itemType); //take off from champ
}
item.onChamp = true; //item is on champ
item.onChampId = _champId; //champ's id
//put on
if(item.itemType == 1){
//take off actual sword
if(champ.eq_sword > 0){
takeOffItem(champ.id, 1);
}
champ.eq_sword = _itemId; //put on sword
}
if(item.itemType == 2){
//take off actual shield
if(champ.eq_shield > 0){
takeOffItem(champ.id, 2);
}
champ.eq_shield = _itemId; //put on shield
}
if(item.itemType == 3){
//take off actual helmet
if(champ.eq_helmet > 0){
takeOffItem(champ.id, 3);
}
champ.eq_helmet = _itemId; //put on helmet
}
}
/// @notice Opens loot box and generates new item
function openLootbox(address _affiliateAddress) external payable
whenNotPaused
isPaid(lootboxFee)
distributeInput(_affiliateAddress) {
uint256 pointToCooldownReduction;
uint256 randNum = randMod(1001); //random number <= 1000
uint256 pointsToShare; //total points given
uint256 itemID;
//sets up item
Item memory item = Item({
itemType: uint8(uint256(randMod(3) + 1)), //generates item type - max num is 2 -> 0 + 1 SWORD | 1 + 1 SHIELD | 2 + 1 HELMET;
itemRarity: uint8(0),
attackPower: 0,
defencePower: 0,
cooldownReduction: 0,
price: 0,
onChampId: 0,
onChamp: false,
forSale: false
});
// Gets Rarity of item
// 45% common
// 27% uncommon
// 19% rare
// 7% epic
// 2% legendary
if(450 > randNum){
pointsToShare = 25 + randMod(9); //25 basic + random number max to 8
item.itemRarity = uint8(1);
}else if(720 > randNum){
pointsToShare = 42 + randMod(17); //42 basic + random number max to 16
item.itemRarity = uint8(2);
}else if(910 > randNum){
pointsToShare = 71 + randMod(25); //71 basic + random number max to 24
item.itemRarity = uint8(3);
}else if(980 > randNum){
pointsToShare = 119 + randMod(33); //119 basic + random number max to 32
item.itemRarity = uint8(4);
}else{
pointsToShare = 235 + randMod(41); //235 basic + random number max to 40
item.itemRarity = uint8(5);
}
//Gets type of item
if(item.itemType == uint8(1)){ //ITEM IS SWORDS
item.attackPower = pointsToShare / 10 * 7; //70% attackPower
pointsToShare -= item.attackPower; //points left;
item.defencePower = pointsToShare / 10 * randMod(6); //up to 15% defencePower
pointsToShare -= item.defencePower; //points left;
item.cooldownReduction = pointsToShare * uint256(1 minutes); //rest of points is cooldown reduction
item.itemType = uint8(1);
}
if(item.itemType == uint8(2)){ //ITEM IS SHIELD
item.defencePower = pointsToShare / 10 * 7; //70% defencePower
pointsToShare -= item.defencePower; //points left;
item.attackPower = pointsToShare / 10 * randMod(6); //up to 15% attackPowerPower
pointsToShare -= item.attackPower; //points left;
item.cooldownReduction = pointsToShare * uint256(1 minutes); //rest of points is cooldown reduction
item.itemType = uint8(2);
}
if(item.itemType == uint8(3)){ //ITEM IS HELMET
pointToCooldownReduction = pointsToShare / 10 * 7; //70% cooldown reduction
item.cooldownReduction = pointToCooldownReduction * uint256(1 minutes); //points to time
pointsToShare -= pointToCooldownReduction; //points left;
item.attackPower = pointsToShare / 10 * randMod(6); //up to 15% attackPower
pointsToShare -= item.attackPower; //points left;
item.defencePower = pointsToShare; //rest of points is defencePower
item.itemType = uint8(3);
}
itemID = items.push(item) - 1;
itemToOwner[itemID] = msg.sender; //sets owner of this item - msg.sender
addressInfo[msg.sender].itemsCount++; //every address has count of items
emit NewItem(itemID, msg.sender);
}
/// @notice Change "lootboxFee".
/// @param _fee New "lootboxFee"
/// @dev Only owner of contract can change "lootboxFee"
function setLootboxFee(uint _fee) external onlyOwner {
lootboxFee = _fee;
}
}
/// @title Moderates buying and selling items
contract ItemMarket is Items {
event TransferItem(address from, address to, uint256 itemID);
/*
* Modifiers
*/
///@notice Checks if item is for sale
modifier itemIsForSale(uint256 _id){
require(items[_id].forSale);
_;
}
///@notice Checks if item is NOT for sale
modifier itemIsNotForSale(uint256 _id){
require(items[_id].forSale == false);
_;
}
///@notice If item is for sale then cancel sale
modifier ifItemForSaleThenCancelSale(uint256 _itemID){
Item storage item = items[_itemID];
if(item.forSale){
_cancelItemSale(item);
}
_;
}
///@notice Distribute sale eth input
modifier distributeSaleInput(address _owner) {
uint256 contractOwnerCommision; //1%
uint256 playerShare; //99%
if(msg.value > 100){
contractOwnerCommision = (msg.value / 100);
playerShare = msg.value - contractOwnerCommision;
}else{
contractOwnerCommision = 0;
playerShare = msg.value;
}
addressInfo[_owner].withdrawal += playerShare;
addressInfo[contractOwner].withdrawal += contractOwnerCommision;
pendingWithdrawal += playerShare + contractOwnerCommision;
_;
}
/*
* View
*/
function getItemsForSale() view external returns(uint256[]){
uint256[] memory result = new uint256[](itemsForSaleCount);
if(itemsForSaleCount > 0){
uint256 counter = 0;
for (uint256 i = 0; i < items.length; i++) {
if (items[i].forSale == true) {
result[counter]=i;
counter++;
}
}
}
return result;
}
/*
* Private
*/
///@notice Cancel sale. Should not be called without checking if item is really for sale.
function _cancelItemSale(Item storage item) private {
//No need to overwrite item's price
item.forSale = false;
itemsForSaleCount--;
}
/*
* Internal
*/
/// @notice Transfer item
function transferItem(address _from, address _to, uint256 _itemID) internal
ifItemForSaleThenCancelSale(_itemID) {
Item storage item = items[_itemID];
//take off
if(item.onChamp && _to != champToOwner[item.onChampId]){
takeOffItem(item.onChampId, item.itemType);
}
addressInfo[_to].itemsCount++;
addressInfo[_from].itemsCount--;
itemToOwner[_itemID] = _to;
emit TransferItem(_from, _to, _itemID);
}
/*
* Public
*/
/// @notice Calls transfer item
/// @notice Address _from is msg.sender. Cannot be used is market, bc msg.sender is buyer
function giveItem(address _to, uint256 _itemID) public
onlyOwnerOfItem(_itemID) {
transferItem(msg.sender, _to, _itemID);
}
/// @notice Calcels item's sale
function cancelItemSale(uint256 _id) public
itemIsForSale(_id)
onlyOwnerOfItem(_id){
Item storage item = items[_id];
_cancelItemSale(item);
}
/*
* External
*/
/// @notice Sets item for sale
function setItemForSale(uint256 _id, uint256 _price) external
onlyOwnerOfItem(_id)
itemIsNotForSale(_id) {
Item storage item = items[_id];
item.forSale = true;
item.price = _price;
itemsForSaleCount++;
}
/// @notice Buys item
function buyItem(uint256 _id) external payable
whenNotPaused
onlyNotOwnerOfItem(_id)
itemIsForSale(_id)
isPaid(items[_id].price)
distributeSaleInput(itemToOwner[_id])
{
transferItem(itemToOwner[_id], msg.sender, _id);
}
}
/// @title Manages forging
contract ItemForge is ItemMarket {
event Forge(uint256 forgedItemID);
///@notice Forge items together
function forgeItems(uint256 _parentItemID, uint256 _childItemID) external
onlyOwnerOfItem(_parentItemID)
onlyOwnerOfItem(_childItemID)
ifItemForSaleThenCancelSale(_parentItemID)
ifItemForSaleThenCancelSale(_childItemID) {
//checks if items are not the same
require(_parentItemID != _childItemID);
Item storage parentItem = items[_parentItemID];
Item storage childItem = items[_childItemID];
//take child item off, because child item will be burned
if(childItem.onChamp){
takeOffItem(childItem.onChampId, childItem.itemType);
}
//update parent item
parentItem.attackPower = (parentItem.attackPower > childItem.attackPower) ? parentItem.attackPower : childItem.attackPower;
parentItem.defencePower = (parentItem.defencePower > childItem.defencePower) ? parentItem.defencePower : childItem.defencePower;
parentItem.cooldownReduction = (parentItem.cooldownReduction > childItem.cooldownReduction) ? parentItem.cooldownReduction : childItem.cooldownReduction;
parentItem.itemRarity = uint8(6);
//burn child item
transferItem(msg.sender, address(0), _childItemID);
emit Forge(_parentItemID);
}
}
/// @title Manages attacks in game
contract ChampAttack is ItemForge {
event Attack(uint256 winnerChampID, uint256 defeatedChampID, bool didAttackerWin);
/*
* Modifiers
*/
/// @notice Is champ ready to fight again?
modifier isChampReady(uint256 _champId) {
require (champs[_champId].readyTime <= block.timestamp);
_;
}
/// @notice Prevents from self-attack
modifier notSelfAttack(uint256 _champId, uint256 _targetId) {
require(_champId != _targetId);
_;
}
/// @notice Checks if champ does exist
modifier targetExists(uint256 _targetId){
require(champToOwner[_targetId] != address(0));
_;
}
/*
* View
*/
/// @notice Gets champ's attack power, defence power and cooldown reduction with items on
function getChampStats(uint256 _champId) public view returns(uint256,uint256,uint256){
Champ storage champ = champs[_champId];
Item storage sword = items[champ.eq_sword];
Item storage shield = items[champ.eq_shield];
Item storage helmet = items[champ.eq_helmet];
//AP
uint256 totalAttackPower = champ.attackPower + sword.attackPower + shield.attackPower + helmet.attackPower; //Gets champs AP
//DP
uint256 totalDefencePower = champ.defencePower + sword.defencePower + shield.defencePower + helmet.defencePower; //Gets champs DP
//CR
uint256 totalCooldownReduction = sword.cooldownReduction + shield.cooldownReduction + helmet.cooldownReduction; //Gets CR
return (totalAttackPower, totalDefencePower, totalCooldownReduction);
}
/*
* Pure
*/
/// @notice Subtracts ability points. Helps to not cross minimal attack ability points -> 2
/// @param _playerAttackPoints Actual player's attack points
/// @param _x Amount to subtract
function subAttack(uint256 _playerAttackPoints, uint256 _x) internal pure returns (uint256) {
return (_playerAttackPoints <= _x + 2) ? 2 : _playerAttackPoints - _x;
}
/// @notice Subtracts ability points. Helps to not cross minimal defence ability points -> 1
/// @param _playerDefencePoints Actual player's defence points
/// @param _x Amount to subtract
function subDefence(uint256 _playerDefencePoints, uint256 _x) internal pure returns (uint256) {
return (_playerDefencePoints <= _x) ? 1 : _playerDefencePoints - _x;
}
/*
* Private
*/
/// @dev Is called from from Attack function after the winner is already chosen
/// @dev Updates abilities, champ's stats and swaps positions
function _attackCompleted(Champ storage _winnerChamp, Champ storage _defeatedChamp, uint256 _pointsGiven, uint256 _pointsToAttackPower) private {
/*
* Updates abilities after fight
*/
//winner abilities update
_winnerChamp.attackPower += _pointsToAttackPower; //increase attack power
_winnerChamp.defencePower += _pointsGiven - _pointsToAttackPower; //max point that was given - already given to AP
//defeated champ's abilities update
//checks for not cross minimal AP & DP points
_defeatedChamp.attackPower = subAttack(_defeatedChamp.attackPower, _pointsToAttackPower); //decrease attack power
_defeatedChamp.defencePower = subDefence(_defeatedChamp.defencePower, _pointsGiven - _pointsToAttackPower); // decrease defence power
/*
* Update champs' wins and losses
*/
_winnerChamp.winCount++;
_defeatedChamp.lossCount++;
/*
* Swap positions
*/
if(_winnerChamp.position > _defeatedChamp.position) { //require loser to has better (lower) postion than attacker
uint256 winnerPosition = _winnerChamp.position;
uint256 loserPosition = _defeatedChamp.position;
_defeatedChamp.position = winnerPosition;
_winnerChamp.position = loserPosition;
//position in champ struct is always one point bigger than in leaderboard array
leaderboard[winnerPosition - 1] = _defeatedChamp.id;
leaderboard[loserPosition - 1] = _winnerChamp.id;
}
}
/// @dev Gets pointsGiven and pointsToAttackPower
function _getPoints(uint256 _pointsGiven) private returns (uint256 pointsGiven, uint256 pointsToAttackPower){
return (_pointsGiven, randMod(_pointsGiven+1));
}
/*
* External
*/
/// @notice Attack function
/// @param _champId Attacker champ
/// @param _targetId Target champ
function attack(uint256 _champId, uint256 _targetId) external
onlyOwnerOfChamp(_champId)
isChampReady(_champId)
notSelfAttack(_champId, _targetId)
targetExists(_targetId) {
Champ storage myChamp = champs[_champId];
Champ storage enemyChamp = champs[_targetId];
uint256 pointsGiven; //total points that will be divided between AP and DP
uint256 pointsToAttackPower; //part of points that will be added to attack power, the rest of points go to defence power
uint256 myChampAttackPower;
uint256 enemyChampDefencePower;
uint256 myChampCooldownReduction;
(myChampAttackPower,,myChampCooldownReduction) = getChampStats(_champId);
(,enemyChampDefencePower,) = getChampStats(_targetId);
//if attacker's AP is more than target's DP then attacker wins
if (myChampAttackPower > enemyChampDefencePower) {
//this should demotivate players from farming on way weaker champs than they are
//the bigger difference between AP & DP is, the reward is smaller
if(myChampAttackPower - enemyChampDefencePower < 5){
//big experience - 3 ability points
(pointsGiven, pointsToAttackPower) = _getPoints(3);
}else if(myChampAttackPower - enemyChampDefencePower < 10){
//medium experience - 2 ability points
(pointsGiven, pointsToAttackPower) = _getPoints(2);
}else{
//small experience - 1 ability point to random ability (attack power or defence power)
(pointsGiven, pointsToAttackPower) = _getPoints(1);
}
_attackCompleted(myChamp, enemyChamp, pointsGiven, pointsToAttackPower);
emit Attack(myChamp.id, enemyChamp.id, true);
} else {
//1 ability point to random ability (attack power or defence power)
(pointsGiven, pointsToAttackPower) = _getPoints(1);
_attackCompleted(enemyChamp, myChamp, pointsGiven, pointsToAttackPower);
emit Attack(enemyChamp.id, myChamp.id, false);
}
//Trigger cooldown for attacker
myChamp.readyTime = uint256(block.timestamp + myChamp.cooldownTime - myChampCooldownReduction);
}
}
/// @title Moderates buying and selling champs
contract ChampMarket is ChampAttack {
event TransferChamp(address from, address to, uint256 champID);
/*
* Modifiers
*/
///@notice Require champ to be sale
modifier champIsForSale(uint256 _id){
require(champs[_id].forSale);
_;
}
///@notice Require champ NOT to be for sale
modifier champIsNotForSale(uint256 _id){
require(champs[_id].forSale == false);
_;
}
///@notice If champ is for sale then cancel sale
modifier ifChampForSaleThenCancelSale(uint256 _champID){
Champ storage champ = champs[_champID];
if(champ.forSale){
_cancelChampSale(champ);
}
_;
}
/*
* View
*/
///@notice Gets all champs for sale
function getChampsForSale() view external returns(uint256[]){
uint256[] memory result = new uint256[](champsForSaleCount);
if(champsForSaleCount > 0){
uint256 counter = 0;
for (uint256 i = 0; i < champs.length; i++) {
if (champs[i].forSale == true) {
result[counter]=i;
counter++;
}
}
}
return result;
}
/*
* Private
*/
///@dev Cancel sale. Should not be called without checking if champ is really for sale.
function _cancelChampSale(Champ storage champ) private {
//cancel champ's sale
//no need waste gas to overwrite his price.
champ.forSale = false;
champsForSaleCount--;
}
/*
* Internal
*/
/// @notice Transfer champ
function transferChamp(address _from, address _to, uint256 _champId) internal ifChampForSaleThenCancelSale(_champId){
Champ storage champ = champs[_champId];
//transfer champ
addressInfo[_to].champsCount++;
addressInfo[_from].champsCount--;
champToOwner[_champId] = _to;
//transfer items
if(champ.eq_sword != 0) { transferItem(_from, _to, champ.eq_sword); }
if(champ.eq_shield != 0) { transferItem(_from, _to, champ.eq_shield); }
if(champ.eq_helmet != 0) { transferItem(_from, _to, champ.eq_helmet); }
emit TransferChamp(_from, _to, _champId);
}
/*
* Public
*/
/// @notice Champ is no more for sale
function cancelChampSale(uint256 _id) public
champIsForSale(_id)
onlyOwnerOfChamp(_id) {
Champ storage champ = champs[_id];
_cancelChampSale(champ);
}
/*
* External
*/
/// @notice Gift champ
/// @dev Address _from is msg.sender
function giveChamp(address _to, uint256 _champId) external
onlyOwnerOfChamp(_champId) {
transferChamp(msg.sender, _to, _champId);
}
/// @notice Sets champ for sale
function setChampForSale(uint256 _id, uint256 _price) external
onlyOwnerOfChamp(_id)
champIsNotForSale(_id) {
Champ storage champ = champs[_id];
champ.forSale = true;
champ.price = _price;
champsForSaleCount++;
}
/// @notice Buys champ
function buyChamp(uint256 _id) external payable
whenNotPaused
onlyNotOwnerOfChamp(_id)
champIsForSale(_id)
isPaid(champs[_id].price)
distributeSaleInput(champToOwner[_id]) {
transferChamp(champToOwner[_id], msg.sender, _id);
}
}
/// @title Only used for deploying all contracts
contract MyCryptoChampCore is ChampMarket {
/*
© Copyright 2018 - Patrik Mojzis
Redistributing and modifying is prohibited.
https://mycryptochamp.io/
What is MyCryptoChamp?
- Blockchain game about upgrading champs by fighting, getting better items,
trading them and the best 800 champs are daily rewarded by real Ether.
Feel free to ask any questions
hello@mycryptochamp.io
*/
}