├── deploy.sh ├── doc ├── img │ ├── deploy-pk.png │ ├── initialize-pk-txs.png │ ├── performance-observer.png │ └── send-pk-transfer-txs.png ├── pk-benchmarking.md ├── pk-preparation.md └── pk-interactive.md ├── .gitignore ├── contract ├── ExternalInterfaces │ ├── GeneScienceInterface.sol │ └── ConcurrentLib.sol ├── Zeppelin │ ├── Pausable.sol │ └── Ownable.sol ├── ERC721Draft.sol ├── Auction │ ├── SiringClockAuction.sol │ ├── SaleClockAuction.sol │ ├── ClockAuction.sol │ └── ClockAuctionBase.sol ├── KittyMinting.sol ├── KittyAuction.sol ├── KittyAccessControl.sol ├── KittyCore.sol ├── KittyOwnership.sol ├── GeneScience.sol ├── KittyBreeding.sol └── KittyBase.sol ├── parallel-kitties-test-scripts.md ├── prepare_transfer.py ├── README.md ├── utils.py ├── distribute_kitties_v2.py └── deploy_v2.py /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python deploy_v2.py $1 ../data/genesis_accounts_5m.txt 4 | 5 | -------------------------------------------------------------------------------- /doc/img/deploy-pk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcology-network/parallel-kitties/HEAD/doc/img/deploy-pk.png -------------------------------------------------------------------------------- /doc/img/initialize-pk-txs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcology-network/parallel-kitties/HEAD/doc/img/initialize-pk-txs.png -------------------------------------------------------------------------------- /doc/img/performance-observer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcology-network/parallel-kitties/HEAD/doc/img/performance-observer.png -------------------------------------------------------------------------------- /doc/img/send-pk-transfer-txs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcology-network/parallel-kitties/HEAD/doc/img/send-pk-transfer-txs.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /contract/ExternalInterfaces/GeneScienceInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /// @title defined the interface that will be referenced in main Kitty contract 5 | contract GeneScienceInterface { 6 | /// @dev simply a boolean to indicate this is the contract we expect to be 7 | function isGeneScience() public pure returns (bool); 8 | 9 | /// @dev given genes of kitten 1 & 2, return a genetic combination - may have a random factor 10 | /// @param genes1 genes of mom 11 | /// @param genes2 genes of sire 12 | /// @return the genes that are supposed to be passed down the child 13 | function mixGenes(uint256 genes1, uint256 genes2) public returns (uint256); 14 | } 15 | -------------------------------------------------------------------------------- /doc/pk-benchmarking.md: -------------------------------------------------------------------------------- 1 | # 1. Benchmarking Scripts 2 | 3 | This document shows you how to benchmark Arcology Network using the parallelized CryptoKitties. 4 | 5 | ## 1.1. Deploy Smart Contracts 6 | 7 | Suppose an Arcology node is running at http://192.168.1.111, in the Ammolite docker image, execute the command below. 8 | Replace the IP address if necessary. 9 | 10 | ```shell 11 | $ cd ~/parallel_kitties 12 | $ ./deploy.sh http://192.168.1.111:8080 13 | ``` 14 | 15 | ## 1.2. Created 2M Owners 16 | 17 | Call `sendtxs.py` to load a pre-generated transaction file and then send them to the test node through port `8080`. 18 | 19 | ```shell 20 | >python sendtxs.py http://192.168.1.106:8080 data/pk_init_gen0/pk_init_gen0_2m_01.out 21 | ``` 22 | 23 | ## 1.3. Transfer 1M Kitties 24 | When all the owners are successfully created, type in the following command to start kitty transfers. 25 | 26 | ```shell 27 | >python sendtxs.py http://192.168.1.106:8080 data/pk_kitty_transfer/pk_kitty_transfer_1m_01.dat 28 | ``` 29 | -------------------------------------------------------------------------------- /contract/Zeppelin/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | import "./Ownable.sol"; 5 | 6 | 7 | /** 8 | * @title Pausable 9 | * @dev Base contract which allows children to implement an emergency stop mechanism. 10 | */ 11 | contract Pausable is Ownable { 12 | event Pause(); 13 | event Unpause(); 14 | 15 | bool public paused = false; 16 | 17 | 18 | /** 19 | * @dev Modifier to make a function callable only when the contract is not paused. 20 | */ 21 | modifier whenNotPaused() { 22 | require(!paused); 23 | _; 24 | } 25 | 26 | /** 27 | * @dev Modifier to make a function callable only when the contract is paused. 28 | */ 29 | modifier whenPaused() { 30 | require(paused); 31 | _; 32 | } 33 | 34 | /** 35 | * @dev called by the owner to pause, triggers stopped state 36 | */ 37 | function pause() onlyOwner whenNotPaused public { 38 | paused = true; 39 | emit Pause(); 40 | } 41 | 42 | /** 43 | * @dev called by the owner to unpause, returns to normal state 44 | */ 45 | function unpause() onlyOwner whenPaused public { 46 | paused = false; 47 | emit Unpause(); 48 | } 49 | } -------------------------------------------------------------------------------- /contract/Zeppelin/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | address public owner; 11 | 12 | 13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 14 | 15 | 16 | /** 17 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 18 | * account. 19 | */ 20 | constructor() public { 21 | owner = msg.sender; 22 | } 23 | 24 | 25 | /** 26 | * @dev Throws if called by any account other than the owner. 27 | */ 28 | modifier onlyOwner() { 29 | require(msg.sender == owner); 30 | _; 31 | } 32 | 33 | 34 | /** 35 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 36 | * @param newOwner The address to transfer ownership to. 37 | */ 38 | function transferOwnership(address newOwner) public onlyOwner { 39 | require(newOwner != address(0)); 40 | emit OwnershipTransferred(owner, newOwner); 41 | owner = newOwner; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /parallel-kitties-test-scripts.md: -------------------------------------------------------------------------------- 1 | 2 | # Parallel Kitties Test Scripts 3 | 4 | - [Parallel Kitties Test Scripts](#parallel-kitties-test-scripts) 5 | - [1. Introduction](#1-introduction) 6 | - [2. Run the Test Scripts](#2-run-the-test-scripts) 7 | - [2.1. Deploy the Contract](#21-deploy-the-contract) 8 | - [2.2. Generate the First Generation](#22-generate-the-first-generation) 9 | - [2.3. Transfer Kitties](#23-transfer-kitties) 10 | 11 | ## 1. Introduction 12 | 13 | --- 14 | The CryptoKitties is one of the most popular NFT application on Ethereum platform. The original version only allow sequential execution. We optimized the CryptoKitties using Arcology's concurrent library to allow concurrent exeuction and named it ParallelKitties. 15 | 16 | ## 2. Run the Test Scripts 17 | 18 | --- 19 | 20 | ### 2.1. Deploy the Contract 21 | 22 | ```sh 23 | cd parallel_kitties 24 | python deploy_v2.py http://192.138.1.103:8080 ../data/genesis_accounts_200.txt 25 | ``` 26 | 27 | ### 2.2. Generate the First Generation 28 | 29 | ```sh 30 | python sendtxs.py http://192.138.1.103:8080 data/pk/pk_init_gen0_200.out 31 | ``` 32 | 33 | ### 2.3. Transfer Kitties 34 | 35 | ```sh 36 | python sendtxs.py http://192.138.1.103:8080 data/pk/pk_kitty_transfer_100.out 37 | ``` -------------------------------------------------------------------------------- /contract/ERC721Draft.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | 4 | /// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens 5 | /// @author Dieter Shirley (https://github.com/dete) 6 | contract ERC721 { 7 | function implementsERC721() public pure returns (bool); 8 | function totalSupply() public view returns (uint256 total); 9 | function balanceOf(address _owner) public view returns (uint256 balance); 10 | function ownerOf(uint256 _tokenId) public view returns (address owner); 11 | function approve(address _to, uint256 _tokenId) public; 12 | function transferFrom(address _from, address _to, uint256 _tokenId) public; 13 | function transfer(address _to, uint256 _tokenId) public; 14 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 15 | event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); 16 | 17 | // Optional 18 | // function name() public view returns (string name); 19 | // function symbol() public view returns (string symbol); 20 | // function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId); 21 | // function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl); 22 | } 23 | -------------------------------------------------------------------------------- /prepare_transfer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../..') 3 | 4 | from ammolite import (Cli, HTTPProvider, Account) 5 | from utils import compile_contracts 6 | from pymongo import MongoClient 7 | 8 | frontend = sys.argv[1] 9 | kitty_core_address = sys.argv[2] 10 | num_ktxs = int(sys.argv[3]) 11 | output = sys.argv[4] 12 | input = sys.argv[5] 13 | 14 | all_users = [] 15 | with open(input, 'r') as f: 16 | for line in f: 17 | line = line.rstrip('\n') 18 | segments = line.split(',') 19 | u = { 20 | 'private_key': segments[0], 21 | 'address': segments[1], 22 | 'kitty': segments[2], 23 | } 24 | all_users.append(u) 25 | 26 | cli = Cli(HTTPProvider(frontend)) 27 | compiled_sol = compile_contracts('./contract') 28 | kitty_core = compiled_sol['./contract/KittyCore.sol:KittyCore'] 29 | kitty_core_contract = cli.eth.contract( 30 | abi = kitty_core['abi'], 31 | address = kitty_core_address, 32 | ) 33 | 34 | num_per_batch = 1000 35 | lines = [] 36 | 37 | def make_one_batch(i): 38 | users = all_users[i * 2000 : (i+1) * 2000] 39 | 40 | for i in range(num_per_batch): 41 | acc = Account(users[i]['private_key']) 42 | raw_tx, tx_hash = acc.sign(kitty_core_contract.functions.transfer( 43 | users[num_per_batch + i]['address'], 44 | users[i]['kitty'], 45 | ).buildTransaction({ 46 | 'value': 0, 47 | 'gas': 100000000, 48 | 'gasPrice': 1, 49 | })) 50 | lines.append('{},{}\n'.format(raw_tx.hex(), tx_hash.hex())) 51 | 52 | for i in range(num_ktxs): 53 | make_one_batch(i) 54 | 55 | with open(output, 'w') as f: 56 | for l in lines: 57 | f.write(l) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parallelized CryptoKitties 2 | 3 | ## 1. Background 4 | 5 | NFT applications make a sizeable proportion of popular DApps today. The first well-know NFT game is CryptoKitties by Dapper Labs. First launched in 2017, CryptoKitties is still the largest nonfinancial DApp on Ethereum. It caused serious network congestion blockchain during to limited scalability of Ethereum platform. 6 | 7 | ## 2. Introduction 8 | 9 | Parallel-Kitties is the parallelized version of the original CryptoKitties core developed by Dapper Labs (https://www.dapperlabs.com) using the concurrency framework. 10 | The original CryptoKitties can be downloaded from https://github.com/dapperlabs/cryptokitties-bounty. The main goal of ParallelKitties is to demonstrate how a typical, 11 | real-world application can benefit from our parallel execution to achieve dramatic performance gains. 12 | 13 | ## 3. Changes 14 | 15 | The original implementation of `KittyBase.sol` would save thekitty information in an array. The kitties' IDs are their index numbers. When a new kitty is born, some info needs to be appended to the back of the array. The length of the array will be used for the next kitty's ID. We replaced this native array with a map, since native Ethereum arrays don't support concurrent push back. 16 | 17 | In the new implementation, kitty IDs are now the map keys that are generated by UUID generators. The map values are the kitty info. Since there is no way to get the length of a map Solidty as of 11/2022, we added a new variable `totalBalance` to keep track of total kitties, which will be updated in a deferred call. 18 | 19 | ## 4. Summary 20 | The new version achieved some astonishing results. Our version is about ~1000x faster than original on Ethereum. 21 | 22 | - Functionally identical to the original one 23 | - Minor changes to the data structure 24 | - 48 EVM instances in parallel 25 | - ~1000x faster than on Ethereum 26 | 27 | 28 | ## **5. Quick Start** 29 | 30 | If you are only interested in trying Arcology testnet out without diving into specific technical details, [then please check this document out for an easy start.](./parallel-kitties-test-scripts.md) 31 | 32 | ## 6. Tests 33 | 34 | - [Interactive](/doc/pk-interactive.md) 35 | - [Benchmarking](/doc/pk-benchmarking.md) 36 | -------------------------------------------------------------------------------- /contract/Auction/SiringClockAuction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./ClockAuction.sol"; 4 | 5 | /// @title Reverse auction modified for siring 6 | contract SiringClockAuction is ClockAuction { 7 | 8 | // @dev Sanity check that allows us to ensure that we are pointing to the 9 | // right auction in our setSiringAuctionAddress() call. 10 | bool public isSiringClockAuction = true; 11 | 12 | // Delegate constructor 13 | constructor(address _nftAddr, uint256 _cut) public 14 | ClockAuction(_nftAddr, _cut) {} 15 | 16 | /// @dev Creates and begins a new auction. Since this function is wrapped, 17 | /// require sender to be KittyCore contract. 18 | /// @param _tokenId - ID of token to auction, sender must be owner. 19 | /// @param _startingPrice - Price of item (in wei) at beginning of auction. 20 | /// @param _endingPrice - Price of item (in wei) at end of auction. 21 | /// @param _duration - Length of auction (in seconds). 22 | /// @param _seller - Seller, if not the message sender 23 | function createAuction( 24 | uint256 _tokenId, 25 | uint256 _startingPrice, 26 | uint256 _endingPrice, 27 | uint256 _duration, 28 | address _seller 29 | ) 30 | public 31 | canBeStoredWith128Bits(_startingPrice) 32 | canBeStoredWith128Bits(_endingPrice) 33 | canBeStoredWith64Bits(_duration) 34 | { 35 | require(msg.sender == address(nonFungibleContract)); 36 | _escrow(_seller, _tokenId); 37 | Auction memory auction = Auction( 38 | _seller, 39 | uint128(_startingPrice), 40 | uint128(_endingPrice), 41 | uint64(_duration), 42 | uint64(now) 43 | ); 44 | _addAuction(_tokenId, auction); 45 | } 46 | 47 | /// @dev Places a bid for siring. Requires the sender 48 | /// is the KittyCore contract because all bid methods 49 | /// should be wrapped. Also returns the kitty to the 50 | /// seller rather than the winner. 51 | function bid(uint256 _tokenId) 52 | public 53 | payable 54 | { 55 | require(msg.sender == address(nonFungibleContract)); 56 | address seller = tokenIdToAuction[_tokenId].seller; 57 | // _bid checks that token ID is valid and will throw if bid fails 58 | _bid(_tokenId, msg.value); 59 | // We transfer the kitty back to the seller, the winner will get 60 | // the offspring 61 | _transfer(seller, _tokenId); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../..') 3 | 4 | import time 5 | import os 6 | from solcx import compile_files 7 | from ammolite import (Account) 8 | 9 | def wait_receipts(cli, hashes): 10 | n = len(hashes) 11 | receipts = {} 12 | retry_time = 0 13 | while True: 14 | rs = cli.getTransactionReceipts(hashes) 15 | if rs is None or len(rs) == 0: 16 | continue 17 | 18 | remains = [] 19 | for h in hashes: 20 | if h not in rs: 21 | remains.append(h) 22 | else: 23 | receipts[h] = rs[h] 24 | 25 | if len(receipts) == n: 26 | return receipts 27 | 28 | hashes = remains 29 | time.sleep(3) 30 | 31 | def wait_for_receipts(cli, hashes): 32 | # print(hashes) 33 | receipts = {} 34 | retry_time = 0 35 | while True: 36 | rs = cli.getTransactionReceipts(hashes) 37 | if rs is None or len(rs) == 0: 38 | continue 39 | if len(rs) != len(hashes): 40 | time.sleep(1) 41 | retry_time += 1 42 | if retry_time > 120: 43 | print('expected = {}, got = {}'.format(len(hashes), len(rs))) 44 | print('-----------------------------------------------------------------------------------', file=sys.stderr) 45 | for h in hashes: 46 | print(h.hex(), file=sys.stderr) 47 | exit(1) 48 | continue 49 | remains = [] 50 | for i in range(len(rs)): 51 | # print(rs[i]) 52 | # print(hashes[i]) 53 | if rs[i] is not None: 54 | receipts[hashes[i]] = rs[i] 55 | else: 56 | remains.append(hashes[i]) 57 | if len(remains) == 0: 58 | return receipts 59 | 60 | time.sleep(3) 61 | hashes = remains 62 | 63 | def compile_contracts(dir): 64 | sources = [] 65 | for root, _, files in os.walk('./contract'): 66 | for file in files: 67 | if file.endswith('.sol'): 68 | sources.append(os.path.join(root, file)) 69 | 70 | # print(sources) 71 | return compile_files(sources, output_values = ['abi', 'bin']) 72 | 73 | def check_receipts(receipts): 74 | #print(receipts) 75 | for r in receipts.values(): 76 | if r['status'] != 1: 77 | assert False 78 | 79 | def init_accounts(file): 80 | accounts = [] 81 | with open(file, 'r') as f: 82 | for key in f: 83 | # print('---' + key + '---') 84 | accounts.append(Account(key[:-1])) 85 | return accounts 86 | -------------------------------------------------------------------------------- /contract/KittyMinting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // Auction wrapper functions 4 | import "./KittyAuction.sol"; 5 | 6 | /// @title all functions related to creating kittens 7 | contract KittyMinting is KittyAuction { 8 | 9 | // Limits the number of cats the contract owner can ever create. 10 | uint256 public promoCreationLimit = 5000; 11 | uint256 public gen0CreationLimit = 50000; 12 | 13 | // Constants for gen0 auctions. 14 | uint256 public gen0StartingPrice = 10 finney; 15 | uint256 public gen0AuctionDuration = 1 days; 16 | 17 | // Counts the number of cats the contract owner has created. 18 | uint256 public promoCreatedCount; 19 | uint256 public gen0CreatedCount; 20 | 21 | /// @dev we can create promo kittens, up to a limit. Only callable by COO 22 | /// @param _genes the encoded genes of the kitten to be created, any value is accepted 23 | /// @param _owner the future owner of the created kittens. Default to contract COO 24 | function createPromoKitty(uint256 _genes, address _owner) public onlyCOO { 25 | if (_owner == address(0)) { 26 | _owner = cooAddress; 27 | } 28 | require(promoCreatedCount < promoCreationLimit); 29 | require(gen0CreatedCount < gen0CreationLimit); 30 | 31 | /* 32 | promoCreatedCount++; 33 | gen0CreatedCount++; 34 | */ 35 | _createKitty(0, 0, 0, _genes, _owner); 36 | } 37 | 38 | /// @dev Creates a new gen0 kitty with the given genes and 39 | /// creates an auction for it. 40 | function createGen0Auction(uint256 _genes) public onlyCOO { 41 | require(gen0CreatedCount < gen0CreationLimit); 42 | 43 | uint256 kittyId = _createKitty(0, 0, 0, _genes, address(this)); 44 | _approve(kittyId, address(saleAuction)); 45 | 46 | saleAuction.createAuction( 47 | kittyId, 48 | _computeNextGen0Price(), 49 | 0, 50 | gen0AuctionDuration, 51 | address(this) 52 | ); 53 | 54 | /* 55 | gen0CreatedCount++; 56 | */ 57 | } 58 | 59 | /// @dev Computes the next gen0 auction starting price, given 60 | /// the average of the past 5 prices + 50%. 61 | function _computeNextGen0Price() internal view returns (uint256) { 62 | uint256 avePrice = saleAuction.averageGen0SalePrice(); 63 | 64 | // sanity check to ensure we don't overflow arithmetic (this big number is 2^128-1). 65 | require(avePrice < 340282366920938463463374607431768211455); 66 | 67 | uint256 nextPrice = avePrice + (avePrice / 2); 68 | 69 | // We never auction for less than starting price 70 | if (nextPrice < gen0StartingPrice) { 71 | nextPrice = gen0StartingPrice; 72 | } 73 | 74 | return nextPrice; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contract/Auction/SaleClockAuction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./ClockAuction.sol"; 4 | 5 | /// @title Clock auction modified for sale of kitties 6 | contract SaleClockAuction is ClockAuction { 7 | 8 | // @dev Sanity check that allows us to ensure that we are pointing to the 9 | // right auction in our setSaleAuctionAddress() call. 10 | bool public isSaleClockAuction = true; 11 | 12 | // Tracks last 5 sale price of gen0 kitty sales 13 | uint256 public gen0SaleCount; 14 | uint256[5] public lastGen0SalePrices; 15 | 16 | // Delegate constructor 17 | constructor(address _nftAddr, uint256 _cut) public 18 | ClockAuction(_nftAddr, _cut) {} 19 | 20 | /// @dev Creates and begins a new auction. 21 | /// @param _tokenId - ID of token to auction, sender must be owner. 22 | /// @param _startingPrice - Price of item (in wei) at beginning of auction. 23 | /// @param _endingPrice - Price of item (in wei) at end of auction. 24 | /// @param _duration - Length of auction (in seconds). 25 | /// @param _seller - Seller, if not the message sender 26 | function createAuction( 27 | uint256 _tokenId, 28 | uint256 _startingPrice, 29 | uint256 _endingPrice, 30 | uint256 _duration, 31 | address _seller 32 | ) 33 | public 34 | canBeStoredWith128Bits(_startingPrice) 35 | canBeStoredWith128Bits(_endingPrice) 36 | canBeStoredWith64Bits(_duration) 37 | { 38 | require(msg.sender == address(nonFungibleContract)); 39 | _escrow(_seller, _tokenId); 40 | Auction memory auction = Auction( 41 | _seller, 42 | uint128(_startingPrice), 43 | uint128(_endingPrice), 44 | uint64(_duration), 45 | uint64(now) 46 | ); 47 | _addAuction(_tokenId, auction); 48 | } 49 | 50 | /// @dev Updates lastSalePrice if seller is the nft contract 51 | /// Otherwise, works the same as default bid method. 52 | function bid(uint256 _tokenId) 53 | public 54 | payable 55 | { 56 | // _bid verifies token ID size 57 | address seller = tokenIdToAuction[_tokenId].seller; 58 | uint256 price = _bid(_tokenId, msg.value); 59 | _transfer(msg.sender, _tokenId); 60 | 61 | /* 62 | // If not a gen0 auction, exit 63 | if (seller == address(nonFungibleContract)) { 64 | // Track gen0 sale prices 65 | lastGen0SalePrices[gen0SaleCount % 5] = price; 66 | gen0SaleCount++; 67 | } 68 | */ 69 | } 70 | 71 | function averageGen0SalePrice() public view returns (uint256) { 72 | uint256 sum = 0; 73 | for (uint256 i = 0; i < 5; i++) { 74 | sum += lastGen0SalePrices[i]; 75 | } 76 | return sum / 5; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /distribute_kitties_v2.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../..') 3 | 4 | import math 5 | from ammolite import (Cli, HTTPProvider, Account) 6 | from utils import (wait_for_receipts, compile_contracts) 7 | from pymongo import MongoClient 8 | 9 | frontend = sys.argv[1] 10 | accounts_file = sys.argv[2] 11 | kitty_core_address = sys.argv[3] 12 | coo_private_key = sys.argv[4] 13 | #database = sys.argv[5] 14 | output1 = sys.argv[5] 15 | output2 = sys.argv[6] 16 | 17 | private_keys = [] 18 | addresses = [] 19 | with open(accounts_file, 'r') as f: 20 | for line in f: 21 | line = line.rstrip('\n') 22 | segments = line.split(',') 23 | private_keys.append(segments[0]) 24 | addresses.append(segments[1]) 25 | 26 | cli = Cli(HTTPProvider(frontend)) 27 | compiled_sol = compile_contracts('./contract') 28 | kitty_core = compiled_sol['./contract/KittyCore.sol:KittyCore'] 29 | kitty_core_contract = cli.eth.contract( 30 | abi = kitty_core['abi'], 31 | address = kitty_core_address, 32 | ) 33 | 34 | #mongo = MongoClient('localhost', 32768) 35 | #db = mongo[database] 36 | 37 | lines1 = [] 38 | lines2 = [] 39 | coo = Account(coo_private_key) 40 | num_batches = int(math.ceil(len(private_keys)) / 1000) 41 | for i in range(num_batches): 42 | batch_start = i * 1000 43 | batch_end = (i + 1) * 1000 44 | if i == num_batches - 1: 45 | batch_end = len(private_keys) 46 | print('batch_start = {}, batch_end = {}'.format(batch_start, batch_end)) 47 | 48 | txs = {} 49 | hashes = [] 50 | for j in range(batch_start, batch_end): 51 | raw_tx, tx_hash = coo.sign(kitty_core_contract.functions.createPromoKitty(j, addresses[j]).buildTransaction({ 52 | 'nonce': j, 53 | 'gas': 1000000, 54 | 'gasPrice': 1, 55 | })) 56 | txs[tx_hash] = raw_tx 57 | hashes.append(tx_hash) 58 | line = '{},{}'.format(raw_tx.hex(), tx_hash.hex()) 59 | lines1.append(line) 60 | 61 | cli.sendTransactions(txs) 62 | candidates = [] 63 | receipts = wait_for_receipts(cli, hashes) 64 | for j in range(len(hashes)): 65 | receipt = receipts[hashes[j]] 66 | if receipt['status'] != 1: 67 | assert False 68 | 69 | processed_receipt = kitty_core_contract.processReceipt(receipt) 70 | if 'Birth' not in processed_receipt: 71 | assert False 72 | 73 | #candidates.append({ 74 | # 'private_key': private_keys[batch_start + j], 75 | # 'address': addresses[batch_start + j], 76 | # 'kitty': processed_receipt['Birth']['kittyId'], 77 | #}) 78 | line = '{},{},{}'.format(private_keys[batch_start + j], addresses[batch_start + j], processed_receipt['Birth']['kittyId']) 79 | lines2.append(line) 80 | #db.candidates.insert_many(candidates) 81 | 82 | print('len(lines1) = {}, line(lines2) = {}'.format(len(lines1), len(lines2))) 83 | 84 | with open(output1, 'a') as f: 85 | for l in lines1: 86 | f.write(l+'\n') 87 | 88 | with open(output2, 'a') as f: 89 | for l in lines2: 90 | f.write(l+'\n') 91 | -------------------------------------------------------------------------------- /doc/pk-preparation.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | Generate N accounts for the test with accgen.py: 4 | 5 | ```shell 6 | $python3 accgen.py 7 | ``` 8 | 9 | Run the deploy script: 10 | 11 | python3 deploy.py \ \ 12 | 13 | * **config-file**: The name of the config file; 14 | * **account-file**: The name of the account file. 15 | 16 | **Format of the config file** 17 | 18 | ```YAML 19 | frontend: http://localhost:8080 20 | gen0_count: 1000 21 | test_case_weights: 22 | sale_auction: 1 23 | siring_auction_creator: 1 24 | siring_auction_bidder: 1 25 | kitty_raiser: 1 26 | kitties_exchanger: 1 27 | ``` 28 | 29 | * **frontend**: The service url of the frontend service; 30 | * **gen0_count**: The number of the gen0 kitties; 31 | * **test_case_weights**: The weight of each test case. 32 | 33 | Example: 34 | 35 | ```shell 36 | $ python3 deploy.py config.yaml accounts.txt 37 | Deploy ParallelKitties complete. 38 | python3 init_gen0.py http://localhost:8080 1000 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb cfac4f5fa828072ba8313b0686f02f576fa0fc8caba947569429e88968577865 39 | python3 kitty_miner.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 42e30ad7f9b7ccb4c19d14277c76c15fddc461548be3102dcb4bbfd7b602c07a 40 | python3 sale_auction_creator_and_bidder.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc1.txt 41 | python3 siring_auction_creator.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb 0x77b35dce39fb045ae99bbd7105fa2e89c60d36cf acc2.txt 42 | python3 siring_auction_bidder.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb 0x77b35dce39fb045ae99bbd7105fa2e89c60d36cf acc3.txt 43 | python3 kitty_raiser.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc4.txt 44 | python3 kitties_exchanger.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc5.txt 45 | ``` 46 | 47 | Init gen0 kitties with init_gen0.py: 48 | 49 | ```shell 50 | $ python3 init_gen0.py http://localhost:8080 1000 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb 51 | ``` 52 | 53 | Run kitty_miner.py before starting all the other tests: 54 | 55 | ```shell 56 | $ python3 kitty_miner.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 42e30ad7f9b7ccb4c19d14277c76c15fddc461548be3102dcb4bbfd7b602c07a 57 | ``` 58 | 59 | ### Deployment 60 | 61 | Copy the output of deploy.py to terminal to run the tests: 62 | 63 | ```shell 64 | python3 sale_auction_creator_and_bidder.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc1.txt 65 | python3 siring_auction_creator.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb 0x77b35dce39fb045ae99bbd7105fa2e89c60d36cf acc2.txt 66 | python3 siring_auction_bidder.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb 0x77b35dce39fb045ae99bbd7105fa2e89c60d36cf acc3.txt 67 | python3 kitty_raiser.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc4.txt 68 | python3 kitties_exchanger.py http://localhost:8080 0x110f04e4690b504638ef281a8e190b00aedf1153 0x1af90c9395ad05e29f8698d6a118bbc47e6af7cb acc5.txt 69 | ``` 70 | 71 | > Run each script in a separate terminal, press Ctrl+C to stop the test. -------------------------------------------------------------------------------- /contract/KittyAuction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./KittyBreeding.sol"; 4 | 5 | /// @title Handles creating auctions for sale and siring of kitties. 6 | /// This wrapper of ReverseAuction exists only so that users can create 7 | /// auctions with only one transaction. 8 | contract KittyAuction is KittyBreeding { 9 | /// @dev Put a kitty up for auction. 10 | /// Does some ownership trickery to create auctions in one tx. 11 | function createSaleAuction( 12 | uint256 _kittyId, 13 | uint256 _startingPrice, 14 | uint256 _endingPrice, 15 | uint256 _duration 16 | ) 17 | public 18 | whenNotPaused 19 | { 20 | // Auction contract checks input sizes 21 | // If kitty is already on any auction, this will throw 22 | // because it will be owned by the auction contract. 23 | require(_owns(msg.sender, _kittyId)); 24 | _approve(_kittyId, address(saleAuction)); 25 | // Sale auction throws if inputs are invalid and clears 26 | // transfer and sire approval after escrowing the kitty. 27 | saleAuction.createAuction( 28 | _kittyId, 29 | _startingPrice, 30 | _endingPrice, 31 | _duration, 32 | msg.sender 33 | ); 34 | } 35 | 36 | /// @dev Put a kitty up for auction to be sire. 37 | /// Performs checks to ensure the kitty can be sired, then 38 | /// delegates to reverse auction. 39 | function createSiringAuction( 40 | uint256 _kittyId, 41 | uint256 _startingPrice, 42 | uint256 _endingPrice, 43 | uint256 _duration 44 | ) 45 | public 46 | whenNotPaused 47 | { 48 | // Auction contract checks input sizes 49 | // If kitty is already on any auction, this will throw 50 | // because it will be owned by the auction contract. 51 | require(_owns(msg.sender, _kittyId)); 52 | require(isReadyToBreed(_kittyId)); 53 | _approve(_kittyId, address(siringAuction)); 54 | // Siring auction throws if inputs are invalid and clears 55 | // transfer and sire approval after escrowing the kitty. 56 | siringAuction.createAuction( 57 | _kittyId, 58 | _startingPrice, 59 | _endingPrice, 60 | _duration, 61 | msg.sender 62 | ); 63 | } 64 | 65 | /// @dev Completes a siring auction by bidding. 66 | /// Immediately breeds the winning matron with the sire on auction. 67 | /// @param _sireId - ID of the sire on auction. 68 | /// @param _matronId - ID of the matron owned by the bidder. 69 | function bidOnSiringAuction( 70 | uint256 _sireId, 71 | uint256 _matronId 72 | ) 73 | public 74 | payable 75 | whenNotPaused 76 | { 77 | // Auction contract checks input sizes 78 | require(_owns(msg.sender, _matronId)); 79 | require(isReadyToBreed(_matronId)); 80 | require(_canBreedWithViaAuction(_matronId, _sireId)); 81 | uint256 currPrice = siringAuction.getCurrentPrice(_sireId); 82 | uint256 bidAmount = msg.value; 83 | bool doAutoBirth = false; 84 | 85 | if (bidAmount >= currPrice + autoBirthFee) { 86 | bidAmount -= autoBirthFee; 87 | doAutoBirth = true; 88 | } 89 | 90 | // Siring auction will throw if the bid fails. 91 | siringAuction.bid.value(bidAmount)(_sireId); 92 | _breedWith(uint32(_matronId), uint32(_sireId)); 93 | 94 | if (doAutoBirth) { 95 | // Auto birth fee provided, trigger autobirth event 96 | Kitty storage matron = kitties[_matronId]; 97 | emit AutoBirth(_matronId, matron.cooldownEndTime); 98 | } 99 | } 100 | 101 | /// @dev Transfers the balance of the sale auction contract 102 | /// to the KittyCore contract. We use two-step withdrawal to 103 | /// prevent two transfer calls in the auction bid function. 104 | function withdrawAuctionBalances() external onlyCOO { 105 | saleAuction.withdrawBalance(); 106 | siringAuction.withdrawBalance(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /contract/KittyAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /// @title A facet of KittyCore that manages special access privileges. 4 | /// @author Axiom Zen (https://www.axiomzen.co) 5 | /// @dev See the KittyCore contract documentation to understand how the various contract facets are arranged. 6 | contract KittyAccessControl { 7 | // This facet controls access control for CryptoKitties. There are four roles managed here: 8 | // 9 | // - The CEO: The CEO can reassign other roles and change the addresses of our dependent smart 10 | // contracts. It is also the only role that can unpause the smart contract. It is initially 11 | // set to the address that created the smart contract in the KittyCore constructor. 12 | // 13 | // - The CFO: The CFO can withdraw funds from KittyCore and its auction contracts. 14 | // 15 | // - The COO: The COO can release gen0 kitties to auction, and mint promo cats. 16 | // 17 | // It should be noted that these roles are distinct without overlap in their access abilities, the 18 | // abilities listed for each role above are exhaustive. In particular, while the CEO can assign any 19 | // address to any role, the CEO address itself doesn't have the ability to act in those roles. This 20 | // restriction is intentional so that we aren't tempted to use the CEO address frequently out of 21 | // convenience. The less we use an address, the less likely it is that we somehow compromise the 22 | // account. 23 | 24 | /// @dev Emited when contract is upgraded - See README.md for updgrade plan 25 | event ContractUpgrade(address newContract); 26 | 27 | // The addresses of the accounts (or contracts) that can execute actions within each roles. 28 | address public ceoAddress; 29 | address payable public cfoAddress; 30 | address public cooAddress; 31 | 32 | // @dev Keeps track whether the contract is paused. When that is true, most actions are blocked 33 | bool public paused = false; 34 | 35 | /// @dev Access modifier for CEO-only functionality 36 | modifier onlyCEO() { 37 | require(msg.sender == ceoAddress); 38 | _; 39 | } 40 | 41 | /// @dev Access modifier for CFO-only functionality 42 | modifier onlyCFO() { 43 | require(msg.sender == cfoAddress); 44 | _; 45 | } 46 | 47 | /// @dev Access modifier for COO-only functionality 48 | modifier onlyCOO() { 49 | require(msg.sender == cooAddress); 50 | _; 51 | } 52 | 53 | modifier onlyCLevel() { 54 | require( 55 | msg.sender == cooAddress || 56 | msg.sender == ceoAddress || 57 | msg.sender == cfoAddress 58 | ); 59 | _; 60 | } 61 | 62 | /// @dev Assigns a new address to act as the CEO. Only available to the current CEO. 63 | /// @param _newCEO The address of the new CEO 64 | function setCEO(address _newCEO) public onlyCEO { 65 | require(_newCEO != address(0)); 66 | 67 | ceoAddress = _newCEO; 68 | } 69 | 70 | /// @dev Assigns a new address to act as the CFO. Only available to the current CEO. 71 | /// @param _newCFO The address of the new CFO 72 | function setCFO(address _newCFO) public onlyCEO { 73 | require(_newCFO != address(0)); 74 | 75 | cfoAddress = address(uint160(_newCFO)); 76 | } 77 | 78 | /// @dev Assigns a new address to act as the COO. Only available to the current CEO. 79 | /// @param _newCOO The address of the new COO 80 | function setCOO(address _newCOO) public onlyCEO { 81 | require(_newCOO != address(0)); 82 | 83 | cooAddress = _newCOO; 84 | } 85 | 86 | function withdrawBalance() external onlyCFO { 87 | cfoAddress.transfer(address(this).balance); 88 | } 89 | 90 | 91 | /*** Pausable functionality adapted from OpenZeppelin ***/ 92 | 93 | /// @dev Modifier to allow actions only when the contract IS NOT paused 94 | modifier whenNotPaused() { 95 | require(!paused); 96 | _; 97 | } 98 | 99 | /// @dev Modifier to allow actions only when the contract IS paused 100 | modifier whenPaused { 101 | require(paused); 102 | _; 103 | } 104 | 105 | /// @dev Called by any "C-level" role to pause the contract. Used only when 106 | /// a bug or exploit is detected and we need to limit damage. 107 | function pause() public onlyCLevel whenNotPaused { 108 | paused = true; 109 | } 110 | 111 | /// @dev Unpauses the smart contract. Can only be called by the CEO, since 112 | /// one reason we may pause the contract is when CFO or COO accounts are 113 | /// compromised. 114 | function unpause() public onlyCEO whenPaused { 115 | // can't unpause if contract was upgraded 116 | paused = false; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /contract/Auction/ClockAuction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./../ERC721Draft.sol"; 4 | import "./ClockAuctionBase.sol"; 5 | import "../Zeppelin/Pausable.sol"; 6 | 7 | /// @title Clock auction for non-fungible tokens. 8 | contract ClockAuction is Pausable, ClockAuctionBase { 9 | 10 | /// @dev Constructor creates a reference to the NFT ownership contract 11 | /// and verifies the owner cut is in the valid range. 12 | /// @param _nftAddress - address of a deployed contract implementing 13 | /// the Nonfungible Interface. 14 | /// @param _cut - percent cut the owner takes on each auction, must be 15 | /// between 0-10,000. 16 | constructor(address _nftAddress, uint256 _cut) public { 17 | require(_cut <= 10000); 18 | ownerCut = _cut; 19 | 20 | ERC721 candidateContract = ERC721(_nftAddress); 21 | require(candidateContract.implementsERC721()); 22 | nonFungibleContract = candidateContract; 23 | } 24 | 25 | /// @dev Remove all Ether from the contract, which is the owner's cuts 26 | /// as well as any Ether sent directly to the contract address. 27 | /// Always transfers to the NFT contract, but can be called either by 28 | /// the owner or the NFT contract. 29 | function withdrawBalance() external { 30 | address payable nftAddress = address(uint160(address(nonFungibleContract))); 31 | 32 | require( 33 | msg.sender == owner || 34 | msg.sender == nftAddress 35 | ); 36 | nftAddress.transfer(address(this).balance); 37 | } 38 | 39 | /// @dev Creates and begins a new auction. 40 | /// @param _tokenId - ID of token to auction, sender must be owner. 41 | /// @param _startingPrice - Price of item (in wei) at beginning of auction. 42 | /// @param _endingPrice - Price of item (in wei) at end of auction. 43 | /// @param _duration - Length of time to move between starting 44 | /// price and ending price (in seconds). 45 | /// @param _seller - Seller, if not the message sender 46 | function createAuction( 47 | uint256 _tokenId, 48 | uint256 _startingPrice, 49 | uint256 _endingPrice, 50 | uint256 _duration, 51 | address _seller 52 | ) 53 | public 54 | whenNotPaused 55 | canBeStoredWith128Bits(_startingPrice) 56 | canBeStoredWith128Bits(_endingPrice) 57 | canBeStoredWith64Bits(_duration) 58 | { 59 | require(_owns(msg.sender, _tokenId)); 60 | _escrow(msg.sender, _tokenId); 61 | Auction memory auction = Auction( 62 | _seller, 63 | uint128(_startingPrice), 64 | uint128(_endingPrice), 65 | uint64(_duration), 66 | uint64(now) 67 | ); 68 | _addAuction(_tokenId, auction); 69 | } 70 | 71 | /// @dev Bids on an open auction, completing the auction and transferring 72 | /// ownership of the NFT if enough Ether is supplied. 73 | /// @param _tokenId - ID of token to bid on. 74 | function bid(uint256 _tokenId) 75 | public 76 | payable 77 | whenNotPaused 78 | { 79 | // _bid will throw if the bid or funds transfer fails 80 | _bid(_tokenId, msg.value); 81 | _transfer(msg.sender, _tokenId); 82 | } 83 | 84 | /// @dev Cancels an auction that hasn't been won yet. 85 | /// Returns the NFT to original owner. 86 | /// @notice This is a state-modifying function that can 87 | /// be called while the contract is paused. 88 | /// @param _tokenId - ID of token on auction 89 | function cancelAuction(uint256 _tokenId) 90 | public 91 | { 92 | Auction storage auction = tokenIdToAuction[_tokenId]; 93 | require(_isOnAuction(auction)); 94 | address seller = auction.seller; 95 | require(msg.sender == seller); 96 | _cancelAuction(_tokenId, seller); 97 | } 98 | 99 | /// @dev Cancels an auction when the contract is paused. 100 | /// Only the owner may do this, and NFTs are returned to 101 | /// the seller. This should only be used in emergencies. 102 | /// @param _tokenId - ID of the NFT on auction to cancel. 103 | function cancelAuctionWhenPaused(uint256 _tokenId) 104 | whenPaused 105 | onlyOwner 106 | public 107 | { 108 | Auction storage auction = tokenIdToAuction[_tokenId]; 109 | require(_isOnAuction(auction)); 110 | _cancelAuction(_tokenId, auction.seller); 111 | } 112 | 113 | /// @dev Returns auction info for an NFT on auction. 114 | /// @param _tokenId - ID of NFT on auction. 115 | function getAuction(uint256 _tokenId) 116 | public 117 | view 118 | returns 119 | ( 120 | address seller, 121 | uint256 startingPrice, 122 | uint256 endingPrice, 123 | uint256 duration, 124 | uint256 startedAt 125 | ) { 126 | Auction storage auction = tokenIdToAuction[_tokenId]; 127 | require(_isOnAuction(auction)); 128 | return ( 129 | auction.seller, 130 | auction.startingPrice, 131 | auction.endingPrice, 132 | auction.duration, 133 | auction.startedAt 134 | ); 135 | } 136 | 137 | /// @dev Returns the current price of an auction. 138 | /// @param _tokenId - ID of the token price we are checking. 139 | function getCurrentPrice(uint256 _tokenId) 140 | public 141 | view 142 | returns (uint256) 143 | { 144 | Auction storage auction = tokenIdToAuction[_tokenId]; 145 | require(_isOnAuction(auction)); 146 | return _currentPrice(auction); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /contract/ExternalInterfaces/ConcurrentLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract UUID { 4 | function gen(string calldata id) external pure returns (uint256); 5 | } 6 | 7 | contract System { 8 | function createDefer(string calldata id, string calldata signature) external; 9 | function callDefer(string calldata id) external; 10 | } 11 | 12 | contract DynamicArray { 13 | function create(string calldata id, uint256 elemType) external; 14 | function length(string calldata id) external view returns(uint256); 15 | 16 | function pushBack(string calldata id, uint256 elem) external; 17 | function pushBack(string calldata id, address elem) external; 18 | function pushBack(string calldata id, bytes calldata elem) external; 19 | 20 | function tryPopFrontUint256(string calldata id) external returns(uint256, bool); 21 | function tryPopFrontAddress(string calldata id) external returns(address, bool); 22 | function tryPopFrontBytes(string calldata id) external returns(bytes memory, bool); 23 | function popFrontUint256(string calldata id) external returns(uint256); 24 | function popFrontAddress(string calldata id) external returns(address); 25 | function popFrontBytes(string calldata id) external returns(bytes memory); 26 | 27 | function tryPopBackUint256(string calldata id) external returns(uint256, bool); 28 | function tryPopBackAddress(string calldata id) external returns(address, bool); 29 | function tryPopBackBytes(string calldata id) external returns(bytes memory, bool); 30 | function popBackUint256(string calldata id) external returns(uint256); 31 | function popBackAddress(string calldata id) external returns(address); 32 | function popBackBytes(string calldata id) external returns(bytes memory); 33 | 34 | function tryGetUint256(string calldata id, uint256 index) external returns(uint256, bool); 35 | function tryGetAddress(string calldata id, uint256 index) external returns(address, bool); 36 | function tryGetBytes(string calldata id, uint256 index) external returns(bytes memory, bool); 37 | function getUint256(string calldata id, uint256 index) external returns(uint256); 38 | function getAddress(string calldata id, uint256 index) external returns(address); 39 | function getBytes(string calldata id, uint256 index) external returns(bytes memory); 40 | } 41 | 42 | library Concurrency { 43 | enum DataType { INVALID, ADDRESS, UINT256, BYTES } 44 | 45 | DynamicArray constant private darray = DynamicArray(address(0x84)); 46 | System constant private sys = System(address(0xa1)); 47 | UUID constant private uuid = UUID(address(0xa0)); 48 | 49 | struct Array { 50 | string id; 51 | } 52 | 53 | function Init(Array storage self, string memory id, DataType typ) public { 54 | self.id = id; 55 | darray.create(id, uint256(typ)); 56 | } 57 | 58 | function Length(Array storage self) public view returns(uint256) { 59 | return darray.length(self.id); 60 | } 61 | 62 | function PushBack(Array storage self, uint256 elem) public { 63 | darray.pushBack(self.id, elem); 64 | } 65 | 66 | function PushBack(Array storage self, address elem) public { 67 | darray.pushBack(self.id, elem); 68 | } 69 | 70 | function PushBack(Array storage self, bytes memory elem) public { 71 | darray.pushBack(self.id, elem); 72 | } 73 | 74 | function TryPopFrontUint256(Array storage self) public returns(uint256, bool) { 75 | return darray.tryPopFrontUint256(self.id); 76 | } 77 | 78 | function TryPopFrontAddress(Array storage self) public returns(address, bool) { 79 | return darray.tryPopFrontAddress(self.id); 80 | } 81 | 82 | function TryPopFrontBytes(Array storage self) public returns(bytes memory, bool) { 83 | return darray.tryPopFrontBytes(self.id); 84 | } 85 | 86 | function PopFrontUint256(Array storage self) public returns(uint256) { 87 | return darray.popFrontUint256(self.id); 88 | } 89 | 90 | function PopFrontAddress(Array storage self) public returns(address) { 91 | return darray.popFrontAddress(self.id); 92 | } 93 | 94 | function TryPopBackUint256(Array storage self) public returns(uint256, bool) { 95 | return darray.tryPopBackUint256(self.id); 96 | } 97 | 98 | function TryPopBackAddress(Array storage self) public returns(address, bool) { 99 | return darray.tryPopBackAddress(self.id); 100 | } 101 | 102 | function TryPopBackBytes(Array storage self) public returns(bytes memory, bool) { 103 | return darray.tryPopBackBytes(self.id); 104 | } 105 | 106 | function PopBackUint256(Array storage self) public returns(uint256) { 107 | return darray.popBackUint256(self.id); 108 | } 109 | 110 | function PopBackAddress(Array storage self) public returns(address) { 111 | return darray.popBackAddress(self.id); 112 | } 113 | 114 | function PopBackBytes(Array storage self) public returns(bytes memory) { 115 | return darray.popBackBytes(self.id); 116 | } 117 | 118 | function TryGetUint256(Array storage self, uint256 index) public returns(uint256, bool) { 119 | return darray.tryGetUint256(self.id, index); 120 | } 121 | 122 | function TryGetAddress(Array storage self, uint256 index) public returns(address, bool) { 123 | return darray.tryGetAddress(self.id, index); 124 | } 125 | 126 | function TryGetBytes(Array storage self, uint256 index) public returns(bytes memory, bool) { 127 | return darray.tryGetBytes(self.id, index); 128 | } 129 | 130 | function GetUint256(Array storage self, uint256 index) public returns(uint256) { 131 | return darray.getUint256(self.id, index); 132 | } 133 | 134 | function GetAddress(Array storage self, uint256 index) public returns(address) { 135 | return darray.getAddress(self.id, index); 136 | } 137 | 138 | function GetBytes(Array storage self, uint256 index) public returns(bytes memory) { 139 | return darray.getBytes(self.id, index); 140 | } 141 | 142 | struct Deferred { 143 | string id; 144 | } 145 | 146 | function Init(Deferred storage self, string memory id, string memory signature) public { 147 | sys.createDefer(id, signature); 148 | self.id = id; 149 | } 150 | 151 | function Call(Deferred storage self) public { 152 | sys.callDefer(self.id); 153 | } 154 | 155 | struct Util { 156 | uint _placeholder; 157 | } 158 | 159 | function GenUUID(Util storage, string memory seed) public pure returns(uint256) { 160 | return uuid.gen(seed); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /contract/KittyCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | // // Auction wrapper functions 4 | import "./KittyMinting.sol"; 5 | 6 | /// @title CryptoKitties: Collectible, breedable, and oh-so-adorable cats on the Ethereum blockchain. 7 | /// @author Axiom Zen (https://www.axiomzen.co) 8 | /// @dev The main CryptoKitties contract, keeps track of kittens so they don't wander around and get lost. 9 | contract KittyCore is KittyMinting { 10 | 11 | // This is the main CryptoKitties contract. In order to keep our code seperated into logical sections, 12 | // we've broken it up in two ways. First, we have several seperately-instantiated sibling contracts 13 | // that handle auctions and our super-top-secret genetic combination algorithm. The auctions are 14 | // seperate since their logic is somewhat complex and there's always a risk of subtle bugs. By keeping 15 | // them in their own contracts, we can upgrade them without disrupting the main contract that tracks 16 | // kitty ownership. The genetic combination algorithm is kept seperate so we can open-source all of 17 | // the rest of our code without making it _too_ easy for folks to figure out how the genetics work. 18 | // Don't worry, I'm sure someone will reverse engineer it soon enough! 19 | // 20 | // Secondly, we break the core contract into multiple files using inheritence, one for each major 21 | // facet of functionality of CK. This allows us to keep related code bundled together while still 22 | // avoiding a single giant file with everything in it. The breakdown is as follows: 23 | // 24 | // - KittyBase: This is where we define the most fundamental code shared throughout the core 25 | // functionality. This includes our main data storage, constants and data types, plus 26 | // internal functions for managing these items. 27 | // 28 | // - KittyAccessControl: This contract manages the various addresses and constraints for operations 29 | // that can be executed only by specific roles. Namely CEO, CFO and COO. 30 | // 31 | // - KittyOwnership: This provides the methods required for basic non-fungible token 32 | // transactions, following the draft ERC-721 spec (https://github.com/ethereum/EIPs/issues/721). 33 | // 34 | // - KittyBreeding: This file contains the methods necessary to breed cats together, including 35 | // keeping track of siring offers, and relies on an external genetic combination contract. 36 | // 37 | // - KittyAuctions: Here we have the public methods for auctioning or bidding on cats or siring 38 | // services. The actual auction functionality is handled in two sibling contracts (one 39 | // for sales and one for siring), while auction creation and bidding is mostly mediated 40 | // through this facet of the core contract. 41 | // 42 | // - KittyMinting: This final facet contains the functionality we use for creating new gen0 cats. 43 | // We can make up to 5000 "promo" cats that can be given away (especially important when 44 | // the community is new), and all others can only be created and then immediately put up 45 | // for auction via an algorithmically determined starting price. Regardless of how they 46 | // are created, there is a hard limit of 50k gen0 cats. After that, it's all up to the 47 | // community to breed, breed, breed! 48 | 49 | // Set in case the core contract is broken and an upgrade is required 50 | address public newContractAddress; 51 | 52 | /// @notice Creates the main CryptoKitties smart contract instance. 53 | constructor() public { 54 | // Starts paused. 55 | paused = true; 56 | 57 | // the creator of the contract is the initial CEO 58 | ceoAddress = msg.sender; 59 | 60 | // the creator of the contract is also the initial COO 61 | cooAddress = msg.sender; 62 | 63 | /* 64 | // start with the mythical kitten 0 - so we don't have generation-0 parent issues 65 | _createKitty(0, 0, 0, uint256(-1), address(0)); 66 | */ 67 | } 68 | 69 | /// @dev Used to mark the smart contract as upgraded, in case there is a serious 70 | /// breaking bug. This method does nothing but keep track of the new contract and 71 | /// emit a message indicating that the new address is set. It's up to clients of this 72 | /// contract to update to the new contract address in that case. (This contract will 73 | /// be paused indefinitely if such an upgrade takes place.) 74 | /// @param _v2Address new address 75 | function setNewAddress(address _v2Address) public onlyCEO whenPaused { 76 | // See README.md for updgrade plan 77 | newContractAddress = _v2Address; 78 | emit ContractUpgrade(_v2Address); 79 | } 80 | 81 | /// @notice No tipping! 82 | /// @dev Reject all Ether from being sent here, unless it's from one of the 83 | /// two auction contracts. (Hopefully, we can prevent user accidents.) 84 | function() external payable { 85 | require( 86 | msg.sender == address(saleAuction) || 87 | msg.sender == address(siringAuction) 88 | ); 89 | } 90 | 91 | /// @notice Returns all the relevant information about a specific kitty. 92 | /// @param _id The ID of the kitty of interest. 93 | function getKitty(uint256 _id) 94 | public 95 | view 96 | returns ( 97 | bool isGestating, 98 | bool isReady, 99 | uint256 cooldownIndex, 100 | uint256 nextActionAt, 101 | uint256 siringWithId, 102 | uint256 birthTime, 103 | uint256 matronId, 104 | uint256 sireId, 105 | uint256 generation, 106 | uint256 genes 107 | ) { 108 | Kitty storage kit = kitties[_id]; 109 | 110 | // if this variable is 0 then it's not gestating 111 | isGestating = (kit.siringWithId != 0); 112 | isReady = (kit.cooldownEndTime <= now); 113 | cooldownIndex = uint256(kit.cooldownIndex); 114 | nextActionAt = uint256(kit.cooldownEndTime); 115 | siringWithId = uint256(kit.siringWithId); 116 | birthTime = uint256(kit.birthTime); 117 | matronId = uint256(kit.matronId); 118 | sireId = uint256(kit.sireId); 119 | generation = uint256(kit.generation); 120 | genes = kit.genes; 121 | } 122 | 123 | /// @dev Override unpause so it requires all external contract addresses 124 | /// to be set before contract can be unpaused. Also, we can't have 125 | /// newContractAddress set either, because then the contract was upgraded. 126 | function unpause() public onlyCEO whenPaused { 127 | require(address(saleAuction) != address(0)); 128 | require(address(siringAuction) != address(0)); 129 | require(address(geneScience) != address(0)); 130 | require(newContractAddress == address(0)); 131 | 132 | // Actually unpause the contract. 133 | super.unpause(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /contract/KittyOwnership.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./KittyBase.sol"; 4 | import "./ERC721Draft.sol"; 5 | 6 | /// @title The facet of the CryptoKitties core contract that manages ownership, ERC-721 (draft) compliant. 7 | /// @author Axiom Zen (https://www.axiomzen.co) 8 | /// @dev Ref: https://github.com/ethereum/EIPs/issues/721 9 | /// See the KittyCore contract documentation to understand how the various contract facets are arranged. 10 | contract KittyOwnership is KittyBase, ERC721 { 11 | 12 | /// @notice Name and symbol of the non fungible token, as defined in ERC721. 13 | string public name = "CryptoKitties"; 14 | string public symbol = "CK"; 15 | 16 | // bool public implementsERC721 = true; 17 | // 18 | function implementsERC721() public pure returns (bool) 19 | { 20 | return true; 21 | } 22 | 23 | // Internal utility functions: These functions all assume that their input arguments 24 | // are valid. We leave it to public methods to sanitize their inputs and follow 25 | // the required logic. 26 | 27 | /// @dev Checks if a given address is the current owner of a particular Kitty. 28 | /// @param _claimant the address we are validating against. 29 | /// @param _tokenId kitten id, only valid when > 0 30 | function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) { 31 | return kittyIndexToOwner[_tokenId] == _claimant; 32 | } 33 | 34 | /// @dev Checks if a given address currently has transferApproval for a particular Kitty. 35 | /// @param _claimant the address we are confirming kitten is approved for. 36 | /// @param _tokenId kitten id, only valid when > 0 37 | function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) { 38 | return kittyIndexToApproved[_tokenId] == _claimant; 39 | } 40 | 41 | /// @dev Marks an address as being approved for transferFrom(), overwriting any previous 42 | /// approval. Setting _approved to address(0) clears all transfer approval. 43 | /// NOTE: _approve() does NOT send the Approval event. This is intentional because 44 | /// _approve() and transferFrom() are used together for putting Kitties on auction, and 45 | /// there is no value in spamming the log with Approval events in that case. 46 | function _approve(uint256 _tokenId, address _approved) internal { 47 | kittyIndexToApproved[_tokenId] = _approved; 48 | } 49 | 50 | /// @dev Transfers a kitty owned by this contract to the specified address. 51 | /// Used to rescue lost kitties. (There is no "proper" flow where this contract 52 | /// should be the owner of any Kitty. This function exists for us to reassign 53 | /// the ownership of Kitties that users may have accidentally sent to our address.) 54 | /// @param _kittyId - ID of kitty 55 | /// @param _recipient - Address to send the cat to 56 | function rescueLostKitty(uint256 _kittyId, address _recipient) public onlyCOO whenNotPaused { 57 | require(_owns(address(this), _kittyId)); 58 | _transfer(address(this), _recipient, _kittyId); 59 | } 60 | 61 | /// @notice Returns the number of Kitties owned by a specific address. 62 | /// @param _owner The owner address to check. 63 | /// @dev Required for ERC-721 compliance 64 | function balanceOf(address _owner) public view returns (uint256 count) { 65 | /* 66 | return ownershipTokenCount[_owner]; 67 | */ 68 | if (_owner == address(saleAuction)) { 69 | return balanceOfSaleAuction; 70 | } else if (_owner == address(siringAuction)) { 71 | return balanceOfSiringAuction; 72 | } else { 73 | return ownershipTokenCount[_owner]; 74 | } 75 | } 76 | 77 | /// @notice Transfers a Kitty to another address. If transferring to a smart 78 | /// contract be VERY CAREFUL to ensure that it is aware of ERC-721 (or 79 | /// CryptoKitties specifically) or your Kitty may be lost forever. Seriously. 80 | /// @param _to The address of the recipient, can be a user or contract. 81 | /// @param _tokenId The ID of the Kitty to transfer. 82 | /// @dev Required for ERC-721 compliance. 83 | function transfer( 84 | address _to, 85 | uint256 _tokenId 86 | ) 87 | public 88 | whenNotPaused 89 | { 90 | // Safety check to prevent against an unexpected 0x0 default. 91 | require(_to != address(0)); 92 | // You can only send your own cat. 93 | require(_owns(msg.sender, _tokenId)); 94 | 95 | // Reassign ownership, clear pending approvals, emit Transfer event. 96 | _transfer(msg.sender, _to, _tokenId); 97 | } 98 | 99 | /// @notice Grant another address the right to transfer a specific Kitty via 100 | /// transferFrom(). This is the preferred flow for transfering NFTs to contracts. 101 | /// @param _to The address to be granted transfer approval. Pass address(0) to 102 | /// clear all approvals. 103 | /// @param _tokenId The ID of the Kitty that can be transferred if this call succeeds. 104 | /// @dev Required for ERC-721 compliance. 105 | function approve( 106 | address _to, 107 | uint256 _tokenId 108 | ) 109 | public 110 | whenNotPaused 111 | { 112 | // Only an owner can grant transfer approval. 113 | require(_owns(msg.sender, _tokenId)); 114 | 115 | // Register the approval (replacing any previous approval). 116 | _approve(_tokenId, _to); 117 | 118 | // Emit approval event. 119 | emit Approval(msg.sender, _to, _tokenId); 120 | } 121 | 122 | /// @notice Transfer a Kitty owned by another address, for which the calling address 123 | /// has previously been granted transfer approval by the owner. 124 | /// @param _from The address that owns the Kitty to be transfered. 125 | /// @param _to The address that should take ownership of the Kitty. Can be any address, 126 | /// including the caller. 127 | /// @param _tokenId The ID of the Kitty to be transferred. 128 | /// @dev Required for ERC-721 compliance. 129 | function transferFrom( 130 | address _from, 131 | address _to, 132 | uint256 _tokenId 133 | ) 134 | public 135 | whenNotPaused 136 | { 137 | // Check for approval and valid ownership 138 | require(_approvedFor(msg.sender, _tokenId)); 139 | require(_owns(_from, _tokenId)); 140 | 141 | // Reassign ownership (also clears pending approvals and emits Transfer event). 142 | _transfer(_from, _to, _tokenId); 143 | } 144 | 145 | /// @notice Returns the total number of Kitties currently in existence. 146 | /// @dev Required for ERC-721 compliance. 147 | function totalSupply() public view returns (uint) { 148 | /* 149 | return kitties.length - 1; 150 | */ 151 | return totalBalance; 152 | } 153 | 154 | /// @notice Returns the address currently assigned ownership of a given Kitty. 155 | /// @dev Required for ERC-721 compliance. 156 | function ownerOf(uint256 _tokenId) 157 | public 158 | view 159 | returns (address owner) 160 | { 161 | owner = kittyIndexToOwner[_tokenId]; 162 | 163 | require(owner != address(0)); 164 | } 165 | 166 | /* 167 | /// @notice Returns the nth Kitty assigned to an address, with n specified by the 168 | /// _index argument. 169 | /// @param _owner The owner whose Kitties we are interested in. 170 | /// @param _index The zero-based index of the cat within the owner's list of cats. 171 | /// Must be less than balanceOf(_owner). 172 | /// @dev This method MUST NEVER be called by smart contract code. It will almost 173 | /// certainly blow past the block gas limit once there are a large number of 174 | /// Kitties in existence. Exists only to allow off-chain queries of ownership. 175 | /// Optional method for ERC-721. 176 | function tokensOfOwnerByIndex(address _owner, uint256 _index) 177 | external 178 | view 179 | returns (uint256 tokenId) 180 | { 181 | uint256 count = 0; 182 | for (uint256 i = 1; i <= totalSupply(); i++) { 183 | if (kittyIndexToOwner[i] == _owner) { 184 | if (count == _index) { 185 | return i; 186 | } else { 187 | count++; 188 | } 189 | } 190 | } 191 | revert(); 192 | } 193 | */ 194 | } 195 | -------------------------------------------------------------------------------- /contract/Auction/ClockAuctionBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./../ERC721Draft.sol"; 4 | 5 | /// @title Auction Core 6 | /// @dev Contains models, variables, and internal methods for the auction. 7 | contract ClockAuctionBase { 8 | 9 | // Represents an auction on an NFT 10 | struct Auction { 11 | // Current owner of NFT 12 | address seller; 13 | // Price (in wei) at beginning of auction 14 | uint128 startingPrice; 15 | // Price (in wei) at end of auction 16 | uint128 endingPrice; 17 | // Duration (in seconds) of auction 18 | uint64 duration; 19 | // Time when auction started 20 | // NOTE: 0 if this auction has been concluded 21 | uint64 startedAt; 22 | } 23 | 24 | // Reference to contract tracking NFT ownership 25 | ERC721 public nonFungibleContract; 26 | 27 | // Cut owner takes on each auction, measured in basis points (1/100 of a percent). 28 | // Values 0-10,000 map to 0%-100% 29 | uint256 public ownerCut; 30 | 31 | // Map from token ID to their corresponding auction. 32 | mapping (uint256 => Auction) tokenIdToAuction; 33 | 34 | event AuctionCreated(uint256 tokenId, uint256 startingPrice, uint256 endingPrice, uint256 duration); 35 | event AuctionSuccessful(uint256 tokenId, uint256 totalPrice, address winner); 36 | event AuctionCancelled(uint256 tokenId); 37 | 38 | /// @dev DON'T give me your money. 39 | function() external {} 40 | 41 | // Modifiers to check that inputs can be safely stored with a certain 42 | // number of bits. We use constants and multiple modifiers to save gas. 43 | modifier canBeStoredWith64Bits(uint256 _value) { 44 | require(_value <= 18446744073709551615); 45 | _; 46 | } 47 | 48 | modifier canBeStoredWith128Bits(uint256 _value) { 49 | require(_value < 340282366920938463463374607431768211455); 50 | _; 51 | } 52 | 53 | /// @dev Returns true if the claimant owns the token. 54 | /// @param _claimant - Address claiming to own the token. 55 | /// @param _tokenId - ID of token whose ownership to verify. 56 | function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) { 57 | return (nonFungibleContract.ownerOf(_tokenId) == _claimant); 58 | } 59 | 60 | /// @dev Escrows the NFT, assigning ownership to this contract. 61 | /// Throws if the escrow fails. 62 | /// @param _owner - Current owner address of token to escrow. 63 | /// @param _tokenId - ID of token whose approval to verify. 64 | function _escrow(address _owner, uint256 _tokenId) internal { 65 | // it will throw if transfer fails 66 | nonFungibleContract.transferFrom(_owner, address(this), _tokenId); 67 | } 68 | 69 | /// @dev Transfers an NFT owned by this contract to another address. 70 | /// Returns true if the transfer succeeds. 71 | /// @param _receiver - Address to transfer NFT to. 72 | /// @param _tokenId - ID of token to transfer. 73 | function _transfer(address _receiver, uint256 _tokenId) internal { 74 | // it will throw if transfer fails 75 | nonFungibleContract.transfer(_receiver, _tokenId); 76 | } 77 | 78 | /// @dev Adds an auction to the list of open auctions. Also fires the 79 | /// AuctionCreated event. 80 | /// @param _tokenId The ID of the token to be put on auction. 81 | /// @param _auction Auction to add. 82 | function _addAuction(uint256 _tokenId, Auction memory _auction) internal { 83 | // Require that all auctions have a duration of 84 | // at least one minute. (Keeps our math from getting hairy!) 85 | require(_auction.duration >= 1 minutes); 86 | 87 | tokenIdToAuction[_tokenId] = _auction; 88 | 89 | emit AuctionCreated( 90 | uint256(_tokenId), 91 | uint256(_auction.startingPrice), 92 | uint256(_auction.endingPrice), 93 | uint256(_auction.duration) 94 | ); 95 | } 96 | 97 | /// @dev Cancels an auction unconditionally. 98 | function _cancelAuction(uint256 _tokenId, address _seller) internal { 99 | _removeAuction(_tokenId); 100 | _transfer(_seller, _tokenId); 101 | emit AuctionCancelled(_tokenId); 102 | } 103 | 104 | /// @dev Computes the price and transfers winnings. 105 | /// Does NOT transfer ownership of token. 106 | function _bid(uint256 _tokenId, uint256 _bidAmount) 107 | internal 108 | returns (uint256) 109 | { 110 | // Get a reference to the auction struct 111 | Auction storage auction = tokenIdToAuction[_tokenId]; 112 | 113 | // Explicitly check that this auction is currently live. 114 | // (Because of how Ethereum mappings work, we can't just count 115 | // on the lookup above failing. An invalid _tokenId will just 116 | // return an auction object that is all zeros.) 117 | require(_isOnAuction(auction)); 118 | 119 | // Check that the incoming bid is higher than the current 120 | // price 121 | uint256 price = _currentPrice(auction); 122 | require(_bidAmount >= price); 123 | 124 | // Grab a reference to the seller before the auction struct 125 | // gets deleted. 126 | address payable seller = address(uint160(auction.seller)); 127 | 128 | // The bid is good! Remove the auction before sending the fees 129 | // to the sender so we can't have a reentrancy attack. 130 | _removeAuction(_tokenId); 131 | 132 | // Transfer proceeds to seller (if there are any!) 133 | if (price > 0) { 134 | // Calculate the auctioneer's cut. 135 | // (NOTE: _computeCut() is guaranteed to return a 136 | // value <= price, so this subtraction can't go negative.) 137 | uint256 auctioneerCut = _computeCut(price); 138 | uint256 sellerProceeds = price - auctioneerCut; 139 | 140 | // NOTE: Doing a transfer() in the middle of a complex 141 | // method like this is generally discouraged because of 142 | // reentrancy attacks and DoS attacks if the seller is 143 | // a contract with an invalid fallback function. We explicitly 144 | // guard against reentrancy attacks by removing the auction 145 | // before calling transfer(), and the only thing the seller 146 | // can DoS is the sale of their own asset! (And if it's an 147 | // accident, they can call cancelAuction(). ) 148 | seller.transfer(sellerProceeds); 149 | } 150 | 151 | // Tell the world! 152 | emit AuctionSuccessful(_tokenId, price, msg.sender); 153 | 154 | return price; 155 | } 156 | 157 | /// @dev Removes an auction from the list of open auctions. 158 | /// @param _tokenId - ID of NFT on auction. 159 | function _removeAuction(uint256 _tokenId) internal { 160 | delete tokenIdToAuction[_tokenId]; 161 | } 162 | 163 | /// @dev Returns true if the NFT is on auction. 164 | /// @param _auction - Auction to check. 165 | function _isOnAuction(Auction storage _auction) internal view returns (bool) { 166 | return (_auction.startedAt > 0); 167 | } 168 | 169 | /// @dev Returns current price of an NFT on auction. Broken into two 170 | /// functions (this one, that computes the duration from the auction 171 | /// structure, and the other that does the price computation) so we 172 | /// can easily test that the price computation works correctly. 173 | function _currentPrice(Auction storage _auction) 174 | internal 175 | view 176 | returns (uint256) 177 | { 178 | uint256 secondsPassed = 0; 179 | 180 | // A bit of insurance against negative values (or wraparound). 181 | // Probably not necessary (since Ethereum guarnatees that the 182 | // now variable doesn't ever go backwards). 183 | if (now > _auction.startedAt) { 184 | secondsPassed = now - _auction.startedAt; 185 | } 186 | 187 | return _computeCurrentPrice( 188 | _auction.startingPrice, 189 | _auction.endingPrice, 190 | _auction.duration, 191 | secondsPassed 192 | ); 193 | } 194 | 195 | /// @dev Computes the current price of an auction. Factored out 196 | /// from _currentPrice so we can run extensive unit tests. 197 | /// When testing, make this function public and turn on 198 | /// `Current price computation` test suite. 199 | function _computeCurrentPrice( 200 | uint256 _startingPrice, 201 | uint256 _endingPrice, 202 | uint256 _duration, 203 | uint256 _secondsPassed 204 | ) 205 | internal 206 | pure 207 | returns (uint256) 208 | { 209 | // NOTE: We don't use SafeMath (or similar) in this function because 210 | // all of our public functions carefully cap the maximum values for 211 | // time (at 64-bits) and currency (at 128-bits). _duration is 212 | // also known to be non-zero (see the require() statement in 213 | // _addAuction()) 214 | if (_secondsPassed >= _duration) { 215 | // We've reached the end of the dynamic pricing portion 216 | // of the auction, just return the end price. 217 | return _endingPrice; 218 | } else { 219 | // Starting price can be higher than ending price (and often is!), so 220 | // this delta can be negative. 221 | int256 totalPriceChange = int256(_endingPrice) - int256(_startingPrice); 222 | 223 | // This multiplication can't overflow, _secondsPassed will easily fit within 224 | // 64-bits, and totalPriceChange will easily fit within 128-bits, their product 225 | // will always fit within 256-bits. 226 | int256 currentPriceChange = totalPriceChange * int256(_secondsPassed) / int256(_duration); 227 | 228 | // currentPriceChange can be negative, but if so, will have a magnitude 229 | // less that _startingPrice. Thus, this result will always end up positive. 230 | int256 currentPrice = int256(_startingPrice) + currentPriceChange; 231 | 232 | return uint256(currentPrice); 233 | } 234 | } 235 | 236 | /// @dev Computes owner's cut of a sale. 237 | /// @param _price - Sale price of NFT. 238 | function _computeCut(uint256 _price) internal view returns (uint256) { 239 | // NOTE: We don't use SafeMath (or similar) in this function because 240 | // all of our entry functions carefully cap the maximum values for 241 | // currency (at 128-bits), and ownerCut <= 10000 (see the require() 242 | // statement in the ClockAuction constructor). The result of this 243 | // function is always guaranteed to be <= _price. 244 | return _price * ownerCut / 10000; 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /doc/pk-interactive.md: -------------------------------------------------------------------------------- 1 | # Interactive Scripts 2 | 3 | Import necessary ammolite modules 4 | ```Python 5 | >>> from ammolite import (Cli, HTTPProvider, Account) 6 | ``` 7 | 8 | Compile the parallelized CryptoKitties source code with the solcx library 9 | ```Python 10 | >>> from solcx import compile_files 11 | ``` 12 | Declare a list to store all the locations of the source code files 13 | ```Python 14 | >>> sources = [] 15 | 16 | ``` 17 | Traverse ~/contract directory,find all Solidity files and add to sources 18 | ```Python 19 | >>> import os 20 | >>> for root, _, files in os.walk('./contract'): 21 | ... for file in files: 22 | ... if file.endswith('.sol'): 23 | ... sources.append(os.path.join(root, file)) 24 | ... 25 | 26 | ``` 27 | Check source code files 28 | ```Python 29 | >>> sources 30 | ['./contract/KittyOwnership.sol', './contract/KittyMinting.sol', './contract/KittyAuction.sol', './contract/KittyBreeding.sol', './contract/ERC721Draft.sol', './contract/KittyBase.sol', './contract/KittyCore.sol', './contract/KittyAccessControl.sol', './contract/GeneScience.sol', './contract/ExternalInterfaces/ConcurrentLibInterface.sol', './contract/ExternalInterfaces/GeneScienceInterface.sol', './contract/Auction/ClockAuctionBase.sol', './contract/Auction/SiringClockAuction.sol', './contract/Auction/SaleClockAuction.sol', './contract/Auction/ClockAuction.sol', './contract/Zeppelin/Pausable.sol', './contract/Zeppelin/Ownable.sol'] 31 | 32 | ``` 33 | Initialize an ammolite client connecting to the frontend of a node cluster 34 | ```Python 35 | >>> cli = Cli(HTTPProvider('http://192.168.1.111:8080')) 36 | 37 | ``` 38 | Compile source code and output ABI 39 | ```Python 40 | >>> compiled_sol = compile_files(sources, output_values=['abi']) 41 | 42 | ``` 43 | Get KittyCore contract related information 44 | ```Python 45 | >>> kitty_core = compiled_sol['./contract/KittyCore.sol:KittyCore'] 46 | 47 | ``` 48 | Use KittyCore ABI information and KittyCore deployment address to initialize a Contract object. For easy testing, using deploy_pk.sh to deploy 49 | ```Python 50 | ParallelKittie to ensure the KittyCore is deployed at a fixed address 51 | >>> kitty_core_contract = cli.eth.contract( 52 | ... abi = kitty_core['abi'], 53 | ... address = 'b1e0e9e68297aae01347f6ce0ff21d5f72d3fa0f', 54 | ... ) 55 | ``` 56 | set the COO account at deployment 57 | ```Python 58 | >>> coo_private_key = '2289ae919f03075448d567c9c4a22846ce3711731c895f1bea572cef25bb346f' 59 | 60 | ``` 61 | Pick a user account from ~/accounts.txt 62 | ```Python 63 | >>> user_private_key = '25dcf355077dc5479435353f3acd6388477c52e1d4c3d73276c2377112539609' 64 | >>> coo = Account(coo_private_key) 65 | >>> user1 = Account(user_private_key) 66 | ``` 67 | Another user account from ~/accounts.txt, remove the '0x' prefix of the address. 68 | ```Python 69 | >>> user2_address = 'b1f0f5FD90E331D695AB1E65C33ce8Cf47b69696' 70 | 71 | ``` 72 | Use COO account to sign a createPromoKitty transaction, 73 | * First argument is the gene of the newly born promotion kitty 74 | * Second argument is the kitty owner 75 | ```Python 76 | >>> raw_tx, tx_hash = coo.sign(kitty_core_contract.functions.createPromoKitty( 77 | ... 0, 78 | ... user1.address() 79 | ... ).buildTransaction({ 80 | ... 'gas': 1000000, 81 | ... 'gasPrice': 1, 82 | ... })) 83 | 84 | ``` 85 | Sending in the transaction 86 | ```Python 87 | >>> cli.sendTransactions({tx_hash:raw_tx}) 88 | 89 | ``` 90 | Use the transaction hash to get the receipts, it may need a few retries if the node cluster is under heavy workload 91 | ```Python 92 | >>> receipts = cli.getTransactionReceipts([tx_hash]) 93 | 94 | ``` 95 | Examine the returned receipts 96 | ```Python 97 | >>> receipts 98 | [{'status': 1, 'contractAddress': '0000000000000000000000000000000000000000', 'gasUsed': 44544, 'logs': [{'address': 'b1e0e9e68297aae01347f6ce0ff21d5f72d3fa0f', 'topics': ['0a5311bd2a6608f08a180df2ee7c5946819a649b204b554bb8e39825b2c50ad5', '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2'], 'data': '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'blockNumber': 77732, 'transactionHash': '0000000000000000000000000000000000000000000000000000000000000000', 'transactionIndex': 0, 'blockHash': '0000000000000000000000000000000000000000000000000000000000000000', 'logIndex': 0}, {'address': 'b1e0e9e68297aae01347f6ce0ff21d5f72d3fa0f', 'topics': ['ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0000000000000000000000000000000000000000000000000000000000000000', '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2', '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa'], 'data': '', 'blockNumber': 77732, 'transactionHash': '0000000000000000000000000000000000000000000000000000000000000000', 'transactionIndex': 0, 'blockHash': '0000000000000000000000000000000000000000000000000000000000000000', 'logIndex': 0}], 'executing logs': ''}] 99 | 100 | ``` 101 | Parse the events field in the receipt 102 | ```Python 103 | >>> events = kitty_core_contract.processReceipt(receipts[0]) 104 | 105 | ``` 106 | There are two events contained in the receipt 107 | 1. Birth event indicates that a new kitty was created and owned by the user1,kittyId is for the new kitty. 108 | matron and sire for promotion kitty are always 0,gene is the first argument when calling kitty_core_contract.functions.createPromoKitty() 109 | 2. Another event is the kitty was transferred from user0 to user1 while the tokenId should be the same as the kittyId in the Birth event 110 | ```Python 111 | >>> events 112 | {'Birth': {'owner': '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2', 'kittyId': '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa', 'matronId': '0000000000000000000000000000000000000000000000000000000000000000', 'sireId': '0000000000000000000000000000000000000000000000000000000000000000', 'genes': '0000000000000000000000000000000000000000000000000000000000000000'}, 'Transfer': {'from': '0000000000000000000000000000000000000000000000000000000000000000', 'to': '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2', 'tokenId': '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa'}} 113 | 114 | ``` 115 | Store newly created kitty ID 116 | ```Python 117 | >>> new_kitty = events['Birth']['kittyId'] 118 | >>> new_kitty 119 | '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa' 120 | 121 | ``` 122 | Use the user1 account to sign a transaction to transfer the kitty to user2. 123 | * The first argument is the address of user2 124 | * The second argument is the kitty ID 125 | ```Python 126 | >>> raw_tx, tx_hash = user1.sign(kitty_core_contract.functions.transfer( 127 | ... user2_address, 128 | ... new_kitty, 129 | ... ).buildTransaction({ 130 | ... 'gas': 1000000, 131 | ... 'gasPrice': 1, 132 | ... })) 133 | 134 | ``` 135 | Send the transaction 136 | ```Python 137 | >>> cli.sendTransactions({tx_hash:raw_tx}) 138 | 139 | ``` 140 | Check receipts 141 | ```Python 142 | >>> receipts = cli.getTransactionReceipts([tx_hash]) 143 | >>> receipts 144 | [{'status': 1, 'contractAddress': '0000000000000000000000000000000000000000', 'gasUsed': 42901, 'logs': [{'address': 'b1e0e9e68297aae01347f6ce0ff21d5f72d3fa0f', 'topics': ['ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2', '000000000000000000000000b1f0f5fd90e331d695ab1e65c33ce8cf47b69696', '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa'], 'data': '', 'blockNumber': 77972, 'transactionHash': '0000000000000000000000000000000000000000000000000000000000000000', 'transactionIndex': 0, 'blockHash': '0000000000000000000000000000000000000000000000000000000000000000', 'logIndex': 0}], 'executing logs': ''}] 145 | 146 | ``` 147 | Parse the events,should contain a Transfer event,from user1 to user2 address,tokenId is kitty ID 148 | ```Python 149 | >>> events = kitty_core_contract.processReceipt(receipts[0]) 150 | >>> events 151 | {'Transfer': {'from': '00000000000000000000000016fcefbde47fbe724aa59e5f73a35aebce7ff2a2', 'to': '000000000000000000000000b1f0f5fd90e331d695ab1e65c33ce8cf47b69696', 'tokenId': '1a5d529ea3a20e584aed24e232dd98f71dab638bed23006512bd5d9d423ca4fa'}} 152 | >>> 153 | ``` 154 | 155 | ## Scripts 156 | 157 | ```Python 158 | import time, os 159 | from solcx import compile_files 160 | from ammolite import (Cli, HTTPProvider, Account) 161 | # Get all Solidity files from the contract direcoty 162 | sources = [] 163 | for root, _, files in os.walk('./contract'): 164 | for file in files: 165 | if file.endswith('.sol'): 166 | sources.append(os.path.join(root, file)) 167 | # Initialize frontend connection 168 | cli = Cli(HTTPProvider('http://192.168.1.111:8080')) 169 | # Compile Solidity files,output ABI 170 | compiled_sol = compile_files(sources, output_values = ['abi']) 171 | # Initialize Contract object of KittyCore contract,address is deployment address of KittyCore. Prior to this, make sure to call deploy_pk.sh to complete the deployment first 172 | kitty_core = compiled_sol['./contract/KittyCore.sol:KittyCore'] 173 | kitty_core_contract = cli.eth.contract( 174 | abi = kitty_core['abi'], 175 | address = 'b1e0e9e68297aae01347f6ce0ff21d5f72d3fa0f', 176 | ) 177 | # Private keys of the COO account and a user account 178 | coo_private_key = '2289ae919f03075448d567c9c4a22846ce3711731c895f1bea572cef25bb346f' 179 | user_private_key = 'd9815a0fa4f31172530f17a6ae64bf5f00a3a651f3d6476146d2c62ae5527dc4' 180 | coo = Account(coo_private_key) 181 | user1 = Account(user_private_key) 182 | user2_address = '230DCCC4660dcBeCb8A6AEA1C713eE7A04B35cAD' 183 | # Sign with COO key to allocate a promotion kitty to user1, the first argument to call createPromoKitty is the kitty gene 184 | raw_tx, tx_hash = coo.sign(kitty_core_contract.functions.createPromoKitty( 185 | 0, 186 | user1.address() 187 | ).buildTransaction({ 188 | 'gas': 1000000, 189 | 'gasPrice': 1, 190 | })) 191 | # Send in the transaction 192 | cli.sendTransactions({tx_hash: raw_tx}) 193 | # Wait till processed 194 | receipts = [] 195 | while True: 196 | receipts = cli.getTransactionReceipts([tx_hash]) 197 | if receipts is None or len(receipts) != 1: 198 | time.sleep(1) 199 | continue 200 | break 201 | # Parse events in receipt 202 | events = kitty_core_contract.processReceipt(receipts[0]) 203 | # New kitty ID 204 | new_kitty = events['Birth']['kittyId'] 205 | print(f'New kitty {new_kitty} born and assigned to {events["Birth"]["owner"][24:]}') 206 | # Use user1 account to sign a transaction to the kitty to user2 207 | raw_tx, tx_hash = user1.sign(kitty_core_contract.functions.transfer( 208 | user2_address, 209 | new_kitty 210 | ).buildTransaction({ 211 | 'gas': 1000000, 212 | 'gasPrice': 1, 213 | })) 214 | # Send in the transaction 215 | cli.sendTransactions({tx_hash: raw_tx}) 216 | # Wait till completed 217 | while True: 218 | receipts = cli.getTransactionReceipts([tx_hash]) 219 | if receipts is None or len(receipts) != 1: 220 | time.sleep(1) 221 | continue 222 | break 223 | # Parse events in the receipt 224 | events = kitty_core_contract.processReceipt(receipts[0]) 225 | print(f'Kitty {events["Transfer"]["tokenId"]} transfered from {events["Transfer"]["from"][24:]} to {events["Transfer"]["to"][24:]}') 226 | ``` 227 | 228 | ## Output 229 | 230 | ```shell 231 | # python simple_pk_example.py 232 | New kitty f1cbc2efb34a8260708f01ed17fc7b5d1d8610a070bcbbce89d60f5bb230b7ff born and assigned to 8aa62d370585e28fd2333325d3dbaef6112279ce 233 | Kitty f1cbc2efb34a8260708f01ed17fc7b5d1d8610a070bcbbce89d60f5bb230b7ff transfered from 8aa62d370585e28fd2333325d3dbaef6112279ce to 230dccc4660dcbecb8a6aea1c713ee7a04b35cad 234 | ``` -------------------------------------------------------------------------------- /deploy_v2.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../../..') 3 | 4 | import time, yaml 5 | from rich.console import Console 6 | from ammolite import (Cli, HTTPProvider, Account) 7 | from utils import (wait_for_receipts, compile_contracts, check_receipts) 8 | 9 | compiled_sol = compile_contracts('./contract') 10 | 11 | sale_auction = compiled_sol['./contract/Auction/SaleClockAuction.sol:SaleClockAuction'] 12 | siring_auction = compiled_sol['./contract/Auction/SiringClockAuction.sol:SiringClockAuction'] 13 | gene_science = compiled_sol['./contract/GeneScience.sol:GeneScience'] 14 | kitty_core = compiled_sol['./contract/KittyCore.sol:KittyCore'] 15 | 16 | #print(gene_science['abi']) 17 | 18 | frontend = sys.argv[1] 19 | account_file = sys.argv[2] 20 | 21 | private_keys = [] 22 | addresses = [] 23 | with open(account_file, 'r') as f: 24 | for line in f: 25 | line = line.rstrip('\n') 26 | segments = line.split(',') 27 | private_keys.append(segments[0]) 28 | addresses.append(segments[1]) 29 | 30 | ceo_private_key = private_keys[0] 31 | coo_private_key = private_keys[1] 32 | cfo_private_key = private_keys[2] 33 | kitty_miner_key = private_keys[3] 34 | 35 | def write_private_keys(private_keys, addresses, file): 36 | with open(file, 'w') as f: 37 | for i in range(len(private_keys)): 38 | f.write(private_keys[i] + ',' + addresses[i] + '\n') 39 | 40 | sale_auction_keys = 'acc1.txt' 41 | siring_auction_creator_keys = 'acc2.txt' 42 | siring_auction_bidder_keys = 'acc3.txt' 43 | kitty_raiser_keys = 'acc4.txt' 44 | kitty_exchanger_keys = 'acc5.txt' 45 | 46 | def get_keys_by_weights(keys, addresses, weights, name): 47 | sum = 0 48 | start = 0 49 | end = 0 50 | for n, w in weights.items(): 51 | if n == name: 52 | start = sum 53 | sum += w 54 | if n == name: 55 | end = sum 56 | 57 | return keys[int(len(keys)*start/sum) : int(len(keys)*end/sum)], addresses[int(len(keys)*start/sum) : int(len(keys)*end/sum)] 58 | 59 | #print(get_keys_by_weights(private_keys, config['test_case_weights'], 'sale_auction')) 60 | #print(get_keys_by_weights(private_keys, config['test_case_weights'], 'siring_auction_creator')) 61 | #print(get_keys_by_weights(private_keys, config['test_case_weights'], 'siring_auction_bidder')) 62 | #print(get_keys_by_weights(private_keys, config['test_case_weights'], 'kitty_raiser')) 63 | #print(get_keys_by_weights(private_keys, config['test_case_weights'], 'kitties_exchanger')) 64 | 65 | #k, a = get_keys_by_weights(private_keys, addresses, config['test_case_weights'], 'sale_auction') 66 | #write_private_keys(k, a, sale_auction_keys) 67 | #k, a = get_keys_by_weights(private_keys, addresses, config['test_case_weights'], 'siring_auction_creator') 68 | #write_private_keys(k, a, siring_auction_creator_keys) 69 | #k, a = get_keys_by_weights(private_keys, addresses, config['test_case_weights'], 'siring_auction_bidder') 70 | #write_private_keys(k, a, siring_auction_bidder_keys) 71 | #k, a = get_keys_by_weights(private_keys, addresses, config['test_case_weights'], 'kitty_raiser') 72 | #write_private_keys(k, a, kitty_raiser_keys) 73 | #k, a = get_keys_by_weights(private_keys, addresses, config['test_case_weights'], 'kitties_exchanger') 74 | #write_private_keys(k, a, kitty_exchanger_keys) 75 | 76 | console = Console() 77 | 78 | cli = Cli(HTTPProvider(frontend)) 79 | kitty_core_contract = cli.eth.contract( 80 | abi = kitty_core['abi'], 81 | bytecode = kitty_core['bin'] 82 | ) 83 | 84 | # 1. Deploy KittyCore. 85 | ceo = Account(ceo_private_key) 86 | coo = Account(coo_private_key) 87 | cfo = Account(cfo_private_key) 88 | 89 | with console.status("[bold green]Working on tasks...") as status: 90 | raw_tx, tx_hash = ceo.sign(kitty_core_contract.constructor().buildTransaction({ 91 | 'nonce': 1, 92 | 'gas': 10000000000, 93 | 'gasPrice': 1, 94 | })) 95 | 96 | cli.sendTransactions({tx_hash: raw_tx}) 97 | receipts = wait_for_receipts(cli, [tx_hash]) 98 | check_receipts(receipts) 99 | kitty_core_address = receipts[tx_hash]['contractAddress'] 100 | kitty_core_contract.setAddress(kitty_core_address) 101 | console.log("Deploy KittyCore complete") 102 | 103 | # 2. Deploy SaleClockAuction, SiringAuction and GeneScience. 104 | sale_auction_contract = cli.eth.contract( 105 | abi = sale_auction['abi'], 106 | bytecode = sale_auction['bin'] 107 | ) 108 | raw_tx1, tx_hash1 = ceo.sign(sale_auction_contract.constructor(kitty_core_address, 100).buildTransaction({ 109 | 'nonce': 2, 110 | 'gas': 10000000000, 111 | 'gasPrice': 1, 112 | })) 113 | siring_auction_contract = cli.eth.contract( 114 | abi = siring_auction['abi'], 115 | bytecode = siring_auction['bin'] 116 | ) 117 | raw_tx2, tx_hash2 = ceo.sign(siring_auction_contract.constructor(kitty_core_address, 100).buildTransaction({ 118 | 'nonce': 3, 119 | 'gas': 10000000000, 120 | 'gasPrice': 1, 121 | })) 122 | gene_science_contract = cli.eth.contract( 123 | abi = gene_science['abi'], 124 | bytecode = gene_science['bin'] 125 | ) 126 | raw_tx3, tx_hash3 = ceo.sign(gene_science_contract.constructor('', kitty_core_address).buildTransaction({ 127 | 'nonce': 4, 128 | 'gas': 10000000000, 129 | 'gasPrice': 1, 130 | })) 131 | 132 | cli.sendTransactions({tx_hash1: raw_tx1}) 133 | receipts = wait_for_receipts(cli, [tx_hash1]) 134 | check_receipts(receipts) 135 | sale_auction_address = receipts[tx_hash1]['contractAddress'] 136 | console.log("Deploy SaleClockAuction complete") 137 | 138 | cli.sendTransactions({tx_hash2: raw_tx2}) 139 | receipts = wait_for_receipts(cli, [tx_hash2]) 140 | check_receipts(receipts) 141 | siring_auction_address = receipts[tx_hash2]['contractAddress'] 142 | console.log("Deploy SiringClockAuction complete") 143 | 144 | cli.sendTransactions({tx_hash3: raw_tx3}) 145 | receipts = wait_for_receipts(cli, [tx_hash3]) 146 | check_receipts(receipts) 147 | gene_science_address = receipts[tx_hash3]['contractAddress'] 148 | console.log("Deploy GeneScience complete") 149 | 150 | time.sleep(1) 151 | 152 | # 3. Setup KittyCore. 153 | raw_tx1, tx_hash1 = ceo.sign(kitty_core_contract.functions.setSaleAuctionAddress(sale_auction_address).buildTransaction({ 154 | 'nonce': 5, 155 | 'gas': 100000000, 156 | 'gasPrice': 1, 157 | })) 158 | raw_tx2, tx_hash2 = ceo.sign(kitty_core_contract.functions.setSiringAuctionAddress(siring_auction_address).buildTransaction({ 159 | 'nonce': 6, 160 | 'gas': 100000000, 161 | 'gasPrice': 1, 162 | })) 163 | raw_tx3, tx_hash3 = ceo.sign(kitty_core_contract.functions.setGeneScienceAddress(gene_science_address).buildTransaction({ 164 | 'nonce': 7, 165 | 'gas': 100000000, 166 | 'gasPrice': 1, 167 | })) 168 | raw_tx4, tx_hash4 = ceo.sign(kitty_core_contract.functions.setCOO(coo.address()).buildTransaction({ 169 | 'nonce': 8, 170 | 'gas': 100000000, 171 | 'gasPrice': 1, 172 | })) 173 | raw_tx5, tx_hash5 = ceo.sign(kitty_core_contract.functions.setCFO(cfo.address()).buildTransaction({ 174 | 'nonce': 9, 175 | 'gas': 100000000, 176 | 'gasPrice': 1, 177 | })) 178 | 179 | cli.sendTransactions({tx_hash1: raw_tx1, tx_hash2: raw_tx2, tx_hash3: raw_tx3, tx_hash4: raw_tx4, tx_hash5: raw_tx5}) 180 | check_receipts(wait_for_receipts(cli, [tx_hash1, tx_hash2, tx_hash3, tx_hash4, tx_hash5])) 181 | console.log("Setup runtime parameters of KittyCore complete") 182 | 183 | # 4. Start service. 184 | raw_tx, tx_hash = ceo.sign(kitty_core_contract.functions.unpause().buildTransaction({ 185 | 'nonce': 9, 186 | 'gas': 100000000, 187 | 'gasPrice': 1, 188 | })) 189 | cli.sendTransactions({tx_hash: raw_tx}) 190 | check_receipts(wait_for_receipts(cli, [tx_hash])) 191 | console.log("Start ParallelKitties service complete.") 192 | 193 | #print('Deploy ParallelKitties complete.') 194 | #print('python distribute_kitties_v2.py {} {} {} {} {} {}'.format(frontend, 'acc1.txt', kitty_core_address, coo_private_key, 'output/dist1.out', 'output/kitties1.out')) 195 | #print('python distribute_kitties_v2.py {} {} {} {} {} {}'.format(frontend, 'acc2.txt', kitty_core_address, coo_private_key, 'output/dist2.out', 'output/kitties2.out')) 196 | #print('python distribute_kitties_v2.py {} {} {} {} {} {}'.format(frontend, 'acc3.txt', kitty_core_address, coo_private_key, 'output/dist3.out', 'output/kitties3.out')) 197 | #print('python distribute_kitties_v2.py {} {} {} {} {} {}'.format(frontend, 'acc4.txt', kitty_core_address, coo_private_key, 'output/dist4.out', 'output/kitties4.out')) 198 | #print('python distribute_kitties_v2.py {} {} {} {} {} {}'.format(frontend, 'acc5.txt', kitty_core_address, coo_private_key, 'output/dist5.out', 'output/kitties5.out')) 199 | #print('python prepare_transfer.py {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000), 'output/transfer.out')) 200 | #print('python prepare_breed_with.py {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/2000), 'output/breed.out')) 201 | #print('python prepare_create_sale_auction_v2.py {} {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000/5), 'output/create_sale1.out', 'output/kitties1.out')) 202 | #print('python prepare_create_sale_auction_v2.py {} {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000/5), 'output/create_sale2.out', 'output/kitties2.out')) 203 | #print('python prepare_create_sale_auction_v2.py {} {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000/5), 'output/create_sale3.out', 'output/kitties3.out')) 204 | #print('python prepare_create_sale_auction_v2.py {} {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000/5), 'output/create_sale4.out', 'output/kitties4.out')) 205 | #print('python prepare_create_sale_auction_v2.py {} {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000/5), 'output/create_sale5.out', 'output/kitties5.out')) 206 | #print('python prepare_create_siring_auction.py {} {} {} {}'.format(frontend, kitty_core_address, int(len(private_keys)/1000), 'output/create_siring.out')) 207 | #print('python prepare_bid_on_sale_auction.py {} {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, int(len(private_keys)/2000), 'output/bid_sale.out')) 208 | #print('python prepare_bid_on_siring_auction.py {} {} {} {} {}'.format(frontend, kitty_core_address, siring_auction_address, int(len(private_keys)/2000), 'output/bid_siring.out')) 209 | #print('python prepare_give_birth.py {} {} {} {} {}'.format(frontend, kitty_core_address, kitty_miner_key, int(len(private_keys)/2000), 'output/give_birth.out')) 210 | #print('python sendtxs.py {} {}'.format(frontend, 'output/*.out')) 211 | #print('python block_mon.py {}'.format(frontend)) 212 | #print('python init_gen0.py {} {} {} {} {}'.format(frontend, config['gen0_count'], kitty_core_address, sale_auction_address, coo_private_key)) 213 | #print('python kitty_miner.py {} {} {}'.format(frontend, kitty_core_address, kitty_miner_key)) 214 | #print('python sale_auction_creator_and_bidder.py {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, sale_auction_keys)) 215 | #print('python siring_auction_creator.py {} {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, siring_auction_address, siring_auction_creator_keys)) 216 | #print('python siring_auction_bidder.py {} {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, siring_auction_address, siring_auction_bidder_keys)) 217 | #print('python kitty_raiser.py {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, kitty_raiser_keys)) 218 | #print('python kitties_exchanger.py {} {} {} {}'.format(frontend, kitty_core_address, sale_auction_address, kitty_exchanger_keys)) 219 | -------------------------------------------------------------------------------- /contract/GeneScience.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract KittyCoreInterface { 4 | function cooAddress() public returns(address); 5 | } 6 | 7 | /// @title GeneScience implements the trait calculation for new kitties 8 | /// @author Axiom Zen, Dieter Shirley (https://github.com/dete), Fabiano P. Soriani (https://github.com/flockonus), Jordan Schalm (https://github.com/jordanschalm), Abhishek Chadha (https://github.com/achadha235) 9 | contract GeneScience { 10 | bool public isGeneScience = true; 11 | 12 | uint256 internal constant maskLast8Bits = uint256(0xff); 13 | uint256 internal constant maskFirst248Bits = uint256(~0xff); 14 | 15 | // This is the privileged birther address. If this is set to 0, privileged birthing is disabled 16 | address internal _privilegedBirther; 17 | // Privileged window size for birthers, set to 5 blocks. 18 | uint256 public privilegedBirtherWindowSize = 5; 19 | KittyCoreInterface _kittyCore; 20 | 21 | constructor(address _privilegedBirtherAddress, address _kittyCoreAddress) public { 22 | require(_kittyCoreAddress != address(0)); 23 | _kittyCore = KittyCoreInterface(_kittyCoreAddress); 24 | _privilegedBirther = _privilegedBirtherAddress; 25 | } 26 | 27 | /// @dev set the privileged birther address 28 | /// @param _birtherAddress the new birther address 29 | function setPrivilegedBirther(address _birtherAddress) public { 30 | require(msg.sender == _kittyCore.cooAddress()); 31 | _privilegedBirther = _birtherAddress; 32 | } 33 | 34 | /// @dev given a characteristic and 2 genes (unsorted) - returns > 0 if the genes ascended, that's the value 35 | /// @param trait1 any trait of that characteristic 36 | /// @param trait2 any trait of that characteristic 37 | /// @param rand is expected to be a 3 bits number (0~7) 38 | /// @return -1 if didnt match any ascention, OR a number from 0 to 30 for the ascended trait 39 | function _ascend(uint8 trait1, uint8 trait2, uint256 rand) internal pure returns(uint8 ascension) { 40 | ascension = 0; 41 | 42 | uint8 smallT = trait1; 43 | uint8 bigT = trait2; 44 | 45 | if (smallT > bigT) { 46 | bigT = trait1; 47 | smallT = trait2; 48 | } 49 | 50 | // https://github.com/axiomzen/cryptokitties/issues/244 51 | if ((bigT - smallT == 1) && smallT % 2 == 0) { 52 | 53 | // The rand argument is expected to be a random number 0-7. 54 | // 1st and 2nd tier: 1/4 chance (rand is 0 or 1) 55 | // 3rd and 4th tier: 1/8 chance (rand is 0) 56 | 57 | // must be at least this much to ascend 58 | uint256 maxRand; 59 | if (smallT < 23) maxRand = 1; 60 | else maxRand = 0; 61 | 62 | if (rand <= maxRand ) { 63 | ascension = (smallT / 2) + 16; 64 | } 65 | } 66 | } 67 | 68 | /// @dev given a number get a slice of any bits, at certain offset 69 | /// @param _n a number to be sliced 70 | /// @param _nbits how many bits long is the new number 71 | /// @param _offset how many bits to skip 72 | function _sliceNumber(uint256 _n, uint256 _nbits, uint256 _offset) private pure returns (uint256) { 73 | // mask is made by shifting left an offset number of times 74 | uint256 mask = uint256((2**_nbits) - 1) << _offset; 75 | // AND n with mask, and trim to max of _nbits bits 76 | return uint256((_n & mask) >> _offset); 77 | } 78 | 79 | /// @dev Get a 5 bit slice from an input as a number 80 | /// @param _input bits, encoded as uint 81 | /// @param _slot from 0 to 50 82 | function _get5Bits(uint256 _input, uint256 _slot) internal pure returns(uint8) { 83 | return uint8(_sliceNumber(_input, uint256(5), _slot * 5)); 84 | } 85 | 86 | /// @dev Parse a kitten gene and returns all of 12 "trait stack" that makes the characteristics 87 | /// @param _genes kitten gene 88 | /// @return the 48 traits that composes the genetic code, logically divided in stacks of 4, where only the first trait of each stack may express 89 | function decode(uint256 _genes) public pure returns(uint8[] memory) { 90 | uint8[] memory traits = new uint8[](48); 91 | uint256 i; 92 | for(i = 0; i < 48; i++) { 93 | traits[i] = _get5Bits(_genes, i); 94 | } 95 | return traits; 96 | } 97 | 98 | /// @dev Given an array of traits return the number that represent genes 99 | function encode(uint8[] memory _traits) public pure returns (uint256 _genes) { 100 | _genes = 0; 101 | for(uint256 i = 0; i < 48; i++) { 102 | _genes = _genes << 5; 103 | // bitwise OR trait with _genes 104 | _genes = _genes | _traits[47 - i]; 105 | } 106 | return _genes; 107 | } 108 | 109 | /// @dev return the expressing traits 110 | /// @param _genes the long number expressing cat genes 111 | function expressingTraits(uint256 _genes) public pure returns(uint8[12] memory) { 112 | uint8[12] memory express; 113 | for(uint256 i = 0; i < 12; i++) { 114 | express[i] = _get5Bits(_genes, i * 4); 115 | } 116 | return express; 117 | } 118 | 119 | /// @dev the function as defined in the breeding contract - as defined in CK bible 120 | function mixGenes(uint256 _genes1, uint256 _genes2, uint256 _targetBlock) public view returns (uint256) { 121 | if (_privilegedBirther == address(0) || tx.origin == _privilegedBirther) { 122 | // Allow immediate births if there is no privileged birther, or if the originator 123 | // of the transaction is the privileged birther 124 | require(block.number > _targetBlock); 125 | } else { 126 | require(block.number > _targetBlock + privilegedBirtherWindowSize); 127 | } 128 | 129 | 130 | // Try to grab the hash of the "target block". This should be available the vast 131 | // majority of the time (it will only fail if no-one calls giveBirth() within 256 132 | // blocks of the target block, which is about 40 minutes. Since anyone can call 133 | // giveBirth() and they are rewarded with ether if it succeeds, this is quite unlikely.) 134 | uint256 randomN = uint256(blockhash(_targetBlock)); 135 | 136 | if (randomN == 0) { 137 | // We don't want to completely bail if the target block is no-longer available, 138 | // nor do we want to just use the current block's hash (since it could allow a 139 | // caller to game the random result). Compute the most recent block that has the 140 | // the same value modulo 256 as the target block. The hash for this block will 141 | // still be available, and – while it can still change as time passes – it will 142 | // only change every 40 minutes. Again, someone is very likely to jump in with 143 | // the giveBirth() call before it can cycle too many times. 144 | _targetBlock = (block.number & maskFirst248Bits) + (_targetBlock & maskLast8Bits); 145 | 146 | // The computation above could result in a block LARGER than the current block, 147 | // if so, subtract 256. 148 | if (_targetBlock >= block.number) _targetBlock -= 256; 149 | 150 | randomN = uint256(blockhash(_targetBlock)); 151 | 152 | // DEBUG ONLY 153 | // assert(block.number != _targetBlock); 154 | // assert((block.number - _targetBlock) <= 256); 155 | // assert(randomN != 0); 156 | } 157 | 158 | // generate 256 bits of random, using as much entropy as we can from 159 | // sources that can't change between calls. 160 | randomN = uint256(keccak256(abi.encodePacked(randomN, _genes1, _genes2, _targetBlock))); 161 | uint256 randomIndex = 0; 162 | 163 | uint8[] memory genes1Array = decode(_genes1); 164 | uint8[] memory genes2Array = decode(_genes2); 165 | // All traits that will belong to baby 166 | uint8[] memory babyArray = new uint8[](48); 167 | // A pointer to the trait we are dealing with currently 168 | uint256 traitPos; 169 | // Trait swap value holder 170 | uint8 swap; 171 | // iterate all 12 characteristics 172 | for(uint256 i = 0; i < 12; i++) { 173 | // pick 4 traits for characteristic i 174 | uint256 j; 175 | // store the current random value 176 | uint256 rand; 177 | for(j = 3; j >= 1; j--) { 178 | traitPos = (i * 4) + j; 179 | 180 | rand = _sliceNumber(randomN, 2, randomIndex); // 0~3 181 | randomIndex += 2; 182 | 183 | // 1/4 of a chance of gene swapping forward towards expressing. 184 | if (rand == 0) { 185 | // do it for parent 1 186 | swap = genes1Array[traitPos]; 187 | genes1Array[traitPos] = genes1Array[traitPos - 1]; 188 | genes1Array[traitPos - 1] = swap; 189 | 190 | } 191 | 192 | rand = _sliceNumber(randomN, 2, randomIndex); // 0~3 193 | randomIndex += 2; 194 | 195 | if (rand == 0) { 196 | // do it for parent 2 197 | swap = genes2Array[traitPos]; 198 | genes2Array[traitPos] = genes2Array[traitPos - 1]; 199 | genes2Array[traitPos - 1] = swap; 200 | } 201 | } 202 | 203 | } 204 | 205 | // DEBUG ONLY - We should have used 72 2-bit slices above for the swapping 206 | // which will have consumed 144 bits. 207 | // assert(randomIndex == 144); 208 | 209 | // We have 256 - 144 = 112 bits of randomness left at this point. We will use up to 210 | // four bits for the first slot of each trait (three for the possible ascension, one 211 | // to pick between mom and dad if the ascension fails, for a total of 48 bits. The other 212 | // traits use one bit to pick between parents (36 gene pairs, 36 genes), leaving us 213 | // well within our entropy budget. 214 | 215 | // done shuffling parent genes, now let's decide on choosing trait and if ascending. 216 | // NOTE: Ascensions ONLY happen in the "top slot" of each characteristic. This saves 217 | // gas and also ensures ascensions only happen when they're visible. 218 | for(traitPos = 0; traitPos < 48; traitPos++) { 219 | 220 | // See if this trait pair should ascend 221 | uint8 ascendedTrait = 0; 222 | 223 | // There are two checks here. The first is straightforward, only the trait 224 | // in the first slot can ascend. The first slot is zero mod 4. 225 | // 226 | // The second check is more subtle: Only values that are one apart can ascend, 227 | // which is what we check inside the _ascend method. However, this simple mask 228 | // and compare is very cheap (9 gas) and will filter out about half of the 229 | // non-ascending pairs without a function call. 230 | // 231 | // The comparison itself just checks that one value is even, and the other 232 | // is odd. 233 | if ((traitPos % 4 == 0) && (genes1Array[traitPos] & 1) != (genes2Array[traitPos] & 1)) { 234 | uint256 rand = _sliceNumber(randomN, 3, randomIndex); 235 | randomIndex += 3; 236 | 237 | ascendedTrait = _ascend(genes1Array[traitPos], genes2Array[traitPos], rand); 238 | } 239 | 240 | if (ascendedTrait > 0) { 241 | babyArray[traitPos] = uint8(ascendedTrait); 242 | } else { 243 | // did not ascend, pick one of the parent's traits for the baby 244 | // We use the top bit of rand for this (the bottom three bits were used 245 | // to check for the ascension itself). 246 | uint256 rand = _sliceNumber(randomN, 1, randomIndex); 247 | randomIndex += 1; 248 | 249 | if (rand == 0) { 250 | babyArray[traitPos] = uint8(genes1Array[traitPos]); 251 | } else { 252 | babyArray[traitPos] = uint8(genes2Array[traitPos]); 253 | } 254 | } 255 | } 256 | 257 | return encode(babyArray); 258 | } 259 | } -------------------------------------------------------------------------------- /contract/KittyBreeding.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import './ExternalInterfaces/GeneScienceInterface.sol'; 4 | import './KittyOwnership.sol'; 5 | 6 | 7 | /// @title A facet of KittyCore that manages Kitty siring, gestation, and birth. 8 | /// @author Axiom Zen (https://www.axiomzen.co) 9 | /// @dev See the KittyCore contract documentation to understand how the various contract facets are arranged. 10 | contract KittyBreeding is KittyOwnership { 11 | 12 | /// @dev The Pregnant event is fired when two cats successfully breed and the pregnancy 13 | /// timer begins for the matron. 14 | event Pregnant(address owner, uint256 matronId, uint256 sireId); 15 | 16 | /// @dev The AutoBirth event is fired when a cat becomes pregant via the breedWithAuto() 17 | /// function. This is used to notify the auto-birth daemon that this breeding action 18 | /// included a pre-payment of the gas required to call the giveBirth() function. 19 | event AutoBirth(uint256 matronId, uint256 cooldownEndTime); 20 | 21 | /// @notice The minimum payment required to use breedWithAuto(). This fee goes towards 22 | /// the gas cost paid by the auto-birth daemon, and can be dynamically updated by 23 | /// the COO role as the gas price changes. 24 | uint256 public autoBirthFee = 1000000 * 1000000000; // (1M * 1 gwei) 25 | 26 | /// @dev The address of the sibling contract that is used to implement the sooper-sekret 27 | /// genetic combination algorithm. 28 | GeneScienceInterface public geneScience; 29 | 30 | /// @dev Update the address of the genetic contract, can only be called by the CEO. 31 | /// @param _address An address of a GeneScience contract instance to be used from this point forward. 32 | function setGeneScienceAddress(address _address) public onlyCEO { 33 | GeneScienceInterface candidateContract = GeneScienceInterface(_address); 34 | 35 | // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117 36 | require(candidateContract.isGeneScience()); 37 | 38 | // Set the new contract address 39 | geneScience = candidateContract; 40 | } 41 | 42 | /// @dev Checks that a given kitten is able to breed. Requires that the 43 | /// current cooldown is finished (for sires) and also checks that there is 44 | /// no pending pregnancy. 45 | function _isReadyToBreed(Kitty memory _kit) internal view returns (bool) { 46 | // In addition to checking the cooldownEndTime, we also need to check to see if 47 | // the cat has a pending birth; there can be some period of time between the end 48 | // of the pregnacy timer and the birth event. 49 | return (_kit.siringWithId == 0) && (_kit.cooldownEndTime <= now); 50 | } 51 | 52 | /// @dev Check if a sire has authorized breeding with this matron. True if both sire 53 | /// and matron have the same owner, or if the sire has given siring permission to 54 | /// the matron's owner (via approveSiring()). 55 | function _isSiringPermitted(uint256 _sireId, uint256 _matronId) internal view returns (bool) { 56 | address matronOwner = kittyIndexToOwner[_matronId]; 57 | address sireOwner = kittyIndexToOwner[_sireId]; 58 | 59 | // Siring is okay if they have same owner, or if the matron's owner was given 60 | // permission to breed with this sire. 61 | return (matronOwner == sireOwner || sireAllowedToAddress[_sireId] == matronOwner); 62 | } 63 | 64 | /// @dev Set the cooldownEndTime for the given Kitty, based on its current cooldownIndex. 65 | /// Also increments the cooldownIndex (unless it has hit the cap). 66 | /// @param _kitten A reference to the Kitty in storage which needs its timer started. 67 | function _triggerCooldown(Kitty storage _kitten) internal { 68 | // Compute the end of the cooldown time (based on current cooldownIndex) 69 | _kitten.cooldownEndTime = uint64(now + cooldowns[_kitten.cooldownIndex]); 70 | 71 | // Increment the breeding count, clamping it at 13, which is the length of the 72 | // cooldowns array. We could check the array size dynamically, but hard-coding 73 | // this as a constant saves gas. Yay, Solidity! 74 | if (_kitten.cooldownIndex < 13) { 75 | _kitten.cooldownIndex += 1; 76 | } 77 | } 78 | 79 | /// @notice Grants approval to another user to sire with one of your Kitties. 80 | /// @param _addr The address that will be able to sire with your Kitty. Set to 81 | /// address(0) to clear all siring approvals for this Kitty. 82 | /// @param _sireId A Kitty that you own that _addr will now be able to sire with. 83 | function approveSiring(address _addr, uint256 _sireId) 84 | public 85 | whenNotPaused 86 | { 87 | require(_owns(msg.sender, _sireId)); 88 | sireAllowedToAddress[_sireId] = _addr; 89 | } 90 | 91 | /// @dev Updates the minimum payment required for calling giveBirthAuto(). Can only 92 | /// be called by the COO address. (This fee is used to offset the gas cost incurred 93 | /// by the autobirth daemon). 94 | function setAutoBirthFee(uint256 val) public onlyCOO { 95 | autoBirthFee = val; 96 | } 97 | 98 | /// @dev Checks to see if a given Kitty is pregnant and (if so) if the gestation 99 | /// period has passed. 100 | function _isReadyToGiveBirth(Kitty memory _matron) private view returns (bool) { 101 | return (_matron.siringWithId != 0) && (_matron.cooldownEndTime <= now); 102 | } 103 | 104 | /// @notice Checks that a given kitten is able to breed (i.e. it is not pregnant or 105 | /// in the middle of a siring cooldown). 106 | /// @param _kittyId reference the id of the kitten, any user can inquire about it 107 | function isReadyToBreed(uint256 _kittyId) 108 | public 109 | view 110 | returns (bool) 111 | { 112 | require(_kittyId > 0); 113 | Kitty storage kit = kitties[_kittyId]; 114 | return _isReadyToBreed(kit); 115 | } 116 | 117 | /// @dev Internal check to see if a given sire and matron are a valid mating pair. DOES NOT 118 | /// check ownership permissions (that is up to the caller). 119 | /// @param _matron A reference to the Kitty struct of the potential matron. 120 | /// @param _matronId The matron's ID. 121 | /// @param _sire A reference to the Kitty struct of the potential sire. 122 | /// @param _sireId The sire's ID 123 | function _isValidMatingPair( 124 | Kitty storage _matron, 125 | uint256 _matronId, 126 | Kitty storage _sire, 127 | uint256 _sireId 128 | ) 129 | private 130 | view 131 | returns(bool) 132 | { 133 | // A Kitty can't breed with itself! 134 | if (_matronId == _sireId) { 135 | return false; 136 | } 137 | 138 | // Kitties can't breed with their parents. 139 | if (_matron.matronId == _sireId || _matron.sireId == _sireId) { 140 | return false; 141 | } 142 | if (_sire.matronId == _matronId || _sire.sireId == _matronId) { 143 | return false; 144 | } 145 | 146 | // We can short circuit the sibling check (below) if either cat is 147 | // gen zero (has a matron ID of zero). 148 | if (_sire.matronId == 0 || _matron.matronId == 0) { 149 | return true; 150 | } 151 | 152 | // Kitties can't breed with full or half siblings. 153 | if (_sire.matronId == _matron.matronId || _sire.matronId == _matron.sireId) { 154 | return false; 155 | } 156 | if (_sire.sireId == _matron.matronId || _sire.sireId == _matron.sireId) { 157 | return false; 158 | } 159 | 160 | // Everything seems cool! Let's get DTF. 161 | return true; 162 | } 163 | 164 | /// @dev Internal check to see if a given sire and matron are a valid mating pair for 165 | /// breeding via auction (i.e. skips ownership and siring approval checks). 166 | function _canBreedWithViaAuction(uint256 _matronId, uint256 _sireId) 167 | internal 168 | view 169 | returns (bool) 170 | { 171 | Kitty storage matron = kitties[_matronId]; 172 | Kitty storage sire = kitties[_sireId]; 173 | return _isValidMatingPair(matron, _matronId, sire, _sireId); 174 | } 175 | 176 | /// @notice Checks to see if two cats can breed together, including checks for 177 | /// ownership and siring approvals. Does NOT check that both cats are ready for 178 | /// breeding (i.e. breedWith could still fail until the cooldowns are finished). 179 | /// TODO: Shouldn't this check pregnancy and cooldowns?!? 180 | /// @param _matronId The ID of the proposed matron. 181 | /// @param _sireId The ID of the proposed sire. 182 | function canBreedWith(uint256 _matronId, uint256 _sireId) 183 | public 184 | view 185 | returns(bool) 186 | { 187 | require(_matronId > 0); 188 | require(_sireId > 0); 189 | Kitty storage matron = kitties[_matronId]; 190 | Kitty storage sire = kitties[_sireId]; 191 | return _isValidMatingPair(matron, _matronId, sire, _sireId) && 192 | _isSiringPermitted(_sireId, _matronId); 193 | } 194 | 195 | /// @notice Breed a Kitty you own (as matron) with a sire that you own, or for which you 196 | /// have previously been given Siring approval. Will either make your cat pregnant, or will 197 | /// fail entirely. 198 | /// @param _matronId The ID of the Kitty acting as matron (will end up pregnant if successful) 199 | /// @param _sireId The ID of the Kitty acting as sire (will begin its siring cooldown if successful) 200 | function breedWith(uint256 _matronId, uint256 _sireId) public whenNotPaused { 201 | // Caller must own the matron. 202 | require(_owns(msg.sender, _matronId)); 203 | 204 | // Neither sire nor matron are allowed to be on auction during a normal 205 | // breeding operation, but we don't need to check that explicitly. 206 | // For matron: The caller of this function can't be the owner of the matron 207 | // because the owner of a Kitty on auction is the auction house, and the 208 | // auction house will never call breedWith(). 209 | // For sire: Similarly, a sire on auction will be owned by the auction house 210 | // and the act of transferring ownership will have cleared any oustanding 211 | // siring approval. 212 | // Thus we don't need to spend gas explicitly checking to see if either cat 213 | // is on auction. 214 | 215 | // Check that matron and sire are both owned by caller, or that the sire 216 | // has given siring permission to caller (i.e. matron's owner). 217 | // Will fail for _sireId = 0 218 | require(_isSiringPermitted(_sireId, _matronId)); 219 | 220 | // Grab a reference to the potential matron 221 | Kitty storage matron = kitties[_matronId]; 222 | 223 | // Make sure matron isn't pregnant, or in the middle of a siring cooldown 224 | require(_isReadyToBreed(matron)); 225 | 226 | // Grab a reference to the potential sire 227 | Kitty storage sire = kitties[_sireId]; 228 | 229 | // Make sure sire isn't pregnant, or in the middle of a siring cooldown 230 | require(_isReadyToBreed(sire)); 231 | 232 | // Test that these cats are a valid mating pair. 233 | require(_isValidMatingPair( 234 | matron, 235 | _matronId, 236 | sire, 237 | _sireId 238 | )); 239 | 240 | // All checks passed, kitty gets pregnant! 241 | _breedWith(_matronId, _sireId); 242 | } 243 | 244 | /// @dev Internal utility function to initiate breeding, assumes that all breeding 245 | /// requirements have been checked. 246 | function _breedWith(uint256 _matronId, uint256 _sireId) internal { 247 | // Grab a reference to the Kitties from storage. 248 | Kitty storage sire = kitties[_sireId]; 249 | Kitty storage matron = kitties[_matronId]; 250 | 251 | // Mark the matron as pregnant, keeping track of who the sire is. 252 | matron.siringWithId = _sireId; 253 | 254 | // Trigger the cooldown for both parents. 255 | _triggerCooldown(sire); 256 | _triggerCooldown(matron); 257 | 258 | // Clear siring permission for both parents. This may not be strictly necessary 259 | // but it's likely to avoid confusion! 260 | delete sireAllowedToAddress[_matronId]; 261 | delete sireAllowedToAddress[_sireId]; 262 | 263 | // Emit the pregnancy event. 264 | emit Pregnant(kittyIndexToOwner[_matronId], _matronId, _sireId); 265 | } 266 | 267 | /// @notice Works like breedWith(), but includes a pre-payment of the gas required to call 268 | /// the giveBirth() function when gestation is over. This will allow our autobirth daemon 269 | /// to call giveBirth() as soon as the gestation timer finishes. The required payment is given 270 | /// by autoBirthFee(). 271 | /// @param _matronId The ID of the Kitty acting as matron (will end up pregnant if successful) 272 | /// @param _sireId The ID of the Kitty acting as sire (will begin its siring cooldown if successful) 273 | function breedWithAuto(uint256 _matronId, uint256 _sireId) 274 | public 275 | payable 276 | whenNotPaused 277 | { 278 | // Check for payment 279 | require(msg.value >= autoBirthFee); 280 | 281 | // Call through the normal breeding flow 282 | breedWith(_matronId, _sireId); 283 | 284 | // Emit an AutoBirth message so the autobirth daemon knows when and for what cat to call 285 | // giveBirth(). 286 | Kitty storage matron = kitties[_matronId]; 287 | emit AutoBirth(_matronId, matron.cooldownEndTime); 288 | } 289 | 290 | /// @notice Have a pregnant Kitty give birth! 291 | /// @param _matronId A Kitty ready to give birth. 292 | /// @return The Kitty ID of the new kitten. 293 | /// @dev Looks at a given Kitty and, if pregnant and if the gestation period has passed, 294 | /// combines the genes of the two parents to create a new kitten. The new Kitty is assigned 295 | /// to the current owner of the matron. Upon successful completion, both the matron and the 296 | /// new kitten will be ready to breed again. Note that anyone can call this function (if they 297 | /// are willing to pay the gas!), but the new kitten always goes to the mother's owner. 298 | function giveBirth(uint256 _matronId) 299 | public 300 | whenNotPaused 301 | returns(uint256) 302 | { 303 | // Grab a reference to the matron in storage. 304 | Kitty storage matron = kitties[_matronId]; 305 | 306 | // Check that the matron is a valid cat. 307 | require(matron.birthTime != 0); 308 | 309 | // Check that the matron is pregnant, and that its time has come! 310 | require(_isReadyToGiveBirth(matron)); 311 | 312 | // Grab a reference to the sire in storage. 313 | uint256 sireId = matron.siringWithId; 314 | Kitty storage sire = kitties[sireId]; 315 | 316 | // Determine the higher generation number of the two parents 317 | uint16 parentGen = matron.generation; 318 | if (sire.generation > matron.generation) { 319 | parentGen = sire.generation; 320 | } 321 | 322 | // Call the sooper-sekret, sooper-expensive, gene mixing operation. 323 | uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes); 324 | 325 | // Make the new kitten! 326 | address owner = kittyIndexToOwner[_matronId]; 327 | uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner); 328 | 329 | // Clear the reference to sire from the matron (REQUIRED! Having siringWithId 330 | // set is what marks a matron as being pregnant.) 331 | delete matron.siringWithId; 332 | 333 | // return the new kitten's ID 334 | return kittenId; 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /contract/KittyBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./KittyAccessControl.sol"; 4 | import "./Auction/SaleClockAuction.sol"; 5 | import "./Auction/SiringClockAuction.sol"; 6 | import "./ExternalInterfaces/ConcurrentLib.sol"; 7 | 8 | /// @title Base contract for CryptoKitties. Holds all common structs, events and base variables. 9 | /// @author Axiom Zen (https://www.axiomzen.co) 10 | /// @dev See the KittyCore contract documentation to understand how the various contract facets are arranged. 11 | contract KittyBase is KittyAccessControl { 12 | /*** EVENTS ***/ 13 | 14 | /// @dev The Birth event is fired whenever a new kitten comes into existence. This obviously 15 | /// includes any time a cat is created through the giveBirth method, but it is also called 16 | /// when a new gen0 cat is created. 17 | event Birth(address indexed owner, uint256 kittyId, uint256 matronId, uint256 sireId, uint256 genes); 18 | 19 | /// @dev Transfer event as defined in current draft of ERC721. Emitted every time a kitten 20 | /// ownership is assigned, including births. 21 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 22 | 23 | /* ---------- */ 24 | event BalanceOf(address addr, uint256 balance); 25 | /* ---------- */ 26 | 27 | /*** DATA TYPES ***/ 28 | 29 | /// @dev The main Kitty struct. Every cat in CryptoKitties is represented by a copy 30 | /// of this structure, so great care was taken to ensure that it fits neatly into 31 | /// exactly two 256-bit words. Note that the order of the members in this structure 32 | /// is important because of the byte-packing rules used by Ethereum. 33 | /// Ref: http://solidity.readthedocs.io/en/develop/miscellaneous.html 34 | struct Kitty { 35 | // The Kitty's genetic code is packed into these 256-bits, the format is 36 | // sooper-sekret! A cat's genes never change. 37 | uint256 genes; 38 | 39 | // The timestamp from the block when this cat came into existence. 40 | uint64 birthTime; 41 | 42 | // The minimum timestamp after which this cat can engage in breeding 43 | // activities again. This same timestamp is used for the pregnancy 44 | // timer (for matrons) as well as the siring cooldown. 45 | uint64 cooldownEndTime; 46 | 47 | // The ID of the parents of this kitty, set to 0 for gen0 cats. 48 | // Note that using 32-bit unsigned integers limits us to a "mere" 49 | // 4 billion cats. This number might seem small until you realize 50 | // that Ethereum currently has a limit of about 500 million 51 | // transactions per year! So, this definitely won't be a problem 52 | // for several years (even as Ethereum learns to scale). 53 | /* 54 | uint32 matronId; 55 | uint32 sireId; 56 | 57 | // Set to the ID of the sire cat for matrons that are pregnant, 58 | // zero otherwise. A non-zero value here is how we know a cat 59 | // is pregnant. Used to retrieve the genetic material for the new 60 | // kitten when the birth transpires. 61 | uint32 siringWithId; 62 | */ 63 | uint256 matronId; 64 | uint256 sireId; 65 | uint256 siringWithId; 66 | 67 | // Set to the index in the cooldown array (see below) that represents 68 | // the current cooldown duration for this Kitty. This starts at zero 69 | // for gen0 cats, and is initialized to floor(generation/2) for others. 70 | // Incremented by one for each successful breeding action, regardless 71 | // of whether this cat is acting as matron or sire. 72 | uint16 cooldownIndex; 73 | 74 | // The "generation number" of this cat. Cats minted by the CK contract 75 | // for sale are called "gen0" and have a generation number of 0. The 76 | // generation number of all other cats is the larger of the two generation 77 | // numbers of their parents, plus one. 78 | // (i.e. max(matron.generation, sire.generation) + 1) 79 | uint16 generation; 80 | } 81 | 82 | /*** CONSTANTS ***/ 83 | 84 | /* 85 | /// @dev A lookup table indicating the cooldown duration after any successful 86 | /// breeding action, called "pregnancy time" for matrons and "siring cooldown" 87 | /// for sires. Designed such that the cooldown roughly doubles each time a cat 88 | /// is bred, encouraging owners not to just keep breeding the same cat over 89 | /// and over again. Caps out at one week (a cat can breed an unbounded number 90 | /// of times, and the maximum cooldown is always seven days). 91 | uint32[14] public cooldowns = [ 92 | uint32(1 minutes), 93 | uint32(2 minutes), 94 | uint32(5 minutes), 95 | uint32(10 minutes), 96 | uint32(30 minutes), 97 | uint32(1 hours), 98 | uint32(2 hours), 99 | uint32(4 hours), 100 | uint32(8 hours), 101 | uint32(16 hours), 102 | uint32(1 days), 103 | uint32(2 days), 104 | uint32(4 days), 105 | uint32(7 days) 106 | ]; 107 | */ 108 | uint32[14] public cooldowns = [ 109 | uint32(1 seconds), 110 | uint32(1 seconds), 111 | uint32(1 seconds), 112 | uint32(1 seconds), 113 | uint32(1 seconds), 114 | uint32(1 seconds), 115 | uint32(1 seconds), 116 | uint32(1 seconds), 117 | uint32(1 seconds), 118 | uint32(1 seconds), 119 | uint32(1 seconds), 120 | uint32(1 seconds), 121 | uint32(1 seconds), 122 | uint32(1 seconds) 123 | ]; 124 | 125 | /*** STORAGE ***/ 126 | 127 | /// @dev An array containing the Kitty struct for all Kitties in existence. The ID 128 | /// of each cat is actually an index into this array. Note that ID 0 is a negacat, 129 | /// the unKitty, the mythical beast that is the parent of all gen0 cats. A bizarre 130 | /// creature that is both matron and sire... to itself! Has an invalid genetic code. 131 | /// In other words, cat ID 0 is invalid... ;-) 132 | mapping (uint256 => Kitty) kitties; 133 | 134 | /// @dev A mapping from cat IDs to the address that owns them. All cats have 135 | /// some valid owner address, even gen0 cats are created with a non-zero owner. 136 | mapping (uint256 => address) public kittyIndexToOwner; 137 | 138 | // @dev A mapping from owner address to count of tokens that address owns. 139 | // Used internally inside balanceOf() to resolve ownership count. 140 | mapping (address => uint256) ownershipTokenCount; 141 | 142 | /// @dev A mapping from KittyIDs to an address that has been approved to call 143 | /// transferFrom(). Each Kitty can only have one approved address for transfer 144 | /// at any time. A zero value means no approval is outstanding. 145 | mapping (uint256 => address) public kittyIndexToApproved; 146 | 147 | /// @dev A mapping from KittyIDs to an address that has been approved to use 148 | /// this Kitty for siring via breedWith(). Each Kitty can only have one approved 149 | /// address for siring at any time. A zero value means no approval is outstanding. 150 | mapping (uint256 => address) public sireAllowedToAddress; 151 | 152 | /* ---------- */ 153 | /// @dev The address of the ClockAuction contract that handles sales of Kitties. This 154 | /// same contract handles both peer-to-peer sales as well as the gen0 sales which are 155 | /// initiated every 15 minutes. 156 | SaleClockAuction public saleAuction; 157 | 158 | /// @dev The address of a custom ClockAution subclassed contract that handles siring 159 | /// auctions. Needs to be separate from saleAuction because the actions taken on success 160 | /// after a sales and siring auction are quite different. 161 | SiringClockAuction public siringAuction; 162 | 163 | /// @dev Sets the reference to the sale auction. 164 | /// @param _address - Address of sale contract. 165 | function setSaleAuctionAddress(address _address) public onlyCEO { 166 | SaleClockAuction candidateContract = SaleClockAuction(_address); 167 | 168 | // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117 169 | require(candidateContract.isSaleClockAuction()); 170 | 171 | // Set the new contract address 172 | saleAuction = candidateContract; 173 | } 174 | 175 | /// @dev Sets the reference to the siring auction. 176 | /// @param _address - Address of siring contract. 177 | function setSiringAuctionAddress(address _address) public onlyCEO { 178 | SiringClockAuction candidateContract = SiringClockAuction(_address); 179 | 180 | // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117 181 | require(candidateContract.isSiringClockAuction()); 182 | 183 | // Set the new contract address 184 | siringAuction = candidateContract; 185 | } 186 | 187 | uint256 balanceOfSaleAuction = 0; 188 | uint256 balanceOfSiringAuction = 0; 189 | uint256 totalBalance = 0; 190 | 191 | using Concurrency for Concurrency.Array; 192 | using Concurrency for Concurrency.Deferred; 193 | using Concurrency for Concurrency.Util; 194 | 195 | Concurrency.Array balanceChangesOfSaleAuction; 196 | Concurrency.Array balanceChangesOfSiringAuction; 197 | Concurrency.Array totalBalanceChanges; 198 | Concurrency.Deferred deferBalanceUpdate; 199 | Concurrency.Util util; 200 | 201 | constructor() public { 202 | balanceChangesOfSaleAuction.Init("balanceChangesOfSaleAuction", Concurrency.DataType.UINT256); 203 | balanceChangesOfSiringAuction.Init("balanceChangesOfSiringAuction", Concurrency.DataType.UINT256); 204 | totalBalanceChanges.Init("totalBalanceChanges", Concurrency.DataType.UINT256); 205 | deferBalanceUpdate.Init("deferBalanceUpdate", "updateBalance(string)"); 206 | } 207 | /* ---------- */ 208 | 209 | /// @dev Assigns ownership of a specific Kitty to an address. 210 | function _transfer(address _from, address _to, uint256 _tokenId) internal { 211 | /* 212 | // since the number of kittens is capped to 2^32 213 | // there is no way to overflow this 214 | ownershipTokenCount[_to]++; 215 | */ 216 | bool balanceUpdated = false; 217 | if (_to == address(saleAuction) && _to != address(0)) { 218 | // balanceChangesOfSaleAuction.PushBack(1); 219 | balanceUpdated = true; 220 | } else if (_to == address(siringAuction) && _to != address(0)) { 221 | // balanceChangesOfSiringAuction.PushBack(1); 222 | balanceUpdated = true; 223 | } else { 224 | ownershipTokenCount[_to]++; 225 | } 226 | 227 | // transfer ownership 228 | kittyIndexToOwner[_tokenId] = _to; 229 | // When creating new kittens _from is 0x0, but we can't account that address. 230 | if (_from != address(0)) { 231 | /* 232 | ownershipTokenCount[_from]--; 233 | */ 234 | if (_from == address(saleAuction)) { 235 | // balanceChangesOfSaleAuction.PushBack(0); 236 | balanceUpdated = true; 237 | } else if (_from == address(siringAuction)) { 238 | // balanceChangesOfSiringAuction.PushBack(0); 239 | balanceUpdated = true; 240 | } else { 241 | ownershipTokenCount[_from]--; 242 | } 243 | 244 | // once the kitten is transferred also clear sire allowances 245 | delete sireAllowedToAddress[_tokenId]; 246 | // clear any previously approved ownership exchange 247 | delete kittyIndexToApproved[_tokenId]; 248 | } 249 | // Emit the transfer event. 250 | emit Transfer(_from, _to, _tokenId); 251 | 252 | // if (balanceUpdated && !(_from == address(0) && _to == address(0))) { 253 | // deferBalanceUpdate.Call(); 254 | // } 255 | } 256 | 257 | /// @dev An internal method that creates a new kitty and stores it. This 258 | /// method doesn't do any checking and should only be called when the 259 | /// input data is known to be valid. Will generate both a Birth event 260 | /// and a Transfer event. 261 | /// @param _matronId The kitty ID of the matron of this cat (zero for gen0) 262 | /// @param _sireId The kitty ID of the sire of this cat (zero for gen0) 263 | /// @param _generation The generation number of this cat, must be computed by caller. 264 | /// @param _genes The kitty's genetic code. 265 | /// @param _owner The inital owner of this cat, must be non-zero (except for the unKitty, ID 0) 266 | function _createKitty( 267 | uint256 _matronId, 268 | uint256 _sireId, 269 | uint256 _generation, 270 | uint256 _genes, 271 | address _owner 272 | ) 273 | internal 274 | returns (uint) 275 | { 276 | /* 277 | // These requires are not strictly necessary, our calling code should make 278 | // sure that these conditions are never broken. However! _createKitty() is already 279 | // an expensive call (for storage), and it doesn't hurt to be especially careful 280 | // to ensure our data structures are always valid. 281 | require(_matronId <= 4294967295); 282 | require(_sireId <= 4294967295); 283 | require(_generation <= 65535); 284 | 285 | Kitty memory _kitty = Kitty({ 286 | genes: _genes, 287 | birthTime: uint64(now), 288 | cooldownEndTime: 0, 289 | matronId: uint32(_matronId), 290 | sireId: uint32(_sireId), 291 | siringWithId: 0, 292 | cooldownIndex: 0, 293 | generation: uint16(_generation) 294 | }); 295 | uint256 newKittenId = kitties.push(_kitty) - 1; 296 | 297 | // It's probably never going to happen, 4 billion cats is A LOT, but 298 | // let's just be 100% sure we never let this happen. 299 | require(newKittenId <= 4294967295); 300 | */ 301 | Kitty memory _kitty = Kitty({ 302 | genes: _genes, 303 | matronId: _matronId, 304 | sireId: _sireId, 305 | siringWithId: 0, 306 | birthTime: uint64(now), 307 | cooldownEndTime: 0, 308 | cooldownIndex: 0, 309 | generation: uint16(_generation) 310 | }); 311 | uint256 newKittenId = util.GenUUID("kittyId"); 312 | kitties[newKittenId] = _kitty; 313 | 314 | // totalBalanceChanges.PushBack(1); 315 | // if (_owner != address(saleAuction) && _owner != address(siringAuction)) { 316 | // deferBalanceUpdate.Call(); 317 | // } 318 | 319 | // emit the birth event 320 | emit Birth( 321 | _owner, 322 | newKittenId, 323 | _kitty.matronId, 324 | _kitty.sireId, 325 | _kitty.genes 326 | ); 327 | 328 | // This will assign ownership, and also emit the Transfer event as 329 | // per ERC721 draft 330 | _transfer(address(0), _owner, newKittenId); 331 | 332 | return newKittenId; 333 | } 334 | 335 | function updateBalance(string memory) public { 336 | uint256 length = balanceChangesOfSaleAuction.Length(); 337 | for (uint256 i = 0; i < length; i++) { 338 | uint256 changes = balanceChangesOfSaleAuction.PopFrontUint256(); 339 | if (changes == 1) { 340 | balanceOfSaleAuction++; 341 | } else { 342 | balanceOfSaleAuction--; 343 | } 344 | } 345 | if (length != 0) { 346 | emit BalanceOf(address(saleAuction), balanceOfSaleAuction); 347 | } 348 | 349 | length = balanceChangesOfSiringAuction.Length(); 350 | for (uint256 i = 0; i < length; i++) { 351 | uint256 changes = balanceChangesOfSiringAuction.PopFrontUint256(); 352 | if (changes == 1) { 353 | balanceOfSiringAuction++; 354 | } else { 355 | balanceOfSiringAuction--; 356 | } 357 | } 358 | if (length != 0) { 359 | emit BalanceOf(address(siringAuction), balanceOfSiringAuction); 360 | } 361 | 362 | length = totalBalanceChanges.Length(); 363 | for (uint256 i = 0; i < length; i++) { 364 | totalBalanceChanges.PopFrontUint256(); 365 | totalBalance++; 366 | } 367 | if (length != 0) { 368 | emit BalanceOf(address(0), totalBalance); 369 | } 370 | } 371 | } 372 | --------------------------------------------------------------------------------