├── .gitignore ├── migrations ├── 1_initial_migration.js ├── 4_deploy_pvp.js ├── 3_deploy_nft.js ├── 5_deploy_exchange.js ├── 2_deploy_coin.js └── 6_initialize_exchange.js ├── truffle.js ├── contracts ├── README.md ├── GoCryptobotNft │ ├── GoCryptobotNftMintable.sol │ ├── GoCryptobotNftBurnable.sol │ ├── GoCryptobotNftCore.sol │ ├── GoCryptobotNftAccessControl.sol │ └── GoCryptobotNftOwnership.sol ├── GoCryptobotPvp │ ├── GoCryptobotCore.sol │ ├── GoCryptobotAccessControl.sol │ ├── GoCryptobotRandom.sol │ ├── GoCryptobotRounds.sol │ └── GoCryptobotScore.sol ├── GoCryptobotCoin │ ├── GoCryptobotCoinCore.sol │ ├── GoCryptobotCoinERC827.sol │ └── GoCryptobotCoinERC20.sol ├── Migrations.sol └── GCCExchange │ ├── GCCExchangeCoinOperation.sol │ ├── GCCExchangeAccessControl.sol │ └── GCCExchangeCore.sol ├── issue_template.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/4_deploy_pvp.js: -------------------------------------------------------------------------------- 1 | const GoCryptobotCore = artifacts.require("GoCryptobotCore"); 2 | 3 | module.exports = function(deployer, network, accounts) { 4 | deployer.deploy(GoCryptobotCore); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "127.0.0.1", 5 | port: 9545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/3_deploy_nft.js: -------------------------------------------------------------------------------- 1 | const GoCrypobotNftCore = artifacts.require("GoCryptobotNftCore"); 2 | 3 | module.exports = function(deployer, network, accounts) { 4 | deployer.deploy(GoCrypobotNftCore); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/5_deploy_exchange.js: -------------------------------------------------------------------------------- 1 | const GCCExchangeCore = artifacts.require("GCCExchangeCore"); 2 | 3 | module.exports = function(deployer, network, accounts) { 4 | deployer.deploy(GCCExchangeCore, {from: accounts[0]}); 5 | }; -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | It is recommended to run using [Truffle 4 framework](http://truffleframework.com/). The migration settings are provided. 4 | 5 | Run the development network: `truffle develop` 6 | 7 | Deploy the contracts: `truffle migrate` 8 | 9 | -------------------------------------------------------------------------------- /contracts/GoCryptobotNft/GoCryptobotNftMintable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotNftOwnership.sol"; 4 | 5 | contract GoCryptobotNftMintable is GoCryptobotNftOwnership { 6 | function mint(address _to, uint256 _tokenId) public onlyOperator { 7 | _mint(_to, _tokenId); 8 | } 9 | } -------------------------------------------------------------------------------- /contracts/GoCryptobotNft/GoCryptobotNftBurnable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotNftMintable.sol"; 4 | 5 | contract GoCryptobotNftBurnable is GoCryptobotNftMintable { 6 | function burn(uint256 _tokenId) public onlyOwnerOf(_tokenId) whenNotPaused { 7 | _burn(_tokenId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/GoCryptobotPvp/GoCryptobotCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotRounds.sol"; 4 | 5 | contract GoCryptobotCore is GoCryptobotRounds { 6 | function GoCryptobotCore() public { 7 | paused = false; 8 | 9 | owner = msg.sender; 10 | operator = msg.sender; 11 | } 12 | } -------------------------------------------------------------------------------- /migrations/2_deploy_coin.js: -------------------------------------------------------------------------------- 1 | const SafeMath = artifacts.require("SafeMath"); 2 | const GoCrypobotCoinCore = artifacts.require("GoCryptobotCoinCore"); 3 | 4 | module.exports = function(deployer, network, accounts) { 5 | deployer.deploy(SafeMath); 6 | deployer.link(SafeMath, GoCrypobotCoinCore); 7 | deployer.deploy(GoCrypobotCoinCore, {from: accounts[0]}); 8 | }; 9 | -------------------------------------------------------------------------------- /contracts/GoCryptobotCoin/GoCryptobotCoinCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotCoinERC827.sol"; 4 | 5 | contract GoCryptobotCoinCore is GoCryptobotCoinERC827 { 6 | function GoCryptobotCoinCore() public { 7 | balances[msg.sender] = 1000000000 * (10 ** uint(decimals)); 8 | totalSupply_.add(balances[msg.sender]); 9 | } 10 | 11 | function () public payable { 12 | revert(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /migrations/6_initialize_exchange.js: -------------------------------------------------------------------------------- 1 | const GoCrypobotCoinCore = artifacts.require("GoCryptobotCoinCore"); 2 | const GCCExchangeCore = artifacts.require("GCCExchangeCore"); 3 | 4 | module.exports = async function(deployer, network, accounts) { 5 | const coin = await GoCrypobotCoinCore.deployed(); 6 | const exchange = await GCCExchangeCore.deployed(); 7 | 8 | exchange.setCoinContract(coin.address, {from: accounts[0]}); 9 | exchange.unpause({from: accounts[0]}); 10 | }; -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/GoCryptobotNft/GoCryptobotNftCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotNftBurnable.sol"; 4 | 5 | contract GoCryptobotNftCore is GoCryptobotNftBurnable { 6 | 7 | function GoCryptobotNftCore() public { 8 | paused = false; 9 | 10 | owner = msg.sender; 11 | operator = msg.sender; 12 | } 13 | 14 | // Users may have accidentally sent to our address 15 | function rescueLostToken(uint256 _tokenId, address _to) public onlyOperator { 16 | clearApprovalAndTransfer(this, _to, _tokenId); 17 | } 18 | 19 | function () public payable { 20 | revert(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | ## How to Create a Good Vulnerability Submission: 2 | - Description: Please, describe the issue you have found 3 | - Use-case: Please, describe the use-case or the scenario in which the issue happened 4 | - Impact: Please, explain the impact that the issue could bring about and how it will affect the game or the users 5 | - Reproduction: Please, provide the exact steps on how to reproduce this bug on a new contract, and if possible, point to specific tx hashes or accounts used 6 | - Bug Fix: If you have a solution or a work-around, please send us a pull request 7 | - Note: If we can't reproduce with given instructions then a (Truffle) test case will be required 8 | -------------------------------------------------------------------------------- /contracts/GCCExchange/GCCExchangeCoinOperation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotCoinERC827.sol"; 4 | import "./GCCExchangeAccessControl.sol"; 5 | 6 | contract GCCExchangeCoinOperation is GCCExchangeAccessControl { 7 | GoCryptobotCoinERC827 internal coin; 8 | 9 | function setCoinContract(address _address) external onlyOwnerOrOperator { 10 | GoCryptobotCoinERC827 _contract = GoCryptobotCoinERC827(_address); 11 | coin = _contract; 12 | } 13 | 14 | function unpause() public { 15 | require(coin != address(0)); 16 | super.unpause(); 17 | } 18 | 19 | function operate(bytes data) external onlyOwnerOrOperator { 20 | require(coin.call(data)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/GoCryptobotPvp/GoCryptobotAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract GoCryptobotAccessControl { 4 | address public owner; 5 | address public operator; 6 | 7 | bool public paused; 8 | 9 | modifier onlyOwner() {require(msg.sender == owner); _;} 10 | modifier onlyOperator() {require(msg.sender == operator); _;} 11 | modifier onlyOwnerOrOperator() {require(msg.sender == owner || msg.sender == operator); _;} 12 | 13 | modifier whenPaused() {require(paused); _;} 14 | modifier whenNotPaused() {require(!paused); _;} 15 | 16 | function transferOwnership(address newOwner) public onlyOwner { 17 | require(newOwner != address(0)); 18 | owner = newOwner; 19 | } 20 | 21 | function transferOperator(address newOperator) public onlyOwner { 22 | require(newOperator != address(0)); 23 | operator = newOperator; 24 | } 25 | 26 | function pause() public onlyOwnerOrOperator whenNotPaused { 27 | paused = true; 28 | } 29 | 30 | function unpause() public onlyOwner whenPaused { 31 | paused = false; 32 | } 33 | } -------------------------------------------------------------------------------- /contracts/GoCryptobotNft/GoCryptobotNftAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract GoCryptobotNftAccessControl { 4 | address public owner; 5 | address public operator; 6 | 7 | bool public paused; 8 | 9 | modifier onlyOwner() {require(msg.sender == owner); _;} 10 | modifier onlyOperator() {require(msg.sender == operator); _;} 11 | modifier onlyOwnerOrOperator() {require(msg.sender == owner || msg.sender == operator); _;} 12 | 13 | modifier whenPaused() {require(paused); _;} 14 | modifier whenNotPaused() {require(!paused); _;} 15 | 16 | function transferOwnership(address newOwner) public onlyOwner { 17 | require(newOwner != address(0)); 18 | owner = newOwner; 19 | } 20 | 21 | function transferOperator(address newOperator) public onlyOwner { 22 | require(newOperator != address(0)); 23 | operator = newOperator; 24 | } 25 | 26 | function pause() public onlyOwnerOrOperator whenNotPaused { 27 | paused = true; 28 | } 29 | 30 | function unpause() public onlyOwner whenPaused { 31 | paused = false; 32 | } 33 | } -------------------------------------------------------------------------------- /contracts/GCCExchange/GCCExchangeAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract GCCExchangeAccessControl { 4 | address public owner; 5 | address public operator; 6 | 7 | bool public paused; 8 | 9 | modifier onlyOwner() {require(msg.sender == owner);_;} 10 | modifier onlyOperator() {require(msg.sender == operator);_;} 11 | modifier onlyOwnerOrOperator() {require(msg.sender == owner || msg.sender == operator);_;} 12 | 13 | modifier whenPaused() {require(paused);_;} 14 | modifier whenNotPaused() {require(!paused);_;} 15 | 16 | function transferOwnership(address newOwner) public onlyOwner { 17 | require(newOwner != address(0)); 18 | owner = newOwner; 19 | } 20 | 21 | function transferOperator(address newOperator) public onlyOwner { 22 | require(newOperator != address(0)); 23 | operator = newOperator; 24 | } 25 | 26 | function pause() public onlyOwnerOrOperator whenNotPaused { 27 | paused = true; 28 | } 29 | 30 | function unpause() public onlyOwner whenPaused { 31 | paused = false; 32 | } 33 | 34 | function withdrawBalance(address _recipient, uint256 amount) external onlyOwnerOrOperator { 35 | require(amount > 0); 36 | require(amount <= this.balance); 37 | require(_recipient != address(0)); 38 | _recipient.transfer(amount); 39 | } 40 | 41 | function depositBalance() external payable {} 42 | } 43 | -------------------------------------------------------------------------------- /contracts/GCCExchange/GCCExchangeCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GCCExchangeCoinOperation.sol"; 4 | 5 | contract GCCExchangeCore is GCCExchangeCoinOperation { 6 | uint8 constant FEE_RATE = 5; 7 | address internal coinStorage; 8 | 9 | event Withdrawal(address claimant, uint256 mgccAmount, uint256 weiAmount); 10 | 11 | function GCCExchangeCore() public { 12 | coinStorage = this; 13 | 14 | paused = true; 15 | 16 | owner = msg.sender; 17 | operator = msg.sender; 18 | } 19 | 20 | function setCoinStorage(address _address) public onlyOwnerOrOperator { 21 | coinStorage = _address; 22 | } 23 | 24 | function withdraw(address _claimant, uint256 _mgcc) public whenNotPaused { 25 | require(coin.allowance(_claimant, this) >= _mgcc); 26 | require(coin.transferFrom(_claimant, coinStorage, _mgcc)); 27 | uint256 exchange = (_convertToWei(_mgcc) / 100) * (100 - FEE_RATE); 28 | _claimant.transfer(exchange); 29 | Withdrawal(_claimant, _mgcc, exchange); 30 | } 31 | 32 | function _convertToWei(uint256 mgcc) internal pure returns (uint256) { 33 | // 1000 GCC = 1000000 mGCC = 1 ETH = 1000000000000000000 wei 34 | // 1 GCC = 1000 mGCC = 0.001 ETH = 1000000000000000 wei 35 | // 1 mGcc = 0.000001 ETH = 1000000000000 wei 36 | return mgcc * 1000000000000 wei; 37 | } 38 | 39 | function () external payable { 40 | revert(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/GoCryptobotPvp/GoCryptobotRandom.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotAccessControl.sol"; 4 | 5 | contract GoCryptobotRandom is GoCryptobotAccessControl { 6 | uint commitmentNumber; 7 | bytes32 randomBytes; 8 | 9 | function commitment() public onlyOperator { 10 | commitmentNumber = block.number; 11 | } 12 | 13 | function _initRandom() internal { 14 | require(commitmentNumber < block.number); 15 | 16 | if (commitmentNumber < block.number - 255) { 17 | randomBytes = block.blockhash(block.number - 1); 18 | } else { 19 | randomBytes = block.blockhash(commitmentNumber); 20 | } 21 | } 22 | 23 | function _shuffle(uint8[] deck) internal { 24 | require(deck.length < 256); 25 | 26 | uint8 deckLength = uint8(deck.length); 27 | uint8 random; 28 | for (uint8 i = 0; i < deckLength; i++) { 29 | if (i % 32 == 0) { 30 | randomBytes = keccak256(randomBytes); 31 | } 32 | random = uint8(randomBytes[i % 32]) % (deckLength - i); 33 | 34 | if (random != deckLength - 1 - i) { 35 | deck[random] ^= deck[deckLength - 1 - i]; 36 | deck[deckLength - 1 - i] ^= deck[random]; 37 | deck[random] ^= deck[deckLength - 1 - i]; 38 | } 39 | } 40 | } 41 | 42 | function _random256() internal returns(uint256) { 43 | randomBytes = keccak256(randomBytes); 44 | return uint256(randomBytes); 45 | } 46 | } -------------------------------------------------------------------------------- /contracts/GoCryptobotPvp/GoCryptobotRounds.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotScore.sol"; 4 | 5 | contract GoCryptobotRounds is GoCryptobotScore { 6 | event RoundFinished(EventType eventType, EventColor eventMajorColor, EventColor eventMinorColor, uint scoreA, uint scoreB, uint scoreC, uint scoreD); 7 | event AllFinished(uint scoreA, uint scoreB, uint scoreC, uint scoreD); 8 | event WinnerTeam(uint8[4] candidates, uint8 winner); 9 | 10 | function run(bytes playerData, uint8[4] eventTypes, uint8[2][4] eventColors) public onlyOperator { 11 | require(playerData.length == 128 * PLAYER_SIZE); 12 | 13 | _initRandom(); 14 | 15 | uint8[] memory colorSelection = new uint8[](8); 16 | colorSelection[0] = 0; 17 | colorSelection[1] = 1; 18 | colorSelection[2] = 0; 19 | colorSelection[3] = 1; 20 | colorSelection[4] = 0; 21 | colorSelection[5] = 1; 22 | colorSelection[6] = 0; 23 | colorSelection[7] = 1; 24 | 25 | _shuffle(colorSelection); 26 | 27 | uint[4] memory totalScores; 28 | for (uint8 i = 0; i < 4; i++) { 29 | uint8 majorColor = eventColors[i][colorSelection[i]]; 30 | uint8 minorColor = eventColors[i][colorSelection[i]^1]; 31 | uint[4] memory roundScores = _round(playerData, EventType(eventTypes[i]), EventColor(majorColor), EventColor(minorColor)); 32 | totalScores[0] += roundScores[0]; 33 | totalScores[1] += roundScores[1]; 34 | totalScores[2] += roundScores[2]; 35 | totalScores[3] += roundScores[3]; 36 | } 37 | AllFinished(totalScores[0], totalScores[1], totalScores[2], totalScores[3]); 38 | 39 | uint maxScore; 40 | uint maxCount; 41 | uint8[4] memory candidates; 42 | for (i = 0; i < 4; i++) { 43 | if (maxScore < totalScores[i]) { 44 | maxScore = totalScores[i]; 45 | maxCount = 0; 46 | candidates[maxCount++] = i + 1; 47 | } else if (maxScore == totalScores[i]) { 48 | candidates[maxCount++] = i + 1; 49 | } 50 | } 51 | assert(maxCount > 0); 52 | if (maxCount == 1) { 53 | WinnerTeam(candidates, candidates[0]); 54 | } else { 55 | WinnerTeam(candidates, candidates[_random256() % maxCount]); 56 | } 57 | } 58 | 59 | function _round(bytes memory playerData, EventType eventType, EventColor eventMajorColor, EventColor eventMinorColor) internal returns(uint[4]) { 60 | uint numOfPlayers = playerData.length / PLAYER_SIZE; 61 | uint[4] memory scores; 62 | for (uint i = 0; i < numOfPlayers; i++) { 63 | scores[i / (numOfPlayers / 4)] += _getPlayerEventScore(playerData, i, eventType, eventMajorColor, eventMinorColor); 64 | } 65 | RoundFinished(eventType, eventMajorColor, eventMinorColor, scores[0], scores[1], scores[2], scores[3]); 66 | return scores; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/GoCryptobotPvp/GoCryptobotScore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotRandom.sol"; 4 | 5 | contract GoCryptobotScore is GoCryptobotRandom { 6 | // A part's skill consists of color and level. (Total 2 bytes) 7 | // 1 2 8 | // Skill 9 | // +---+---+ 10 | // | C | L + 11 | // +---+---+ 12 | // 13 | // C = Color, 0 ~ 4. 14 | // L = Level, 0 ~ 8. 15 | // 16 | uint256 constant PART_SKILL_SIZE = 2; 17 | 18 | // A part consists of level and 3 skills. (Total 7 bytes) 19 | // 1 2 3 4 5 6 7 20 | // Part 21 | // +---+---+---+---+---+---+---+ 22 | // | L | Skill | Skill | Skill | 23 | // +---+---+---+---+---+---+---+ 24 | // 25 | // L = Level, 1 ~ 50. 26 | // 27 | // A part doesn't contains color because individual color doesn't affect to 28 | // the score, but it is used to calculate player's theme color. 29 | // 30 | uint256 constant PART_BASE_SIZE = 1; 31 | uint256 constant PART_SIZE = PART_BASE_SIZE + 3 * PART_SKILL_SIZE; 32 | 33 | // A player consists of theme effect and 4 parts. (Total 29 bytes) 34 | // 1 2 3 4 5 6 7 35 | // Player 36 | // +---+ 37 | // | C | 38 | // +---+---+---+---+---+---+---+ 39 | // | HEAD PART | 40 | // +---+---+---+---+---+---+---+ 41 | // | BODY PART | 42 | // +---+---+---+---+---+---+---+ 43 | // | LEGS PART | 44 | // +---+---+---+---+---+---+---+ 45 | // | BOOSTER PART | 46 | // +---+---+---+---+---+---+---+ 47 | // 48 | // C = Whether player's theme effect is enabled or not, 1 or 0. 49 | // 50 | // The theme effect is set to 1 iff the theme of each part are identical. 51 | // 52 | uint256 constant PLAYER_BASE_SIZE = 1; 53 | uint256 constant PLAYER_SIZE = PLAYER_BASE_SIZE + PART_SIZE * 4; 54 | 55 | enum PartType {HEAD, BODY, LEGS, BOOSTER} 56 | enum EventType {BOWLING, HANGING, SPRINT, HIGH_JUMP} 57 | enum EventColor {NONE, YELLOW, BLUE, GREEN, RED} 58 | 59 | function _getPartLevel(bytes data, uint partOffset) internal pure returns(uint8) { 60 | return uint8(data[partOffset + 0]); 61 | } 62 | // NOTE: _getPartSkillColor is called up to 128 * 4 * 3 times. Explicit 63 | // conversion to EventColor could be costful. 64 | function _getPartSkillColor(bytes data, uint partOffset, uint skillIndex) internal pure returns(byte) { 65 | return data[partOffset + PART_BASE_SIZE + (skillIndex * PART_SKILL_SIZE) + 0]; 66 | } 67 | function _getPartSkillLevel(bytes data, uint partOffset, uint skillIndex) internal pure returns(uint8) { 68 | return uint8(data[partOffset + PART_BASE_SIZE + (skillIndex * PART_SKILL_SIZE) + 1]); 69 | } 70 | 71 | function _getPlayerThemeEffect(bytes data, uint playerOffset) internal pure returns(byte) { 72 | return data[playerOffset + 0]; 73 | } 74 | 75 | function _getPlayerEventScore(bytes data, uint playerIndex, EventType eventType, EventColor _eventMajorColor, EventColor _eventMinorColor) internal pure returns(uint) { 76 | uint partOffset = (PLAYER_SIZE * playerIndex) + PLAYER_BASE_SIZE + (uint256(eventType) * PART_SIZE); 77 | uint level = _getPartLevel(data, partOffset); 78 | uint majorSkillSum = 0; 79 | uint minorSkillSum = 0; 80 | 81 | byte eventMajorColor = byte(uint8(_eventMajorColor)); 82 | byte eventMinorColor = byte(uint8(_eventMinorColor)); 83 | for (uint i = 0; i < 3; i++) { 84 | byte skillColor = _getPartSkillColor(data, partOffset, i); 85 | if (skillColor == eventMajorColor) { 86 | majorSkillSum += _getPartSkillLevel(data, partOffset, i); 87 | } else if (skillColor == eventMinorColor) { 88 | minorSkillSum += _getPartSkillLevel(data, partOffset, i); 89 | } 90 | } 91 | byte playerThemeEffect = _getPlayerThemeEffect(data, PLAYER_SIZE * playerIndex); 92 | if (playerThemeEffect != 0) { 93 | return level + (majorSkillSum * 4) + (minorSkillSum * 2); 94 | } else { 95 | return level + (majorSkillSum * 3) + (minorSkillSum * 1); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/GoCryptobotCoin/GoCryptobotCoinERC827.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotCoinERC20.sol"; 4 | 5 | contract GoCryptobotCoinERC827 is GoCryptobotCoinERC20 { 6 | /** 7 | @dev Addition to ERC20 token methods. It allows to 8 | approve the transfer of value and execute a call with the sent data. 9 | 10 | Beware that changing an allowance with this method brings the risk that 11 | someone may use both the old and the new allowance by unfortunate 12 | transaction ordering. One possible solution to mitigate this race condition 13 | is to first reduce the spender's allowance to 0 and set the desired value 14 | afterwards: 15 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 16 | 17 | @param _spender The address that will spend the funds. 18 | @param _value The amount of tokens to be spent. 19 | @param _data ABI-encoded contract call to call `_to` address. 20 | 21 | @return true if the call function was executed successfully 22 | */ 23 | function approve( address _spender, uint256 _value, bytes _data ) public returns (bool) { 24 | require(_spender != address(this)); 25 | super.approve(_spender, _value); 26 | require(_spender.call(_data)); 27 | return true; 28 | } 29 | 30 | /** 31 | @dev Addition to ERC20 token methods. Transfer tokens to a specified 32 | address and execute a call with the sent data on the same transaction 33 | 34 | @param _to address The address which you want to transfer to 35 | @param _value uint256 the amout of tokens to be transfered 36 | @param _data ABI-encoded contract call to call `_to` address. 37 | 38 | @return true if the call function was executed successfully 39 | */ 40 | function transfer( address _to, uint256 _value, bytes _data ) public returns (bool) { 41 | require(_to != address(this)); 42 | super.transfer(_to, _value); 43 | require(_to.call(_data)); 44 | return true; 45 | } 46 | 47 | /** 48 | @dev Addition to ERC20 token methods. Transfer tokens from one address to 49 | another and make a contract call on the same transaction 50 | 51 | @param _from The address which you want to send tokens from 52 | @param _to The address which you want to transfer to 53 | @param _value The amout of tokens to be transferred 54 | @param _data ABI-encoded contract call to call `_to` address. 55 | 56 | @return true if the call function was executed successfully 57 | */ 58 | function transferFrom( address _from, address _to, uint256 _value, bytes _data ) public returns (bool) { 59 | require(_to != address(this)); 60 | super.transferFrom(_from, _to, _value); 61 | require(_to.call(_data)); 62 | return true; 63 | } 64 | 65 | /** 66 | @dev Addition to StandardToken methods. Increase the amount of tokens that 67 | an owner allowed to a spender and execute a call with the sent data. 68 | 69 | approve should be called when allowed[_spender] == 0. To increment 70 | allowed value is better to use this function to avoid 2 calls (and wait until 71 | the first transaction is mined) 72 | From MonolithDAO Token.sol 73 | @param _spender The address which will spend the funds. 74 | @param _addedValue The amount of tokens to increase the allowance by. 75 | @param _data ABI-encoded contract call to call `_spender` address. 76 | */ 77 | function increaseApproval(address _spender, uint _addedValue, bytes _data) public returns (bool) { 78 | require(_spender != address(this)); 79 | super.increaseApproval(_spender, _addedValue); 80 | require(_spender.call(_data)); 81 | return true; 82 | } 83 | 84 | /** 85 | @dev Addition to StandardToken methods. Decrease the amount of tokens that 86 | an owner allowed to a spender and execute a call with the sent data. 87 | 88 | approve should be called when allowed[_spender] == 0. To decrement 89 | allowed value is better to use this function to avoid 2 calls (and wait until 90 | the first transaction is mined) 91 | From MonolithDAO Token.sol 92 | @param _spender The address which will spend the funds. 93 | @param _subtractedValue The amount of tokens to decrease the allowance by. 94 | @param _data ABI-encoded contract call to call `_spender` address. 95 | */ 96 | function decreaseApproval(address _spender, uint _subtractedValue, bytes _data) public returns (bool) { 97 | require(_spender != address(this)); 98 | super.decreaseApproval(_spender, _subtractedValue); 99 | require(_spender.call(_data)); 100 | return true; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bug Bounty Program 2 | 3 | Take part in our bounty program and help GoCryptobot become a killer blockchain game. 4 | 5 | [GoCryptobot](http://gocryptobot.io/) is an Ethereum-based mobile game fully playable on mobile. You can have 100% ownership of the in-game characters, upgrade them on run mode, trade them at the in-game exchange, and even have them battle on PvP mode. Join GoCryptobot, a fun and rewarding game that allows transparent and safe transactions among players. All this is possible on your mobile. 6 | 7 | In order to keep our smart contracts safe from security vulnerabilities, we have decided to split our bug bounty program into two phases. The first bug bounty program aims to find vulnerabilities in our in-game currency (ERC20) and in our game items (ERC721), whereas the second one aims to find issues in the smart contracts used in PvP mode and at the ERC20 exchange. You can join either or both of them by only sending us a pull request on Github. 8 | 9 | ## Bug Bounty Program 1 (closed) 10 | 11 | - Period: March 10~16th, 2018 12 | 13 | - Smart contract 1: ERC20 14 | 15 | GCC (ERC20) is an in-game currency used in GoCryptobot 16 | 17 | - Smart contract 2: ERC721 18 | 19 | GoCryptobot is a story about AI-installed robots that run from their exploiter humans in search for their creator, Doctor Z. The robots are composed of 4 parts: head, body, legs and buster which are upgraded throughout the game. Each part of the robot is a non-fungible token that complies with the ERC 721 specification. This means that the player has 100% ownership of the parts, trade and exchange them with other people even outside the game. At Kodebox, we are also developing a follow-up game where the player has the choice of using the same robots that he/she is using in GoCryptobot. 20 | 21 | ### Bug Classification 22 | 23 | Bugs are classified into 3 levels: P1 (high), P2 (medium) and P3 (low) 24 | 25 | - P1 (high): a security vulnerability that will result in loss of value 26 | 27 | e.g. Steal ERC20/721 tokens from someone, mint tokens at your discretion 28 | 29 | - P2 (medium): a security vulnerability that will not result in loss of value but can result in a showstopper of the game 30 | 31 | e.g. Block actions for all users 32 | 33 | - P3 (low): a security vulnerability that will not result in loss of value but can cause great inconvenience for some fraction of users 34 | 35 | e.g. Block a user from transferring tokens 36 | 37 | ### Bug Report Process 38 | 39 | Send us a [pull request on our Github repository](https://github.com/gocryptobot/contracts-bounty/pulls). 40 | We trust you are a decent developer with common sense, so actions such as reporting issues already reported by others, issues that are out of scope, abusive behavior, personal attacks, etc will be completely ignored. Please, be reminded that we will review the issues and assess if they fall into the bugs classification at our own discretion. 41 | 42 | Every contributor that has reported a bug that can be classified as P1, P2 and P3 issues will be rewarded. Please, use Github accounts that you own and use, so that we can contact you when we need your wallet address for sending the compensations. 43 | 44 | - P1 (high): 1 ETH 45 | - P2 (medium): 0.5 ETH 46 | - P3 (low): 0.3 ETH 47 | 48 | ## Bug Bounty Program 2 49 | 50 | - Period: April 15th ~ 21st, 2018 (it closes at 23:59pm KST) 51 | 52 | ### Smart contract 1: PvP mode 53 | 54 | #### Background Information 1: admission condition 55 | 56 | Each PvP match admits 128 cryptobots. The match is a competition among 4 teams and each teams has 32 cryptobots. The player has to choose one team to participate. Only the cryprotobots with their all 4 parts higher than level 5 are eligible for the match. 57 | 58 | #### Background Information 2: rules 59 | 60 | * PvP points 61 | 62 | The part of the cryptobot that you’re using for the match (either head, body, legs or booster) will be given points depending on its level. The total team score will be the sum of the points of each cryptobot. This means the higher level your cryptobot part is, the higher your contribution to the team. For example, AI is required for the PvP bowling match, so the level of the head of each cryptobot will determine the number of points. Tug-of-war requires strength (body part), racing requires speed (legs part), and high jumping requires power (booster part). 63 | 64 | * Lucky color 65 | 66 | Each match has two lucky colors; this is an information that is given to the players prior to the match. The smart contract chooses one as the luckier color (the probability is 0.5). Those parts that match the lucky color get extra points and the parts that match the lucky color chosen by the smart contract get additional extra points. 67 | 68 | #### Points calculation (actual smart contract) 69 | 70 | The total number of points that a part contributes is the sum of: 71 | `Part’s level` 72 | \+ `the sum of the skill level, if the part has either of the two lucky colors` 73 | \+ `the sum of the skill level, if the part has either of the two lucky colors and the cryptobot has theme effect` 74 | \+ `the sum of the skill level multiplied by 2, if the part has the lucky color chosen by the smart contract` 75 | 76 | 77 | ### Smart contract 2: ERC20 exchange 78 | 79 | GoCryptobot’s in-game currency is GCC which is pegged to ETH at 1:1000 ratio (1 ETH = 1000 GCC). 80 | Players can withdraw GCC from the game to their personal wallet. Whenever they want, they can exchange to ETH. The exchange commission is 5%. 81 | 82 | ### Bug Classification 83 | 84 | Bugs are classified into 3 levels: P1 (high), P2 (medium) and P3 (low) 85 | 86 | - P1 (high): a security vulnerability that will result in loss of value 87 | 88 | e.g. Steal ERC20/721 tokens from someone, mint tokens at your discretion 89 | 90 | - P2 (medium): a security vulnerability that will not result in loss of value but can result in a showstopper of the game 91 | 92 | e.g. Block actions for all users 93 | 94 | - P3 (low): a security vulnerability that will not result in loss of value but can cause great inconvenience for some fraction of users 95 | 96 | e.g. Block a user from transferring tokens 97 | 98 | ### Bug Report Process 99 | 100 | Send us a [pull request on our Github repository](https://github.com/gocryptobot/contracts-bounty/pulls). 101 | We trust you are a decent developer with common sense, so actions such as reporting issues already reported by others, issues that are out of scope, abusive behavior, personal attacks, etc will be completely ignored. Please, be reminded that we will review the issues and assess if they fall into the bugs classification at our own discretion. 102 | 103 | Every contributor that has reported a bug that can be classified as P1, P2 and P3 issues will be rewarded. Please participate with a Github account that you own and use, so that we can contact you to send you the reward. 104 | 105 | - P1 (high): 3 ETH 106 | - P2 (medium): 2 ETH 107 | - P3 (low): 1 ETH 108 | -------------------------------------------------------------------------------- /contracts/GoCryptobotCoin/GoCryptobotCoinERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | /** 4 | @title SafeMath 5 | @dev Math operations with safety checks that throw on error 6 | */ 7 | library SafeMath { 8 | 9 | /** 10 | @dev Multiplies two numbers, throws on overflow. 11 | */ 12 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 13 | if (a == 0) { 14 | return 0; 15 | } 16 | uint256 c = a * b; 17 | assert(c / a == b); 18 | return c; 19 | } 20 | 21 | /** 22 | @dev Integer division of two numbers, truncating the quotient. 23 | */ 24 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 25 | // assert(b > 0); // Solidity automatically throws when dividing by 0 26 | uint256 c = a / b; 27 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 28 | return c; 29 | } 30 | 31 | /** 32 | @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). 33 | */ 34 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 35 | assert(b <= a); 36 | return a - b; 37 | } 38 | 39 | /** 40 | @dev Adds two numbers, throws on overflow. 41 | */ 42 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 43 | uint256 c = a + b; 44 | assert(c >= a); 45 | return c; 46 | } 47 | } 48 | 49 | contract GoCryptobotCoinERC20 { 50 | using SafeMath for uint256; 51 | 52 | string public constant name = "GoCryptobotCoin"; 53 | string public constant symbol = "GCC"; 54 | uint8 public constant decimals = 3; 55 | 56 | mapping(address => uint256) balances; 57 | mapping (address => mapping (address => uint256)) internal allowed; 58 | 59 | uint256 totalSupply_; 60 | 61 | event Transfer(address indexed from, address indexed to, uint256 value); 62 | event Approval(address indexed owner, address indexed spender, uint256 value); 63 | 64 | /** 65 | @dev total number of tokens in existence 66 | */ 67 | function totalSupply() public view returns (uint256) { 68 | return totalSupply_; 69 | } 70 | 71 | /** 72 | @dev Gets the balance of the specified address. 73 | @param _owner The address to query the the balance of. 74 | @return An uint256 representing the amount owned by the passed address. 75 | */ 76 | function balanceOf(address _owner) public view returns (uint256 balance) { 77 | return balances[_owner]; 78 | } 79 | 80 | /** 81 | @dev transfer token for a specified address 82 | @param _to The address to transfer to. 83 | @param _value The amount to be transferred. 84 | */ 85 | function transfer(address _to, uint256 _value) public returns (bool) { 86 | require(_to != address(0)); 87 | require(_value <= balances[msg.sender]); 88 | 89 | // SafeMath.sub will throw if there is not enough balance. 90 | balances[msg.sender] = balances[msg.sender].sub(_value); 91 | balances[_to] = balances[_to].add(_value); 92 | Transfer(msg.sender, _to, _value); 93 | return true; 94 | } 95 | 96 | /** 97 | @dev Function to check the amount of tokens that an owner allowed to a spender. 98 | @param _owner address The address which owns the funds. 99 | @param _spender address The address which will spend the funds. 100 | @return A uint256 specifying the amount of tokens still available for the spender. 101 | */ 102 | function allowance(address _owner, address _spender) public view returns (uint256) { 103 | return allowed[_owner][_spender]; 104 | } 105 | 106 | /** 107 | @dev Transfer tokens from one address to another 108 | @param _from address The address which you want to send tokens from 109 | @param _to address The address which you want to transfer to 110 | @param _value uint256 the amount of tokens to be transferred 111 | */ 112 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 113 | require(_to != address(0)); 114 | require(_value <= balances[_from]); 115 | require(_value <= allowed[_from][msg.sender]); 116 | 117 | balances[_from] = balances[_from].sub(_value); 118 | balances[_to] = balances[_to].add(_value); 119 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); 120 | Transfer(_from, _to, _value); 121 | return true; 122 | } 123 | 124 | /** 125 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 126 | 127 | Beware that changing an allowance with this method brings the risk that someone may use both the old 128 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 129 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 130 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 131 | @param _spender The address which will spend the funds. 132 | @param _value The amount of tokens to be spent. 133 | */ 134 | function approve(address _spender, uint256 _value) public returns (bool) { 135 | allowed[msg.sender][_spender] = _value; 136 | Approval(msg.sender, _spender, _value); 137 | return true; 138 | } 139 | 140 | /** 141 | @dev Increase the amount of tokens that an owner allowed to a spender. 142 | 143 | approve should be called when allowed[_spender] == 0. To increment 144 | allowed value is better to use this function to avoid 2 calls (and wait until 145 | the first transaction is mined) 146 | From MonolithDAO Token.sol 147 | @param _spender The address which will spend the funds. 148 | @param _addedValue The amount of tokens to increase the allowance by. 149 | */ 150 | function increaseApproval(address _spender, uint _addedValue) public returns (bool) { 151 | allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); 152 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 153 | return true; 154 | } 155 | 156 | /** 157 | @dev Decrease the amount of tokens that an owner allowed to a spender. 158 | 159 | approve should be called when allowed[_spender] == 0. To decrement 160 | allowed value is better to use this function to avoid 2 calls (and wait until 161 | the first transaction is mined) 162 | From MonolithDAO Token.sol 163 | @param _spender The address which will spend the funds. 164 | @param _subtractedValue The amount of tokens to decrease the allowance by. 165 | */ 166 | function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { 167 | uint oldValue = allowed[msg.sender][_spender]; 168 | if (_subtractedValue > oldValue) { 169 | allowed[msg.sender][_spender] = 0; 170 | } else { 171 | allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); 172 | } 173 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 174 | return true; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /contracts/GoCryptobotNft/GoCryptobotNftOwnership.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./GoCryptobotNftAccessControl.sol"; 4 | 5 | contract GoCryptobotNftOwnership is GoCryptobotNftAccessControl { 6 | event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); 7 | event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); 8 | 9 | // Total amount of tokens 10 | uint256 private totalTokens; 11 | 12 | // Mapping from token ID to owner 13 | mapping (uint256 => address) private tokenOwner; 14 | 15 | // Mapping from token ID to approved address 16 | mapping (uint256 => address) private tokenApprovals; 17 | 18 | // Mapping from owner to list of owned token IDs 19 | mapping (address => uint256[]) private ownedTokens; 20 | 21 | // Mapping from token ID to index of the owner tokens list 22 | mapping(uint256 => uint256) private ownedTokensIndex; 23 | 24 | /** 25 | @dev Guarantees msg.sender is owner of the given token 26 | @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender 27 | */ 28 | modifier onlyOwnerOf(uint256 _tokenId) { 29 | require(ownerOf(_tokenId) == msg.sender); 30 | _; 31 | } 32 | 33 | /** 34 | @dev Gets the total amount of tokens stored by the contract 35 | @return uint256 representing the total amount of tokens 36 | */ 37 | function totalSupply() public view returns (uint256) { 38 | return totalTokens; 39 | } 40 | 41 | /** 42 | @dev Gets the balance of the specified address 43 | @param _owner address to query the balance of 44 | @return uint256 representing the amount owned by the passed address 45 | */ 46 | function balanceOf(address _owner) public view returns (uint256) { 47 | return ownedTokens[_owner].length; 48 | } 49 | 50 | /** 51 | @dev Gets the list of tokens owned by a given address 52 | @param _owner address to query the tokens of 53 | @return uint256[] representing the list of tokens owned by the passed address 54 | */ 55 | function tokensOf(address _owner) public view returns (uint256[]) { 56 | return ownedTokens[_owner]; 57 | } 58 | 59 | /** 60 | @dev Gets the owner of the specified token ID 61 | @param _tokenId uint256 ID of the token to query the owner of 62 | @return owner address currently marked as the owner of the given token ID 63 | */ 64 | function ownerOf(uint256 _tokenId) public view returns (address) { 65 | address owner = tokenOwner[_tokenId]; 66 | require(owner != address(0)); 67 | return owner; 68 | } 69 | 70 | /** 71 | @dev Gets the approved address to take ownership of a given token ID 72 | @param _tokenId uint256 ID of the token to query the approval of 73 | @return address currently approved to take ownership of the given token ID 74 | */ 75 | function approvedFor(uint256 _tokenId) public view returns (address) { 76 | return tokenApprovals[_tokenId]; 77 | } 78 | 79 | /** 80 | @dev Transfers the ownership of a given token ID to another address 81 | @param _to address to receive the ownership of the given token ID 82 | @param _tokenId uint256 ID of the token to be transferred 83 | */ 84 | function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) whenNotPaused { 85 | clearApprovalAndTransfer(msg.sender, _to, _tokenId); 86 | } 87 | 88 | /** 89 | @dev Approves another address to claim for the ownership of the given token ID 90 | @param _to address to be approved for the given token ID 91 | @param _tokenId uint256 ID of the token to be approved 92 | */ 93 | function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) whenNotPaused { 94 | address owner = ownerOf(_tokenId); 95 | require(_to != owner); 96 | if (approvedFor(_tokenId) != 0 || _to != 0) { 97 | tokenApprovals[_tokenId] = _to; 98 | Approval(owner, _to, _tokenId); 99 | } 100 | } 101 | 102 | /** 103 | @dev Claims the ownership of a given token ID 104 | @param _tokenId uint256 ID of the token being claimed by the msg.sender 105 | */ 106 | function takeOwnership(uint256 _tokenId) public whenNotPaused { 107 | require(isApprovedFor(msg.sender, _tokenId)); 108 | clearApprovalAndTransfer(ownerOf(_tokenId), msg.sender, _tokenId); 109 | } 110 | 111 | /** 112 | @dev Mint token function 113 | @param _to The address that will own the minted token 114 | @param _tokenId uint256 ID of the token to be minted by the msg.sender 115 | */ 116 | function _mint(address _to, uint256 _tokenId) internal { 117 | require(_to != address(0)); 118 | addToken(_to, _tokenId); 119 | Transfer(0x0, _to, _tokenId); 120 | } 121 | 122 | /** 123 | @dev Burns a specific token 124 | @param _tokenId uint256 ID of the token being burned by the msg.sender 125 | */ 126 | function _burn(uint256 _tokenId) onlyOwnerOf(_tokenId) internal { 127 | if (approvedFor(_tokenId) != 0) { 128 | clearApproval(msg.sender, _tokenId); 129 | } 130 | removeToken(msg.sender, _tokenId); 131 | Transfer(msg.sender, 0x0, _tokenId); 132 | } 133 | 134 | /** 135 | @dev Tells whether the msg.sender is approved for the given token ID or not 136 | This function is not private so it can be extended in further implementations like the operatable ERC721 137 | @param _owner address of the owner to query the approval of 138 | @param _tokenId uint256 ID of the token to query the approval of 139 | @return bool whether the msg.sender is approved for the given token ID or not 140 | */ 141 | function isApprovedFor(address _owner, uint256 _tokenId) internal view returns (bool) { 142 | return approvedFor(_tokenId) == _owner; 143 | } 144 | 145 | /** 146 | @dev Internal function to clear current approval and transfer the ownership of a given token ID 147 | @param _from address which you want to send tokens from 148 | @param _to address which you want to transfer the token to 149 | @param _tokenId uint256 ID of the token to be transferred 150 | */ 151 | function clearApprovalAndTransfer(address _from, address _to, uint256 _tokenId) internal { 152 | require(_to != address(0)); 153 | require(_to != ownerOf(_tokenId)); 154 | require(ownerOf(_tokenId) == _from); 155 | 156 | clearApproval(_from, _tokenId); 157 | removeToken(_from, _tokenId); 158 | addToken(_to, _tokenId); 159 | Transfer(_from, _to, _tokenId); 160 | } 161 | 162 | /** 163 | @dev Internal function to clear current approval of a given token ID 164 | @param _tokenId uint256 ID of the token to be transferred 165 | */ 166 | function clearApproval(address _owner, uint256 _tokenId) private { 167 | require(ownerOf(_tokenId) == _owner); 168 | tokenApprovals[_tokenId] = 0; 169 | Approval(_owner, 0, _tokenId); 170 | } 171 | 172 | /** 173 | @dev Internal function to add a token ID to the list of a given address 174 | @param _to address representing the new owner of the given token ID 175 | @param _tokenId uint256 ID of the token to be added to the tokens list of the given address 176 | */ 177 | function addToken(address _to, uint256 _tokenId) private { 178 | require(tokenOwner[_tokenId] == address(0)); 179 | tokenOwner[_tokenId] = _to; 180 | uint256 length = balanceOf(_to); 181 | ownedTokens[_to].push(_tokenId); 182 | ownedTokensIndex[_tokenId] = length; 183 | assert(uint256(totalTokens + 1) >= totalTokens); 184 | totalTokens = totalTokens + 1; 185 | } 186 | 187 | /** 188 | @dev Internal function to remove a token ID from the list of a given address 189 | @param _from address representing the previous owner of the given token ID 190 | @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address 191 | */ 192 | function removeToken(address _from, uint256 _tokenId) private { 193 | require(ownerOf(_tokenId) == _from); 194 | 195 | uint256 tokenIndex = ownedTokensIndex[_tokenId]; 196 | assert(balanceOf(_from) >= 1); 197 | uint256 lastTokenIndex = balanceOf(_from) - 1; 198 | uint256 lastToken = ownedTokens[_from][lastTokenIndex]; 199 | 200 | tokenOwner[_tokenId] = 0; 201 | ownedTokens[_from][tokenIndex] = lastToken; 202 | ownedTokens[_from][lastTokenIndex] = 0; 203 | // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to 204 | // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping 205 | // the lastToken to the first position, and then dropping the element placed in the last position of the list 206 | 207 | ownedTokens[_from].length--; 208 | ownedTokensIndex[_tokenId] = 0; 209 | ownedTokensIndex[lastToken] = tokenIndex; 210 | assert(totalTokens >= 1); 211 | totalTokens = totalTokens - 1; 212 | } 213 | } 214 | --------------------------------------------------------------------------------