├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── build └── contracts │ ├── MRVToken.json │ ├── Macroverse.json │ ├── MacroverseExistenceChecker.json │ ├── MacroverseMoonGenerator.json │ ├── MacroverseNFTUtils.json │ ├── MacroverseRealEstate.json │ ├── MacroverseStarGenerator.json │ ├── MacroverseStarGeneratorPatch1.json │ ├── MacroverseStarRegistry.json │ ├── MacroverseSystemGenerator.json │ ├── MacroverseSystemGeneratorPart1.json │ ├── MacroverseSystemGeneratorPart2.json │ ├── MacroverseUniversalRegistry.json │ ├── Migrations.json │ ├── MinimumBalanceAccessControl.json │ ├── OrbitalMechanics.json │ ├── RNG.json │ ├── RealMath.json │ ├── TestnetMRVToken.json │ └── UnrestrictedAccessControl.json ├── contracts ├── AccessControl.sol ├── ControlledAccess.sol ├── HasNoContracts.sol ├── HasNoEther.sol ├── HasNoTokens.sol ├── MRVToken.sol ├── Macroverse.sol ├── MacroverseExistenceChecker.sol ├── MacroverseMoonGenerator.sol ├── MacroverseNFTUtils.sol ├── MacroverseRealEstate.sol ├── MacroverseStarGenerator.sol ├── MacroverseStarGeneratorPatch1.sol ├── MacroverseStarRegistry.sol ├── MacroverseSystemGenerator.sol ├── MacroverseSystemGeneratorPart1.sol ├── MacroverseSystemGeneratorPart2.sol ├── MacroverseTerrainGenerator.sol ├── MacroverseUniversalRegistry.sol ├── Migrations.sol ├── MinimumBalanceAccessControl.sol ├── OZ1BasicToken.sol ├── OZ1ERC20.sol ├── OZ1ERC20Basic.sol ├── OZ1StandardToken.sol ├── OrbitalMechanics.sol ├── RNG.sol ├── RealMath.sol ├── TestnetMRVToken.sol └── UnrestrictedAccessControl.sol ├── example.env ├── migrations ├── 10_deploy_existence_checker.js ├── 11_deactivate_old_registry.js ├── 12_deploy_universal_registry.js ├── 13_reclaim_real_estate.js ├── 14_redeploy_universal_registry.js ├── 15_deploy_terrain_generator.js ├── 1_initial_migration.js ├── 2_deploy_contracts.js ├── 3_replace_star_generator.js ├── 4_replace_real_math.js ├── 5_deploy_star_generator_patch.js ├── 6_deploy_system_generator.js ├── 7_deploy_orbital_mechanics.js ├── 8_deploy_moon_generator.js └── 9_deploy_nft_utils.js ├── package-lock.json ├── package.json ├── proto ├── OrbitControls.js ├── three.js └── trixel.html ├── src └── index.js ├── test ├── SizeMacroverseUniversalRegistry.js ├── TestERC20Returns.sol ├── TestMRVToken.js ├── TestMacroverseExistenceChecker.js ├── TestMacroverseMoonGenerator.js ├── TestMacroverseRealEstate.js ├── TestMacroverseStarGenerator.js ├── TestMacroverseStarRegistry.js ├── TestMacroverseSystemGenerator.js ├── TestMacroverseTerrainGenerator.js ├── TestMacroverseUniversalRegistry.js ├── TestMinimumBalanceAccessControl.js ├── TestOrbitalMechanics.js └── TestRealMath.js └── truffle.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .env 4 | keystore/* 5 | node_modules/* 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | .env 4 | .gitattributes 5 | keystore/* 6 | node_modules/* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Portions of this software with no other license specified are licensed under 2 | the following license: 3 | 4 | Copyright (c) 2017-2020 Novak Distributed 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | In copies of the Software that are deployed to the main Ethereum network, 14 | the portions of the Software which restrict access to the Macroverse world to 15 | holders of the Ethereum token 0xAB6CF87a50F17d7F5E1FEaf81B6fE9FfBe8EBF84 may 16 | not be removed, weakened, or otherwise defeated without the explicit 17 | permission of Novak Distributed. 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | 30 | Some portions of this software are licensed under the MIT License given below: 31 | 32 | Copyright (c) 2017-2020 Novak Distributed 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all 42 | copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 50 | SOFTWARE. 51 | 52 | The portions licensed under this less-restrictive license are: 53 | 54 | contracts/RNG.sol 55 | contracts/RealMath.sol 56 | 57 | Some portions of this software are licensed under the MIT License given below: 58 | 59 | The MIT License (MIT) 60 | 61 | Copyright (c) 2016 Smart Contract Solutions, Inc. 62 | 63 | Permission is hereby granted, free of charge, to any person obtaining 64 | a copy of this software and associated documentation files (the 65 | "Software"), to deal in the Software without restriction, including 66 | without limitation the rights to use, copy, modify, merge, publish, 67 | distribute, sublicense, and/or sell copies of the Software, and to 68 | permit persons to whom the Software is furnished to do so, subject to 69 | the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be included 72 | in all copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 75 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 76 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 77 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 78 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 79 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 80 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 81 | 82 | The portions licensed under this less-restrictive license are: 83 | 84 | contracts/OZ1ERC20Basic.sol 85 | contracts/OZ1ERC20.sol 86 | contracts/OZ1BasicToken.sol 87 | contracts/OZ1StandardToken.sol 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Macroverse 2 | ## An entire universe on the Ethereum blockchain 3 | 4 | [Official Site](https://macroverse.io/) 5 | 6 | Macroverse is a project to deploy a procedurally-generated universe to the Ethereum blockchain, suitable for use as a setting for games and a subject for exploration. 7 | 8 | Players are be able to own a token, MRV, that provides access to this procedural world. Players are also able to claim and trade pieces of virtual real estate, with in-game benefits in supported games. 9 | 10 | For full details, see the [whitepaper](https://macroverse.io/MacroverseWhitepaper.pdf) and read the [smart contracts](https://github.com/NovakDistributed/macroverse/tree/master/contracts). 11 | 12 | Macroverse is a project of Novak Distributed. Macroverse is (C) 2017-2020 Novak Distributed. See [LICENSE](LICENSE) for licensing information. 13 | 14 | ## Installation 15 | 16 | Make sure you have a compatible version of the Truffle build tool: 17 | 18 | ``` 19 | npm install -g truffle@5.1.16 20 | ``` 21 | 22 | Then install from source: 23 | 24 | ``` 25 | git clone https://github.com/NovakDistributed/macroverse.git 26 | cd macroverse 27 | npm install --dev 28 | ``` 29 | 30 | And run the tests: 31 | 32 | ``` 33 | truffle test 34 | ``` 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /build/contracts/Macroverse.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Macroverse", 3 | "abi": [], 4 | "metadata": "{\"compiler\":{\"version\":\"0.6.10+commit.00c0fcaf\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{},\"notice\":\"Library which exists to hold types shared across the Macroverse ecosystem. Never actually needs to be linked into any dependents, since it has no functions.\"}},\"settings\":{\"compilationTarget\":{\"/home/anovak/workspace/macroverse/contracts/Macroverse.sol\":\"Macroverse\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/anovak/workspace/macroverse/contracts/Macroverse.sol\":{\"keccak256\":\"0x6310724c21dfcb680881ac605dbc9d72982028b0bc63c5d87da4c5341ec44914\",\"license\":\"UNLICENSED\",\"urls\":[\"bzz-raw://5f49dc347fdc1511c3e39478687d22920d9d102beab470a27be66b63fd8c2342\",\"dweb:/ipfs/QmcQkHtjY7HMzSP95YCLaCfbhVYECZ6nbC47TTFXaJVkBo\"]}},\"version\":1}", 5 | "bytecode": "0x60566023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212200f8c2af1c619c45fbdbed6132230c42287adf5a060aafe64e07773467a3da11a64736f6c634300060a0033", 6 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea26469706673582212200f8c2af1c619c45fbdbed6132230c42287adf5a060aafe64e07773467a3da11a64736f6c634300060a0033", 7 | "immutableReferences": {}, 8 | "sourceMap": "197:746:6:-:0;;;;;;;;;;;;;;;;;;;;;;;;;", 9 | "deployedSourceMap": "197:746:6:-:0;;;;;;;;", 10 | "source": "pragma solidity ^0.6.10;\n\n/**\n * Library which exists to hold types shared across the Macroverse ecosystem.\n * Never actually needs to be linked into any dependents, since it has no functions.\n */\nlibrary Macroverse {\n\n /**\n * Define different types of planet or moon.\n * \n * There are two main progressions:\n * Asteroidal, Lunar, Terrestrial, Jovian are rocky things.\n * Cometary, Europan, Panthalassic, Neptunian are icy/watery things, depending on temperature.\n * The last thing in each series is the gas/ice giant.\n *\n * Asteroidal and Cometary are only valid for moons; we don't track such tiny bodies at system scale.\n *\n * We also have rings and asteroid belts. Rings can only be around planets, and we fake the Roche limit math we really should do.\n * \n */\n enum WorldClass {Asteroidal, Lunar, Terrestrial, Jovian, Cometary, Europan, Panthalassic, Neptunian, Ring, AsteroidBelt}\n\n}\n\n// SPDX-License-Identifier: UNLICENSED\n", 11 | "sourcePath": "/home/anovak/workspace/macroverse/contracts/Macroverse.sol", 12 | "ast": { 13 | "absolutePath": "/home/anovak/workspace/macroverse/contracts/Macroverse.sol", 14 | "exportedSymbols": { 15 | "Macroverse": [ 16 | 739 17 | ] 18 | }, 19 | "id": 740, 20 | "license": "UNLICENSED", 21 | "nodeType": "SourceUnit", 22 | "nodes": [ 23 | { 24 | "id": 726, 25 | "literals": [ 26 | "solidity", 27 | "^", 28 | "0.6", 29 | ".10" 30 | ], 31 | "nodeType": "PragmaDirective", 32 | "src": "0:24:6" 33 | }, 34 | { 35 | "abstract": false, 36 | "baseContracts": [], 37 | "contractDependencies": [], 38 | "contractKind": "library", 39 | "documentation": { 40 | "id": 727, 41 | "nodeType": "StructuredDocumentation", 42 | "src": "26:170:6", 43 | "text": " Library which exists to hold types shared across the Macroverse ecosystem.\n Never actually needs to be linked into any dependents, since it has no functions." 44 | }, 45 | "fullyImplemented": true, 46 | "id": 739, 47 | "linearizedBaseContracts": [ 48 | 739 49 | ], 50 | "name": "Macroverse", 51 | "nodeType": "ContractDefinition", 52 | "nodes": [ 53 | { 54 | "canonicalName": "Macroverse.WorldClass", 55 | "id": 738, 56 | "members": [ 57 | { 58 | "id": 728, 59 | "name": "Asteroidal", 60 | "nodeType": "EnumValue", 61 | "src": "837:10:6" 62 | }, 63 | { 64 | "id": 729, 65 | "name": "Lunar", 66 | "nodeType": "EnumValue", 67 | "src": "849:5:6" 68 | }, 69 | { 70 | "id": 730, 71 | "name": "Terrestrial", 72 | "nodeType": "EnumValue", 73 | "src": "856:11:6" 74 | }, 75 | { 76 | "id": 731, 77 | "name": "Jovian", 78 | "nodeType": "EnumValue", 79 | "src": "869:6:6" 80 | }, 81 | { 82 | "id": 732, 83 | "name": "Cometary", 84 | "nodeType": "EnumValue", 85 | "src": "877:8:6" 86 | }, 87 | { 88 | "id": 733, 89 | "name": "Europan", 90 | "nodeType": "EnumValue", 91 | "src": "887:7:6" 92 | }, 93 | { 94 | "id": 734, 95 | "name": "Panthalassic", 96 | "nodeType": "EnumValue", 97 | "src": "896:12:6" 98 | }, 99 | { 100 | "id": 735, 101 | "name": "Neptunian", 102 | "nodeType": "EnumValue", 103 | "src": "910:9:6" 104 | }, 105 | { 106 | "id": 736, 107 | "name": "Ring", 108 | "nodeType": "EnumValue", 109 | "src": "921:4:6" 110 | }, 111 | { 112 | "id": 737, 113 | "name": "AsteroidBelt", 114 | "nodeType": "EnumValue", 115 | "src": "927:12:6" 116 | } 117 | ], 118 | "name": "WorldClass", 119 | "nodeType": "EnumDefinition", 120 | "src": "820:120:6" 121 | } 122 | ], 123 | "scope": 740, 124 | "src": "197:746:6" 125 | } 126 | ], 127 | "src": "0:984:6" 128 | }, 129 | "legacyAST": { 130 | "absolutePath": "/home/anovak/workspace/macroverse/contracts/Macroverse.sol", 131 | "exportedSymbols": { 132 | "Macroverse": [ 133 | 739 134 | ] 135 | }, 136 | "id": 740, 137 | "license": "UNLICENSED", 138 | "nodeType": "SourceUnit", 139 | "nodes": [ 140 | { 141 | "id": 726, 142 | "literals": [ 143 | "solidity", 144 | "^", 145 | "0.6", 146 | ".10" 147 | ], 148 | "nodeType": "PragmaDirective", 149 | "src": "0:24:6" 150 | }, 151 | { 152 | "abstract": false, 153 | "baseContracts": [], 154 | "contractDependencies": [], 155 | "contractKind": "library", 156 | "documentation": { 157 | "id": 727, 158 | "nodeType": "StructuredDocumentation", 159 | "src": "26:170:6", 160 | "text": " Library which exists to hold types shared across the Macroverse ecosystem.\n Never actually needs to be linked into any dependents, since it has no functions." 161 | }, 162 | "fullyImplemented": true, 163 | "id": 739, 164 | "linearizedBaseContracts": [ 165 | 739 166 | ], 167 | "name": "Macroverse", 168 | "nodeType": "ContractDefinition", 169 | "nodes": [ 170 | { 171 | "canonicalName": "Macroverse.WorldClass", 172 | "id": 738, 173 | "members": [ 174 | { 175 | "id": 728, 176 | "name": "Asteroidal", 177 | "nodeType": "EnumValue", 178 | "src": "837:10:6" 179 | }, 180 | { 181 | "id": 729, 182 | "name": "Lunar", 183 | "nodeType": "EnumValue", 184 | "src": "849:5:6" 185 | }, 186 | { 187 | "id": 730, 188 | "name": "Terrestrial", 189 | "nodeType": "EnumValue", 190 | "src": "856:11:6" 191 | }, 192 | { 193 | "id": 731, 194 | "name": "Jovian", 195 | "nodeType": "EnumValue", 196 | "src": "869:6:6" 197 | }, 198 | { 199 | "id": 732, 200 | "name": "Cometary", 201 | "nodeType": "EnumValue", 202 | "src": "877:8:6" 203 | }, 204 | { 205 | "id": 733, 206 | "name": "Europan", 207 | "nodeType": "EnumValue", 208 | "src": "887:7:6" 209 | }, 210 | { 211 | "id": 734, 212 | "name": "Panthalassic", 213 | "nodeType": "EnumValue", 214 | "src": "896:12:6" 215 | }, 216 | { 217 | "id": 735, 218 | "name": "Neptunian", 219 | "nodeType": "EnumValue", 220 | "src": "910:9:6" 221 | }, 222 | { 223 | "id": 736, 224 | "name": "Ring", 225 | "nodeType": "EnumValue", 226 | "src": "921:4:6" 227 | }, 228 | { 229 | "id": 737, 230 | "name": "AsteroidBelt", 231 | "nodeType": "EnumValue", 232 | "src": "927:12:6" 233 | } 234 | ], 235 | "name": "WorldClass", 236 | "nodeType": "EnumDefinition", 237 | "src": "820:120:6" 238 | } 239 | ], 240 | "scope": 740, 241 | "src": "197:746:6" 242 | } 243 | ], 244 | "src": "0:984:6" 245 | }, 246 | "compiler": { 247 | "name": "solc", 248 | "version": "0.6.10+commit.00c0fcaf.Emscripten.clang" 249 | }, 250 | "networks": { 251 | "1": { 252 | "events": {}, 253 | "links": {}, 254 | "address": "0xE0c2271B6A0Bdab28753EA9b3515871fB1cac977", 255 | "transactionHash": "0xba54e2e7481cf061c4f4ba5761cc8da4e96bc35e4b2158812bb5c5bf17b2c556" 256 | }, 257 | "4": { 258 | "events": {}, 259 | "links": {}, 260 | "address": "0x4aC71cD03A5DBab0A3E40b471404604BD1E14F88", 261 | "transactionHash": "0xa557d0a43e6883a2a731f12e7e47ae15d70186ed974af07a00e6b701780d7b08" 262 | }, 263 | "5777": { 264 | "events": {}, 265 | "links": {}, 266 | "address": "0x8EA342667ac022e5b194Fc94e278eA712AFEBad7", 267 | "transactionHash": "0xdde5ebd48c9d80314e16b5b06c04accecb1865aea8b730566e14b14ec11fbcc3" 268 | } 269 | }, 270 | "schemaVersion": "3.3.1", 271 | "updatedAt": "2020-12-11T02:46:40.791Z", 272 | "devdoc": { 273 | "methods": {} 274 | }, 275 | "userdoc": { 276 | "methods": {}, 277 | "notice": "Library which exists to hold types shared across the Macroverse ecosystem. Never actually needs to be linked into any dependents, since it has no functions." 278 | } 279 | } -------------------------------------------------------------------------------- /contracts/AccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | /** 4 | * Interface for an access control strategy for Macroverse contracts. 5 | * Can be asked if a certain query should be allowed, and will return true or false. 6 | * Allows for different access control strategies (unrestricted, minimum balance, subscription, etc.) to be swapped in. 7 | */ 8 | abstract contract AccessControl { 9 | /** 10 | * Should a query be allowed for this msg.sender (calling contract) and this tx.origin (calling user)? 11 | */ 12 | function allowQuery(address sender, address origin) virtual public view returns (bool); 13 | } 14 | 15 | // SPDX-License-Identifier: UNLICENSED 16 | -------------------------------------------------------------------------------- /contracts/ControlledAccess.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./AccessControl.sol"; 4 | 5 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 6 | 7 | /** 8 | * Represents a contract that is Ownable, and which has methods that are to be protected by an AccessControl strategy selected by the owner. 9 | */ 10 | contract ControlledAccess is Ownable { 11 | 12 | // This AccessControl contract determines who can run onlyControlledAccess methods. 13 | AccessControl accessControl; 14 | 15 | /** 16 | * Make a new ControlledAccess contract, controlling access with the given AccessControl strategy. 17 | */ 18 | constructor(address originalAccessControl) internal { 19 | accessControl = AccessControl(originalAccessControl); 20 | } 21 | 22 | /** 23 | * Change the access control strategy of the prototype. 24 | */ 25 | function changeAccessControl(address newAccessControl) public onlyOwner { 26 | accessControl = AccessControl(newAccessControl); 27 | } 28 | 29 | /** 30 | * Only allow queries approved by the access control contract. 31 | */ 32 | modifier onlyControlledAccess { 33 | if (!accessControl.allowQuery(msg.sender, tx.origin)) revert(); 34 | _; 35 | } 36 | 37 | 38 | } 39 | 40 | // SPDX-License-Identifier: UNLICENSED 41 | -------------------------------------------------------------------------------- /contracts/HasNoContracts.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 3 | 4 | /** 5 | * @title Contracts that should not own Contracts 6 | * @author Remco Bloemen 7 | * @dev Should contracts (anything Ownable) end up being owned by this contract, it allows the owner 8 | * of this contract to reclaim ownership of the contracts. 9 | */ 10 | contract HasNoContracts is Ownable { 11 | 12 | /** 13 | * @dev Reclaim ownership of Ownable contracts 14 | * @param contractAddr The address of the Ownable to be reclaimed. 15 | */ 16 | function reclaimContract(address contractAddr) external onlyOwner { 17 | Ownable contractInst = Ownable(contractAddr); 18 | contractInst.transferOwnership(owner()); 19 | } 20 | } 21 | 22 | // SPDX-License-Identifier: MIT 23 | /* 24 | The MIT License (MIT) 25 | 26 | Copyright (c) 2016 Smart Contract Solutions, Inc. 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining 29 | a copy of this software and associated documentation files (the 30 | "Software"), to deal in the Software without restriction, including 31 | without limitation the rights to use, copy, modify, merge, publish, 32 | distribute, sublicense, and/or sell copies of the Software, and to 33 | permit persons to whom the Software is furnished to do so, subject to 34 | the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included 37 | in all copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 40 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 41 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 42 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 43 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 44 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 45 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | */ 47 | 48 | 49 | -------------------------------------------------------------------------------- /contracts/HasNoEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 3 | 4 | /** 5 | * @title Contracts that should not own Ether 6 | * @author Remco Bloemen 7 | * @author Novak Distributed 8 | * @dev This tries to block incoming ether to prevent accidental loss of Ether. Should Ether end up 9 | * in the contract, it will allow the owner to reclaim this Ether. 10 | * @notice Ether can still be sent to this contract by: 11 | * calling functions labeled `payable` 12 | * `selfdestruct(contract_address)` 13 | * mining directly to the contract address 14 | */ 15 | contract HasNoEther is Ownable { 16 | /** 17 | * @dev Constructor that rejects incoming Ether 18 | * The `payable` flag is added so we can access `msg.value` without compiler warning. If we 19 | * leave out payable, then Solidity will allow inheriting contracts to implement a payable 20 | * constructor. By doing it this way we prevent a payable constructor from working. Alternatively 21 | * we could use assembly to access msg.value. 22 | */ 23 | constructor() public payable { 24 | require(msg.value == 0); 25 | } 26 | /** 27 | * @dev Disallows direct send by throwing in the receive function. 28 | */ 29 | receive() external payable { 30 | revert(); 31 | } 32 | /** 33 | * @dev Transfer all Ether held by the contract to the owner. 34 | */ 35 | function reclaimEther() external onlyOwner { 36 | // For some reason Ownable doesn't insist that the owner is payable. 37 | // This makes it payable. 38 | address(uint160(owner())).transfer(address(this).balance); 39 | } 40 | } 41 | 42 | // SPDX-License-Identifier: MIT 43 | /* 44 | The MIT License (MIT) 45 | 46 | Copyright (c) 2016 Smart Contract Solutions, Inc. 47 | Copyright (c) 2020 Novak Distributed 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of this software and associated documentation files (the 51 | "Software"), to deal in the Software without restriction, including 52 | without limitation the rights to use, copy, modify, merge, publish, 53 | distribute, sublicense, and/or sell copies of the Software, and to 54 | permit persons to whom the Software is furnished to do so, subject to 55 | the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be included 58 | in all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 61 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 62 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 63 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 64 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 65 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 66 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 67 | */ 68 | 69 | -------------------------------------------------------------------------------- /contracts/HasNoTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 3 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 4 | 5 | /** 6 | * @title Contracts that should not own Tokens 7 | * @author Remco Bloemen 8 | * @dev This blocks incoming ERC23 tokens to prevent accidental loss of tokens. 9 | * Should tokens (any IERC20 compatible) end up in the contract, it allows the 10 | * owner to reclaim the tokens. 11 | */ 12 | contract HasNoTokens is Ownable { 13 | 14 | /** 15 | * @dev Reject all ERC23 compatible tokens 16 | */ 17 | function tokenFallback(address /* from_ */, uint256 /* value_ */, bytes calldata /* data_ */) external pure { 18 | revert(); 19 | } 20 | 21 | /** 22 | * @dev Reclaim all IERC20 compatible tokens 23 | * @param tokenAddr address The address of the token contract 24 | */ 25 | function reclaimToken(address tokenAddr) external onlyOwner { 26 | IERC20 tokenInst = IERC20(tokenAddr); 27 | uint256 balance = tokenInst.balanceOf(address(this)); 28 | tokenInst.transfer(owner(), balance); 29 | } 30 | } 31 | 32 | // SPDX-License-Identifier: MIT 33 | /* 34 | The MIT License (MIT) 35 | 36 | Copyright (c) 2016 Smart Contract Solutions, Inc. 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining 39 | a copy of this software and associated documentation files (the 40 | "Software"), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included 47 | in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 50 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 52 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 53 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 54 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 55 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | */ 57 | 58 | 59 | -------------------------------------------------------------------------------- /contracts/Macroverse.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | /** 4 | * Library which exists to hold types shared across the Macroverse ecosystem. 5 | * Never actually needs to be linked into any dependents, since it has no functions. 6 | */ 7 | library Macroverse { 8 | 9 | /** 10 | * Define different types of planet or moon. 11 | * 12 | * There are two main progressions: 13 | * Asteroidal, Lunar, Terrestrial, Jovian are rocky things. 14 | * Cometary, Europan, Panthalassic, Neptunian are icy/watery things, depending on temperature. 15 | * The last thing in each series is the gas/ice giant. 16 | * 17 | * Asteroidal and Cometary are only valid for moons; we don't track such tiny bodies at system scale. 18 | * 19 | * We also have rings and asteroid belts. Rings can only be around planets, and we fake the Roche limit math we really should do. 20 | * 21 | */ 22 | enum WorldClass {Asteroidal, Lunar, Terrestrial, Jovian, Cometary, Europan, Panthalassic, Neptunian, Ring, AsteroidBelt} 23 | 24 | } 25 | 26 | // SPDX-License-Identifier: UNLICENSED 27 | -------------------------------------------------------------------------------- /contracts/MacroverseExistenceChecker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 4 | import "./HasNoEther.sol"; 5 | import "./HasNoContracts.sol"; 6 | 7 | import "./MacroverseNFTUtils.sol"; 8 | import "./MacroverseStarGenerator.sol"; 9 | import "./MacroverseStarGeneratorPatch1.sol"; 10 | import "./MacroverseSystemGenerator.sol"; 11 | import "./MacroverseMoonGenerator.sol"; 12 | import "./Macroverse.sol"; 13 | 14 | /** 15 | * The MacroverseExistenceChecker queries Macroverse generator contracts to 16 | * determine if a particular thing (e.g. the nth planet of such-and-such a 17 | * star) exists in the Macroverse world. 18 | * 19 | * It does not need to be ControlledAccess because the Macroverse contracts it 20 | * calls into are. It does not have defenses against receiving stuck Ether and 21 | * tokens because it is not intended to be involved in end-user token 22 | * transactions in any capacity. 23 | * 24 | * Serves as an example for how Macroverse can be queried from on-chain logic. 25 | */ 26 | contract MacroverseExistenceChecker { 27 | 28 | using MacroverseNFTUtils for uint256; 29 | 30 | // These constants are shared with the TokenUtils library 31 | 32 | // Define the types of tokens that can exist 33 | uint256 constant TOKEN_TYPE_SECTOR = 0; 34 | uint256 constant TOKEN_TYPE_SYSTEM = 1; 35 | uint256 constant TOKEN_TYPE_PLANET = 2; 36 | uint256 constant TOKEN_TYPE_MOON = 3; 37 | // Land tokens are a range of type field values. 38 | // Land tokens of the min type use one trixel field 39 | uint256 constant TOKEN_TYPE_LAND_MIN = 4; 40 | uint256 constant TOKEN_TYPE_LAND_MAX = 31; 41 | 42 | // Sentinel for no moon used (for land on a planet) 43 | uint16 constant MOON_NONE = 0xFFFF; 44 | 45 | // These constants are shared with the generator contracts 46 | 47 | // How far out does the sector system extend? 48 | int16 constant MAX_SECTOR = 10000; 49 | 50 | // 51 | // Contract state 52 | // 53 | 54 | // Keep track of all of the generator contract addresses 55 | MacroverseStarGenerator private starGenerator; 56 | MacroverseStarGeneratorPatch1 private starGeneratorPatch; 57 | MacroverseSystemGenerator private systemGenerator; 58 | MacroverseMoonGenerator private moonGenerator; 59 | 60 | /** 61 | * Deploy a new copy of the Macroverse Existence Checker. 62 | * 63 | * The given generator contracts will be queried. 64 | */ 65 | constructor(address starGeneratorAddress, address starGeneratorPatchAddress, 66 | address systemGeneratorAddress, address moonGeneratorAddress) public { 67 | 68 | // Remember where all the generators are 69 | starGenerator = MacroverseStarGenerator(starGeneratorAddress); 70 | starGeneratorPatch = MacroverseStarGeneratorPatch1(starGeneratorPatchAddress); 71 | systemGenerator = MacroverseSystemGenerator(systemGeneratorAddress); 72 | moonGenerator = MacroverseMoonGenerator(moonGeneratorAddress); 73 | 74 | } 75 | 76 | /** 77 | * Return true if a sector with the given coordinates exists in the 78 | * Macroverse universe, and false otherwise. 79 | */ 80 | function sectorExists(int16 sectorX, int16 sectorY, int16 sectorZ) public pure returns (bool) { 81 | // Enforce absolute bounds. 82 | if (sectorX > MAX_SECTOR) return false; 83 | if (sectorY > MAX_SECTOR) return false; 84 | if (sectorZ > MAX_SECTOR) return false; 85 | if (sectorX < -MAX_SECTOR) return false; 86 | if (sectorY < -MAX_SECTOR) return false; 87 | if (sectorZ < -MAX_SECTOR) return false; 88 | 89 | return true; 90 | } 91 | 92 | /** 93 | * Determine if the given system (which might be a star, black hole, etc.) 94 | * exists in the given sector. If the sector doesn't exist, returns false. 95 | */ 96 | function systemExists(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 system) public view returns (bool) { 97 | if (!sectorExists(sectorX, sectorY, sectorZ)) { 98 | // The system can't exist if the sector doesn't. 99 | return false; 100 | } 101 | 102 | // If the sector does exist, the system exists if it is in bounds 103 | return (system < starGenerator.getSectorObjectCount(sectorX, sectorY, sectorZ)); 104 | } 105 | 106 | 107 | /** 108 | * Determine if the given planet exists, and if so returns some information 109 | * generated about it for re-use. 110 | */ 111 | function planetExistsVerbose(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 system, uint16 planet) internal view returns (bool exists, 112 | bytes32 systemSeed, uint16 totalPlanets) { 113 | 114 | if (!systemExists(sectorX, sectorY, sectorZ, system)) { 115 | // The planet can't exist if the parent system doesn't. 116 | exists = false; 117 | } else { 118 | // Get the system seed for the parent star/black hole/whatever 119 | // TODO: unify with above to save on derives? 120 | systemSeed = starGenerator.getSectorObjectSeed(sectorX, sectorY, sectorZ, system); 121 | 122 | // Get class and spectral type 123 | MacroverseStarGenerator.ObjectClass systemClass = starGenerator.getObjectClass(systemSeed); 124 | MacroverseStarGenerator.SpectralType systemType = starGenerator.getObjectSpectralType(systemSeed, systemClass); 125 | 126 | if (starGenerator.getObjectHasPlanets(systemSeed, systemClass, systemType)) { 127 | // There are some planets. Are there enough? 128 | totalPlanets = starGeneratorPatch.getObjectPlanetCount(systemSeed, systemClass, systemType); 129 | exists = (planet < totalPlanets); 130 | } else { 131 | // This system doesn't actually have planets 132 | exists = false; 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * Determine if the given moon exists, and if so returns some information 139 | * generated about it for re-use. 140 | */ 141 | function moonExistsVerbose(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 system, uint16 planet, uint16 moon) public view returns (bool exists, 142 | bytes32 planetSeed, Macroverse.WorldClass planetClass) { 143 | 144 | (bool havePlanet, bytes32 systemSeed, uint16 totalPlanets) = planetExistsVerbose(sectorX, sectorY, sectorZ, system, planet); 145 | 146 | if (!havePlanet) { 147 | // Moon can't exist without its planet 148 | exists = false; 149 | } else { 150 | 151 | // Otherwise, work out the seed of the planet. 152 | planetSeed = systemGenerator.getWorldSeed(systemSeed, planet); 153 | 154 | // Use it to get the class of the planet, which is important for knowing if there is a moon 155 | planetClass = systemGenerator.getPlanetClass(planetSeed, planet, totalPlanets); 156 | 157 | // Count its moons 158 | uint16 moonCount = moonGenerator.getPlanetMoonCount(planetSeed, planetClass); 159 | 160 | // This moon exists if it is less than the count 161 | exists = (moon < moonCount); 162 | } 163 | } 164 | 165 | /** 166 | * Determine if the given planet exists. 167 | */ 168 | function planetExists(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 system, uint16 planet) public view returns (bool) { 169 | // Get only one return value. Ignore the others with these extra commas 170 | (bool exists, , ) = planetExistsVerbose(sectorX, sectorY, sectorZ, system, planet); 171 | 172 | // Caller only cares about existence 173 | return exists; 174 | } 175 | 176 | /** 177 | * Determine if the given moon exists. 178 | */ 179 | function moonExists(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 system, uint16 planet, uint16 moon) public view returns (bool) { 180 | // Get only the existence flag 181 | (bool exists, , ) = moonExistsVerbose(sectorX, sectorY, sectorZ, system, planet, moon); 182 | 183 | // Return it 184 | return exists; 185 | } 186 | 187 | /** 188 | * Determine if the thing referred to by the given packed NFT token number 189 | * exists. 190 | * 191 | * Token is assumed to be canonical/valid. Use MacroverseNFTUtils 192 | * tokenIsCanonical() to validate it first. 193 | */ 194 | function exists(uint256 token) public view returns (bool) { 195 | // Get the type 196 | uint256 tokenType = token.getTokenType(); 197 | 198 | // Unpack the sector. There's always a sector. 199 | (int16 sectorX, int16 sectorY, int16 sectorZ) = token.getTokenSector(); 200 | 201 | if (tokenType == TOKEN_TYPE_SECTOR) { 202 | // Check if the requested sector exists 203 | return sectorExists(sectorX, sectorY, sectorZ); 204 | } 205 | 206 | // There must be a system number 207 | uint16 system = token.getTokenSystem(); 208 | 209 | if (tokenType == TOKEN_TYPE_SYSTEM) { 210 | // Check if the requested system exists 211 | return systemExists(sectorX, sectorY, sectorZ, system); 212 | } 213 | 214 | // There must be a planet number 215 | uint16 planet = token.getTokenPlanet(); 216 | 217 | // And there may be a moon 218 | uint16 moon = token.getTokenMoon(); 219 | 220 | if (tokenType == TOKEN_TYPE_PLANET) { 221 | // We exist if the planet exists. 222 | // TODO: maybe check for ring/asteroid field types and don't let their land exist at all? 223 | return planetExists(sectorX, sectorY, sectorZ, system, planet); 224 | } 225 | 226 | if (tokenType == TOKEN_TYPE_MOON) { 227 | // We exist if the moon exists 228 | return moonExists(sectorX, sectorY, sectorZ, system, planet, moon); 229 | } 230 | 231 | // Otherwise it must be land. 232 | assert(token.tokenIsLand()); 233 | 234 | // We exist if the planet or moon exists and isn't a ring or asteroid belt 235 | // We need the parent existence flag 236 | bool haveParent; 237 | // We will need a seed scratch. 238 | bytes32 seed; 239 | 240 | if (moon == MOON_NONE) { 241 | // Make sure the planet exists and isn't a ring 242 | uint16 totalPlanets; 243 | (haveParent, seed, totalPlanets) = planetExistsVerbose(sectorX, sectorY, sectorZ, system, planet); 244 | 245 | if (!haveParent) { 246 | return false; 247 | } 248 | 249 | // Get the planet's seed 250 | seed = systemGenerator.getWorldSeed(seed, planet); 251 | 252 | // Land exists if the planet isn't an AsteroidBelt 253 | return systemGenerator.getPlanetClass(seed, planet, totalPlanets) != Macroverse.WorldClass.AsteroidBelt; 254 | 255 | } else { 256 | // Make sure the moon exists and isn't a ring 257 | Macroverse.WorldClass planetClass; 258 | (haveParent, seed, planetClass) = moonExistsVerbose(sectorX, sectorY, sectorZ, system, planet, moon); 259 | 260 | if (!haveParent) { 261 | return false; 262 | } 263 | 264 | // Get the moon's seed 265 | seed = systemGenerator.getWorldSeed(seed, moon); 266 | 267 | // Land exists if the moon isn't a Ring 268 | return moonGenerator.getMoonClass(planetClass, seed, moon) != Macroverse.WorldClass.Ring; 269 | } 270 | } 271 | 272 | } 273 | 274 | // SPDX-License-Identifier: UNLICENSED 275 | -------------------------------------------------------------------------------- /contracts/MacroverseMoonGenerator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RNG.sol"; 4 | import "./RealMath.sol"; 5 | 6 | import "./AccessControl.sol"; 7 | import "./ControlledAccess.sol"; 8 | 9 | import "./MacroverseStarGenerator.sol"; 10 | import "./MacroverseSystemGenerator.sol"; 11 | import "./Macroverse.sol"; 12 | 13 | /** 14 | * Represents a Macroverse generator for moons around planets. 15 | * 16 | * Not part of the system generator to keep it from going over the contract 17 | * size limit. 18 | * 19 | * Permission to call methods on this contract is regulated by a configurable 20 | * AccessControl contract. One such set of terms might be to require that the 21 | * account initiating a transaction have a certain minimum MRV token balance. 22 | * 23 | * The owner of this contract reserves the right to supersede it with a new 24 | * version, and to modify the terms for accessing this contract, at any time, 25 | * for any reason, and without notice. This includes the right to indefinitely 26 | * or permanently suspend or terminate access to this contract for any person, 27 | * account, or other contract, or for all persons, accounts, or other 28 | * contracts. The owner also reserves the right to not do any of the above. 29 | */ 30 | contract MacroverseMoonGenerator is ControlledAccess { 31 | // TODO: RNG doesn't get linked against because we can't pass the struct to the library... 32 | using RNG for *; 33 | using RealMath for *; 34 | // No SafeMath or it might confuse RealMath 35 | 36 | /**@dev 37 | * It is useful to have Pi around. 38 | * We can't pull it in from the library. 39 | */ 40 | int128 constant REAL_PI = 3454217652358; 41 | 42 | /**@dev 43 | * How many fractional bits are there? 44 | */ 45 | int256 constant REAL_FBITS = 40; 46 | 47 | /**@dev 48 | * What's the first non-fractional bit 49 | */ 50 | int128 constant REAL_ONE = int128(1) << int128(REAL_FBITS); 51 | 52 | /**@dev 53 | * What's the last fractional bit? 54 | */ 55 | int128 constant REAL_HALF = REAL_ONE >> 1; 56 | 57 | /**@dev 58 | * What's two? Two is pretty useful. 59 | */ 60 | int128 constant REAL_TWO = REAL_ONE << int128(1); 61 | 62 | /**@dev 63 | * For having moons, we need to be able to run the orbit calculations (all 64 | * specified in solar masses for the central mass) on 65 | * Earth-mass-denominated planet masses. 66 | * See the "Equivalent Planetary masses" table at https://en.wikipedia.org/wiki/Astronomical_system_of_units 67 | */ 68 | int256 constant EARTH_MASSES_PER_SOLAR_MASS = 332950; 69 | 70 | /**@dev 71 | * We define the number of earth masses per solar mass as a real, so we don't have to convert it always. 72 | */ 73 | int128 constant REAL_EARTH_MASSES_PER_SOLAR_MASS = int128(EARTH_MASSES_PER_SOLAR_MASS) * REAL_ONE; 74 | 75 | /**@dev 76 | * We also keep a "stowage factor" for planetary material in m^3 per earth mass, at water density, for 77 | * faking planetary radii during moon orbit calculations. 78 | */ 79 | int128 constant REAL_M3_PER_EARTH = 6566501804087548000000000000000000; // 6.566501804087548E33 as an int, 5.97219E21 m^3/earth 80 | 81 | /** 82 | * Deploy a new copy of the MacroverseMoonGenerator. 83 | */ 84 | constructor(address accessControlAddress) ControlledAccess(accessControlAddress) public { 85 | // Nothing to do! 86 | } 87 | 88 | /** 89 | * Get the number of moons a planet has, using its class. Will sometimes return 0; there is no hasMoons boolean flag to check. 90 | * The seed of each moon is obtained from the MacroverseSystemGenerator. 91 | */ 92 | function getPlanetMoonCount(bytes32 planetSeed, Macroverse.WorldClass class) public view onlyControlledAccess returns (uint16) { 93 | // We will roll n of this kind of die and subtract n to get our moon count 94 | int8 die; 95 | int8 n = 2; 96 | // We can also divide by this 97 | int8 divisor = 1; 98 | 99 | if (class == Macroverse.WorldClass.Lunar || class == Macroverse.WorldClass.Europan) { 100 | die = 2; 101 | divisor = 2; 102 | // (2d2 - 2) / 2 = 25% chance of 1, 75% chance of 0 103 | } else if (class == Macroverse.WorldClass.Terrestrial || class == Macroverse.WorldClass.Panthalassic) { 104 | die = 3; 105 | // 2d3-2: https://www.wolframalpha.com/input/?i=roll+2d3 106 | } else if (class == Macroverse.WorldClass.Neptunian) { 107 | die = 8; 108 | n = 2; 109 | divisor = 2; 110 | } else if (class == Macroverse.WorldClass.Jovian) { 111 | die = 6; 112 | n = 3; 113 | divisor = 2; 114 | } else if (class == Macroverse.WorldClass.AsteroidBelt) { 115 | // Just no moons here 116 | return 0; 117 | } else { 118 | // Not real! 119 | revert(); 120 | } 121 | 122 | RNG.RandNode memory node = RNG.RandNode(planetSeed).derive("mooncount"); 123 | 124 | uint16 roll = uint16(node.d(n, die, -n) / int88(divisor)); 125 | 126 | return roll; 127 | } 128 | 129 | /** 130 | * Get the class of a moon, given the moon's seed and the class of its parent planet. 131 | * The seed of each moon is obtained from the MacroverseSystemGenerator. 132 | * The actual moon body properties (i.e. mass) are generated with the MacroverseSystemGenerator as if it were a planet. 133 | */ 134 | function getMoonClass(Macroverse.WorldClass parent, bytes32 moonSeed, uint16 moonNumber) public view onlyControlledAccess 135 | returns (Macroverse.WorldClass) { 136 | 137 | // We can have moons of smaller classes than us only. 138 | // Classes are Asteroidal, Lunar, Terrestrial, Jovian, Cometary, Europan, Panthalassic, Neptunian, Ring, AsteroidBelt 139 | // AsteroidBelts never have moons and never are moons. 140 | // Asteroidal and Cometary planets only ever are moons. 141 | // Moons of the same type (rocky or icy) should be more common than cross-type. 142 | // Jovians can have Neptunian moons 143 | 144 | RNG.RandNode memory moonNode = RNG.RandNode(moonSeed); 145 | 146 | if (moonNumber == 0 && moonNode.derive("ring").d(1, 100, 0) < 20) { 147 | // This should be a ring 148 | return Macroverse.WorldClass.Ring; 149 | } 150 | 151 | // Should we be of the opposite ice/rock type to our parent? 152 | bool crossType = moonNode.derive("crosstype").d(1, 100, 0) < 30; 153 | 154 | // What type is our parent? 0=rock, 1=ice 155 | uint parentType = uint(parent) / 4; 156 | 157 | // What number is the parent in its type? 0=Asteroidal/Cometary, 3=Jovian/Neptunian 158 | // The types happen to be arranged so this works. 159 | uint rankInType = uint(parent) % 4; 160 | 161 | if (parent == Macroverse.WorldClass.Jovian && crossType) { 162 | // Say we can have the gas giant type (Neptunian) 163 | rankInType++; 164 | } 165 | 166 | // Roll a lower rank. Bias upward by subtracting 1 instead of 2, so we more or less round up. 167 | uint lowerRank = uint(moonNode.derive("rank").d(2, int8(rankInType), -1) / 2); 168 | 169 | // Determine the type of the moon (0=rock, 1=ice) 170 | uint moonType = crossType ? parentType : (parentType + 1) % 2; 171 | 172 | return Macroverse.WorldClass(moonType * 4 + lowerRank); 173 | 174 | } 175 | 176 | /** 177 | * Use the mass of a planet to compute its moon scale distance in AU. This is sort of like the Roche limit and must be bigger than the planet's radius. 178 | */ 179 | function getPlanetMoonScale(bytes32 planetSeed, int128 planetRealMass) public view onlyControlledAccess returns (int128) { 180 | // We assume a fictional inverse density of 1 cm^3/g = 5.9721986E21 cubic meters per earth mass 181 | // Then we take cube root of volume / (4/3 pi) to get the radius of such a body 182 | // Then we derive the scale factor from a few times that. 183 | 184 | RNG.RandNode memory node = RNG.RandNode(planetSeed).derive("moonscale"); 185 | 186 | // Get the volume. We can definitely hold Jupiter's volume in m^3 187 | int128 realVolume = planetRealMass.mul(REAL_M3_PER_EARTH); 188 | 189 | // Get the radius in meters 190 | int128 realRadius = realVolume.div(REAL_PI.mul(RealMath.fraction(4, 3))).pow(RealMath.fraction(1, 3)); 191 | 192 | // Return some useful, randomized multiple of it. 193 | return realRadius.mul(node.getRealBetween(RealMath.fraction(5, 2), RealMath.fraction(7, 2))); 194 | } 195 | 196 | /** 197 | * Given the parent planet's scale radius, a moon's seed, the moon's class, and the previous moon's outer clearance (or 0), return the orbit shape of the moon. 198 | * Other orbit properties come from the system generator. 199 | */ 200 | function getMoonOrbitDimensions(int128 planetMoonScale, bytes32 seed, Macroverse.WorldClass class, int128 realPrevClearance) 201 | public view onlyControlledAccess returns (int128 realPeriapsis, int128 realApoapsis, int128 realClearance) { 202 | 203 | RNG.RandNode memory moonNode = RNG.RandNode(seed); 204 | 205 | if (class == Macroverse.WorldClass.Ring) { 206 | // Rings are special 207 | realPeriapsis = realPrevClearance + planetMoonScale.mul(REAL_HALF).mul(moonNode.derive("ringstart").getRealBetween(REAL_ONE, REAL_TWO)); 208 | realApoapsis = realPeriapsis + realPeriapsis.mul(moonNode.derive("ringwidth").getRealBetween(REAL_HALF, REAL_TWO)); 209 | realClearance = realApoapsis + planetMoonScale.mul(REAL_HALF).mul(moonNode.derive("ringclear").getRealBetween(REAL_HALF, REAL_TWO)); 210 | } else { 211 | // Otherwise just roll some stuff 212 | realPeriapsis = realPrevClearance + planetMoonScale.mul(moonNode.derive("periapsis").getRealBetween(REAL_HALF, REAL_ONE)); 213 | realApoapsis = realPeriapsis.mul(moonNode.derive("apoapsis").getRealBetween(REAL_ONE, RealMath.fraction(120, 100))); 214 | 215 | if (class == Macroverse.WorldClass.Asteroidal || class == Macroverse.WorldClass.Cometary) { 216 | // Captured tiny things should be more eccentric 217 | realApoapsis = realApoapsis + (realApoapsis - realPeriapsis).mul(REAL_TWO); 218 | } 219 | 220 | realClearance = realApoapsis.mul(moonNode.derive("clearance").getRealBetween(RealMath.fraction(110, 100), RealMath.fraction(130, 100))); 221 | } 222 | } 223 | 224 | /** 225 | * Get the inclination (angle from parent body's equatorial plane to orbital plane at the ascending node) for a moon. 226 | * Inclination is always positive. If it were negative, the ascending node would really be the descending node. 227 | * Result is a real in radians. 228 | */ 229 | function getMoonInclination(bytes32 seed, Macroverse.WorldClass class) public view onlyControlledAccess returns (int128 real_inclination) { 230 | 231 | RNG.RandNode memory node = RNG.RandNode(seed).derive("inclination"); 232 | 233 | // Define maximum inclination in milliradians 234 | // 175 milliradians = ~ 10 degrees 235 | int88 maximum; 236 | if (class == Macroverse.WorldClass.Asteroidal || class == Macroverse.WorldClass.Cometary) { 237 | // Tiny captured things can be pretty free 238 | maximum = 850; 239 | } else if (class == Macroverse.WorldClass.Lunar || class == Macroverse.WorldClass.Europan) { 240 | maximum = 100; 241 | } else if (class == Macroverse.WorldClass.Terrestrial || class == Macroverse.WorldClass.Panthalassic) { 242 | maximum = 80; 243 | } else if (class == Macroverse.WorldClass.Neptunian) { 244 | maximum = 50; 245 | } else if (class == Macroverse.WorldClass.Ring) { 246 | maximum = 350; 247 | } else { 248 | // Not real! 249 | revert(); 250 | } 251 | 252 | // Compute the inclination 253 | real_inclination = node.getRealBetween(0, RealMath.toReal(maximum)).div(RealMath.toReal(1000)); 254 | 255 | if (node.derive("retrograde").d(1, 100, 0) < 10) { 256 | // This moon ought to move retrograde (subtract inclination from pi instead of adding it to 0) 257 | real_inclination = REAL_PI - real_inclination; 258 | } 259 | 260 | return real_inclination; 261 | } 262 | } 263 | 264 | // SPDX-License-Identifier: UNLICENSED 265 | -------------------------------------------------------------------------------- /contracts/MacroverseRealEstate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; 4 | 5 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 6 | 7 | import "openzeppelin-solidity/contracts/utils/Strings.sol"; 8 | 9 | /** 10 | * The MacroverseRealEstate contract keeps track of who currently owns virtual 11 | * real estate in the Macroverse world, at all scales. It supersedes the 12 | * MacroverseStarRegistry. Registration and Macroverse-specific manipulation of 13 | * tokens is accomplished through the MacroverseUniversalRegistry, which owns 14 | * this contract. 15 | * 16 | * The split between this contract and the MacroverseUniversalRegistry exists 17 | * to keep contract size under the limit. 18 | */ 19 | contract MacroverseRealEstate is ERC721, Ownable { 20 | 21 | 22 | /** 23 | * Deploy the backend, taking mint, burn, and set-user commands from the deployer. 24 | * Use the given domain as the domain for token URIs. 25 | */ 26 | constructor(string memory domain) public ERC721("Macroverse Real Estate", "MRE") { 27 | _setTokenMetadataDomain(domain); 28 | } 29 | 30 | /** 31 | * Allow this contract to change the ERC721 metadata URI domain. 32 | */ 33 | function _setTokenMetadataDomain(string memory domain) internal { 34 | // Set up new OpenZeppelin 3.0 automatic token URI system. 35 | // Good thing we match their format or we'd have to fork OZ. 36 | uint chainId = 0; 37 | assembly { 38 | chainId := chainid() 39 | } 40 | _setBaseURI(string(abi.encodePacked("https://", domain, "/vre/v1/chain/", Strings.toString(chainId), "/token/"))); 41 | } 42 | 43 | /** 44 | * Allow this the owner to change the ERC721 metadata URI domain. 45 | */ 46 | function setTokenMetadataDomain(string memory domain) external onlyOwner { 47 | _setTokenMetadataDomain(domain); 48 | } 49 | 50 | /** 51 | * Mint tokens at the direction of the owning contract. 52 | */ 53 | function mint(address to, uint256 tokenId) external onlyOwner { 54 | _mint(to, tokenId); 55 | } 56 | 57 | /** 58 | * Burn tokens at the direction of the owning contract. 59 | */ 60 | function burn(uint256 tokenId) external onlyOwner { 61 | _burn(tokenId); 62 | } 63 | 64 | /** 65 | * Publically expose a token existence test. Returns true if the given 66 | * token is owned by someone, and false otherwise. Note that tokens sent to 67 | * 0x0 but not burned may still exist. 68 | */ 69 | function exists(uint256 tokenId) external view returns (bool) { 70 | return _exists(tokenId); 71 | } 72 | } 73 | 74 | // SPDX-License-Identifier: UNLICENSED 75 | -------------------------------------------------------------------------------- /contracts/MacroverseStarGenerator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RNG.sol"; 4 | import "./RealMath.sol"; 5 | 6 | import "./AccessControl.sol"; 7 | import "./ControlledAccess.sol"; 8 | 9 | /** 10 | * Represents a Macroverse Generator for a galaxy. 11 | * 12 | * Permission to call methods on this contract is regulated by a configurable 13 | * AccessControl contract. One such set of terms might be to require that the 14 | * account initiating a transaction have a certain minimum MRV token balance. 15 | * 16 | * The owner of this contract reserves the right to supersede it with a new 17 | * version, and to modify the terms for accessing this contract, at any time, 18 | * for any reason, and without notice. This includes the right to indefinitely 19 | * or permanently suspend or terminate access to this contract for any person, 20 | * account, or other contract, or for all persons, accounts, or other 21 | * contracts. The owner also reserves the right to not do any of the above. 22 | */ 23 | contract MacroverseStarGenerator is ControlledAccess { 24 | // TODO: RNG doesn't get linked against because we can't pass the struct to the library... 25 | using RNG for *; 26 | using RealMath for *; 27 | // No SafeMath or it might confuse RealMath 28 | 29 | // How big is a sector on a side in LY? 30 | int16 constant SECTOR_SIZE = 25; 31 | // How far out does the sector system extend? 32 | int16 constant MAX_SECTOR = 10000; 33 | // How big is the galaxy? 34 | int16 constant DISK_RADIUS_IN_SECTORS = 6800; 35 | // How thick is its disk? 36 | int16 constant DISK_HALFHEIGHT_IN_SECTORS = 40; 37 | // How big is the central sphere? 38 | int16 constant CORE_RADIUS_IN_SECTORS = 1000; 39 | 40 | // There are kinds of stars. 41 | // We can add more later; these are from http://www.mit.edu/afs.new/sipb/user/sekullbe/furble/planet.txt 42 | // 0 1 2 3 4 5 43 | enum ObjectClass { Supergiant, Giant, MainSequence, WhiteDwarf, NeutronStar, BlackHole } 44 | // Actual stars have a spectral type 45 | // 0 1 2 3 4 5 6 7 46 | enum SpectralType { TypeO, TypeB, TypeA, TypeF, TypeG, TypeK, TypeM, NotApplicable } 47 | // Each type has subtypes 0-9, except O which only has 5-9 48 | 49 | // This root RandNode provides the seed for the universe. 50 | RNG.RandNode root; 51 | 52 | /** 53 | * Deploy a new copy of the Macroverse generator contract. Use the given seed to generate a galaxy, down to the star level. 54 | * Use the contract at the given address to regulate access. 55 | */ 56 | constructor(bytes32 baseSeed, address accessControlAddress) ControlledAccess(accessControlAddress) public { 57 | root = RNG.RandNode(baseSeed); 58 | } 59 | 60 | /** 61 | * Get the density (between 0 and 1 as a fixed-point real88x40) of stars in the given sector. Sector 0,0,0 is centered on the galactic origin. 62 | * +Y is upwards. 63 | */ 64 | function getGalaxyDensity(int16 sectorX, int16 sectorY, int16 sectorZ) public view onlyControlledAccess returns (int128 realDensity) { 65 | // We have a central sphere and a surrounding disk. 66 | 67 | // Enforce absolute bounds. 68 | if (sectorX > MAX_SECTOR) return 0; 69 | if (sectorY > MAX_SECTOR) return 0; 70 | if (sectorZ > MAX_SECTOR) return 0; 71 | if (sectorX < -MAX_SECTOR) return 0; 72 | if (sectorY < -MAX_SECTOR) return 0; 73 | if (sectorZ < -MAX_SECTOR) return 0; 74 | 75 | if (int(sectorX) * int(sectorX) + int(sectorY) * int(sectorY) + int(sectorZ) * int(sectorZ) < int(CORE_RADIUS_IN_SECTORS) * int(CORE_RADIUS_IN_SECTORS)) { 76 | // Central sphere 77 | return RealMath.fraction(9, 10); 78 | } else if (int(sectorX) * int(sectorX) + int(sectorZ) * int(sectorZ) < int(DISK_RADIUS_IN_SECTORS) * int(DISK_RADIUS_IN_SECTORS) && sectorY < DISK_HALFHEIGHT_IN_SECTORS && sectorY > -DISK_HALFHEIGHT_IN_SECTORS) { 79 | // Disk 80 | return RealMath.fraction(1, 2); 81 | } else { 82 | // General background object rate 83 | // Set so that some background sectors do indeed have an object in them. 84 | return RealMath.fraction(1, 60); 85 | } 86 | } 87 | 88 | /** 89 | * Get the number of objects in the sector at the given coordinates. 90 | */ 91 | function getSectorObjectCount(int16 sectorX, int16 sectorY, int16 sectorZ) public view onlyControlledAccess returns (uint16) { 92 | // Decide on a base item count 93 | RNG.RandNode memory sectorNode = root.derive(sectorX).derive(sectorY).derive(sectorZ); 94 | int16 maxObjects = sectorNode.derive("count").d(3, 20, 0); 95 | 96 | // Multiply by the density function 97 | int128 presentObjects = RealMath.toReal(maxObjects).mul(getGalaxyDensity(sectorX, sectorY, sectorZ)); 98 | 99 | return uint16(RealMath.fromReal(RealMath.round(presentObjects))); 100 | } 101 | 102 | /** 103 | * Get the seed for an object in a sector. 104 | */ 105 | function getSectorObjectSeed(int16 sectorX, int16 sectorY, int16 sectorZ, uint16 object) public view onlyControlledAccess returns (bytes32) { 106 | return root.derive(sectorX).derive(sectorY).derive(sectorZ).derive(uint(object))._hash; 107 | } 108 | 109 | /** 110 | * Get the class of the star system with the given seed. 111 | */ 112 | function getObjectClass(bytes32 seed) public view onlyControlledAccess returns (ObjectClass) { 113 | // Make a node for rolling for the class. 114 | RNG.RandNode memory node = RNG.RandNode(seed).derive("class"); 115 | // Roll an impractical d10,000 116 | int88 roll = node.getIntBetween(1, 10000); 117 | 118 | if (roll == 1) { 119 | // Should be a black hole 120 | return ObjectClass.BlackHole; 121 | } else if (roll <= 3) { 122 | // Should be a neutron star 123 | return ObjectClass.NeutronStar; 124 | } else if (roll <= 700) { 125 | // Should be a white dwarf 126 | return ObjectClass.WhiteDwarf; 127 | } else if (roll <= 9900) { 128 | // Most things are main sequence 129 | return ObjectClass.MainSequence; 130 | } else if (roll <= 9990) { 131 | return ObjectClass.Giant; 132 | } else { 133 | return ObjectClass.Supergiant; 134 | } 135 | } 136 | 137 | /** 138 | * Get the spectral type for an object with the given seed of the given class. 139 | */ 140 | function getObjectSpectralType(bytes32 seed, ObjectClass objectClass) public view onlyControlledAccess returns (SpectralType) { 141 | RNG.RandNode memory node = RNG.RandNode(seed).derive("type"); 142 | int88 roll = node.getIntBetween(1, 10000000); // Even more implausible dice 143 | 144 | if (objectClass == ObjectClass.MainSequence) { 145 | if (roll <= 3) { 146 | return SpectralType.TypeO; 147 | } else if (roll <= 13003) { 148 | return SpectralType.TypeB; 149 | } else if (roll <= 73003) { 150 | return SpectralType.TypeA; 151 | } else if (roll <= 373003) { 152 | return SpectralType.TypeF; 153 | } else if (roll <= 1133003) { 154 | return SpectralType.TypeG; 155 | } else if (roll <= 2343003) { 156 | return SpectralType.TypeK; 157 | } else { 158 | return SpectralType.TypeM; 159 | } 160 | } else if (objectClass == ObjectClass.Giant) { 161 | if (roll <= 500000) { 162 | return SpectralType.TypeF; 163 | } else if (roll <= 1000000) { 164 | return SpectralType.TypeG; 165 | } else if (roll <= 5500000) { 166 | return SpectralType.TypeK; 167 | } else { 168 | return SpectralType.TypeM; 169 | } 170 | } else if (objectClass == ObjectClass.Supergiant) { 171 | if (roll <= 1000000) { 172 | return SpectralType.TypeB; 173 | } else if (roll <= 2000000) { 174 | return SpectralType.TypeA; 175 | } else if (roll <= 4000000) { 176 | return SpectralType.TypeF; 177 | } else if (roll <= 6000000) { 178 | return SpectralType.TypeG; 179 | } else if (roll <= 8000000) { 180 | return SpectralType.TypeK; 181 | } else { 182 | return SpectralType.TypeM; 183 | } 184 | } else { 185 | // TODO: No spectral class for anyone else. 186 | return SpectralType.NotApplicable; 187 | } 188 | 189 | } 190 | 191 | /** 192 | * Get the position of a star within its sector, as reals from 0 to 25. 193 | * Note that stars may end up implausibly close together. Such is life in the Macroverse. 194 | */ 195 | function getObjectPosition(bytes32 seed) public view onlyControlledAccess returns (int128 realX, int128 realY, int128 realZ) { 196 | RNG.RandNode memory node = RNG.RandNode(seed).derive("position"); 197 | 198 | realX = node.derive("x").getRealBetween(RealMath.toReal(0), RealMath.toReal(25)); 199 | realY = node.derive("y").getRealBetween(RealMath.toReal(0), RealMath.toReal(25)); 200 | realZ = node.derive("z").getRealBetween(RealMath.toReal(0), RealMath.toReal(25)); 201 | } 202 | 203 | /** 204 | * Get the mass of a star, in solar masses as a real, given its seed and class and spectral type. 205 | */ 206 | function getObjectMass(bytes32 seed, ObjectClass objectClass, SpectralType spectralType) public view onlyControlledAccess returns (int128) { 207 | RNG.RandNode memory node = RNG.RandNode(seed).derive("mass"); 208 | 209 | if (objectClass == ObjectClass.BlackHole) { 210 | return node.getRealBetween(RealMath.toReal(5), RealMath.toReal(50)); 211 | } else if (objectClass == ObjectClass.NeutronStar) { 212 | return node.getRealBetween(RealMath.fraction(11, 10), RealMath.toReal(2)); 213 | } else if (objectClass == ObjectClass.WhiteDwarf) { 214 | return node.getRealBetween(RealMath.fraction(3, 10), RealMath.fraction(11, 10)); 215 | } else if (objectClass == ObjectClass.MainSequence) { 216 | if (spectralType == SpectralType.TypeO) { 217 | return node.getRealBetween(RealMath.toReal(16), RealMath.toReal(40)); 218 | } else if (spectralType == SpectralType.TypeB) { 219 | return node.getRealBetween(RealMath.fraction(21, 10), RealMath.toReal(16)); 220 | } else if (spectralType == SpectralType.TypeA) { 221 | return node.getRealBetween(RealMath.fraction(14, 10), RealMath.fraction(21, 10)); 222 | } else if (spectralType == SpectralType.TypeF) { 223 | return node.getRealBetween(RealMath.fraction(104, 100), RealMath.fraction(14, 10)); 224 | } else if (spectralType == SpectralType.TypeG) { 225 | return node.getRealBetween(RealMath.fraction(80, 100), RealMath.fraction(104, 100)); 226 | } else if (spectralType == SpectralType.TypeK) { 227 | return node.getRealBetween(RealMath.fraction(45, 100), RealMath.fraction(80, 100)); 228 | } else if (spectralType == SpectralType.TypeM) { 229 | return node.getRealBetween(RealMath.fraction(8, 100), RealMath.fraction(45, 100)); 230 | } 231 | } else if (objectClass == ObjectClass.Giant) { 232 | // Just make it really big 233 | return node.getRealBetween(RealMath.toReal(40), RealMath.toReal(50)); 234 | } else if (objectClass == ObjectClass.Supergiant) { 235 | // Just make it really, really big 236 | return node.getRealBetween(RealMath.toReal(50), RealMath.toReal(70)); 237 | } 238 | } 239 | 240 | /** 241 | * Determine if the given star has any orbiting planets or not. 242 | */ 243 | function getObjectHasPlanets(bytes32 seed, ObjectClass objectClass, SpectralType spectralType) public view onlyControlledAccess returns (bool) { 244 | RNG.RandNode memory node = RNG.RandNode(seed).derive("hasplanets"); 245 | int88 roll = node.getIntBetween(1, 1000); 246 | 247 | if (objectClass == ObjectClass.MainSequence) { 248 | if (spectralType == SpectralType.TypeO || spectralType == SpectralType.TypeB) { 249 | return (roll <= 1); 250 | } else if (spectralType == SpectralType.TypeA) { 251 | return (roll <= 500); 252 | } else if (spectralType == SpectralType.TypeF || spectralType == SpectralType.TypeG || spectralType == SpectralType.TypeK) { 253 | return (roll <= 990); 254 | } else if (spectralType == SpectralType.TypeM) { 255 | return (roll <= 634); 256 | } 257 | } else if (objectClass == ObjectClass.Giant) { 258 | return (roll <= 90); 259 | } else if (objectClass == ObjectClass.Supergiant) { 260 | return (roll <= 50); 261 | } else { 262 | // Black hole, neutron star, or white dwarf 263 | return (roll <= 70); 264 | } 265 | } 266 | 267 | 268 | } 269 | 270 | // SPDX-License-Identifier: UNLICENSED 271 | -------------------------------------------------------------------------------- /contracts/MacroverseStarGeneratorPatch1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RNG.sol"; 4 | import "./RealMath.sol"; 5 | 6 | import "./AccessControl.sol"; 7 | import "./ControlledAccess.sol"; 8 | 9 | import "./MacroverseStarGenerator.sol"; 10 | 11 | /** 12 | * Provides extra methods not present in the original MacroverseStarGenerator 13 | * that generate new properties of the galaxy's stars. Meant to be deployed and 14 | * queried alongside the original. 15 | * 16 | * Permission to call methods on this contract is regulated by a configurable 17 | * AccessControl contract. One such set of terms might be to require that the 18 | * account initiating a transaction have a certain minimum MRV token balance. 19 | * 20 | * The owner of this contract reserves the right to supersede it with a new 21 | * version, and to modify the terms for accessing this contract, at any time, 22 | * for any reason, and without notice. This includes the right to indefinitely 23 | * or permanently suspend or terminate access to this contract for any person, 24 | * account, or other contract, or for all persons, accounts, or other 25 | * contracts. The owner also reserves the right to not do any of the above. 26 | */ 27 | contract MacroverseStarGeneratorPatch1 is ControlledAccess { 28 | // TODO: RNG doesn't get linked against because we can't pass the struct to the library... 29 | using RNG for *; 30 | using RealMath for *; 31 | // No SafeMath or it might confuse RealMath 32 | 33 | /**@dev 34 | * How many fractional bits are there? 35 | */ 36 | int256 constant REAL_FBITS = 40; 37 | 38 | /**@dev 39 | * What's the first non-fractional bit 40 | */ 41 | int128 constant REAL_ONE = int128(1) << int128(REAL_FBITS); 42 | 43 | /**@dev 44 | * What's the last fractional bit? 45 | */ 46 | int128 constant REAL_HALF = REAL_ONE >> 1; 47 | 48 | /**@dev 49 | * It is useful to have Pi around. 50 | * We can't pull it in from the library. 51 | */ 52 | int128 constant REAL_PI = 3454217652358; 53 | 54 | /** 55 | * Deploy a new copy of the patch generator. 56 | * Use the contract at the given address to regulate access. 57 | */ 58 | constructor(address accessControlAddress) ControlledAccess(accessControlAddress) public { 59 | // Nothing to do! 60 | } 61 | 62 | /** 63 | * If the object has any planets at all, get the planet count. Will return 64 | * nonzero numbers always, so make sure to check getObjectHasPlanets in the 65 | * Star Generator. 66 | */ 67 | function getObjectPlanetCount(bytes32 starSeed, MacroverseStarGenerator.ObjectClass objectClass, 68 | MacroverseStarGenerator.SpectralType spectralType) public view onlyControlledAccess returns (uint16) { 69 | 70 | RNG.RandNode memory node = RNG.RandNode(starSeed).derive("planetcount"); 71 | 72 | 73 | uint16 limit; 74 | 75 | if (objectClass == MacroverseStarGenerator.ObjectClass.MainSequence) { 76 | if (spectralType == MacroverseStarGenerator.SpectralType.TypeO || 77 | spectralType == MacroverseStarGenerator.SpectralType.TypeB) { 78 | 79 | limit = 5; 80 | } else if (spectralType == MacroverseStarGenerator.SpectralType.TypeA) { 81 | limit = 7; 82 | } else if (spectralType == MacroverseStarGenerator.SpectralType.TypeF || 83 | spectralType == MacroverseStarGenerator.SpectralType.TypeG || 84 | spectralType == MacroverseStarGenerator.SpectralType.TypeK) { 85 | 86 | limit = 12; 87 | } else if (spectralType == MacroverseStarGenerator.SpectralType.TypeM) { 88 | limit = 14; 89 | } 90 | } else if (objectClass == MacroverseStarGenerator.ObjectClass.Giant) { 91 | limit = 2; 92 | } else if (objectClass == MacroverseStarGenerator.ObjectClass.Supergiant) { 93 | limit = 2; 94 | } else { 95 | // Black hole, neutron star, or white dwarf 96 | limit = 2; 97 | } 98 | 99 | uint16 roll = uint16(node.getIntBetween(1, int88(limit + 1))); 100 | 101 | return roll; 102 | } 103 | 104 | /** 105 | * Compute the luminosity of a stellar object given its mass and class. 106 | * We didn't define this in the star generator, but we need it for the planet generator. 107 | * 108 | * Returns luminosity in solar luminosities. 109 | */ 110 | function getObjectLuminosity(bytes32 starSeed, MacroverseStarGenerator.ObjectClass objectClass, int128 realObjectMass) public view onlyControlledAccess returns (int128) { 111 | 112 | RNG.RandNode memory node = RNG.RandNode(starSeed); 113 | 114 | int128 realBaseLuminosity; 115 | if (objectClass == MacroverseStarGenerator.ObjectClass.BlackHole) { 116 | // Black hole luminosity is going to be from the accretion disk. 117 | // See 118 | // We'll return pretty much whatever and user code can back-fill the accretion disk if any. 119 | if(node.derive("accretiondisk").getBool()) { 120 | // These aren't absurd masses; they're on the order of world annual food production per second. 121 | realBaseLuminosity = node.derive("luminosity").getRealBetween(RealMath.toReal(1), RealMath.toReal(5)); 122 | } else { 123 | // No accretion disk 124 | realBaseLuminosity = 0; 125 | } 126 | } else if (objectClass == MacroverseStarGenerator.ObjectClass.NeutronStar) { 127 | // These will be dim and not really mass-related 128 | realBaseLuminosity = node.derive("luminosity").getRealBetween(RealMath.fraction(1, 20), RealMath.fraction(2, 10)); 129 | } else if (objectClass == MacroverseStarGenerator.ObjectClass.WhiteDwarf) { 130 | // These are also dim 131 | realBaseLuminosity = RealMath.pow(realObjectMass.mul(REAL_HALF), RealMath.fraction(35, 10)); 132 | } else { 133 | // Normal stars follow a normal mass-lumoinosity relationship 134 | realBaseLuminosity = RealMath.pow(realObjectMass, RealMath.fraction(35, 10)); 135 | } 136 | 137 | // Perturb the generated luminosity for fun 138 | return realBaseLuminosity.mul(node.derive("luminosityScale").getRealBetween(RealMath.fraction(95, 100), RealMath.fraction(105, 100))); 139 | } 140 | 141 | /** 142 | * Get the inner and outer boundaries of the habitable zone for a star, in meters, based on its luminosity in solar luminosities. 143 | * This is just a rule-of-thumb; actual habitability is going to depend on atmosphere (see Venus, Mars) 144 | */ 145 | function getObjectHabitableZone(int128 realLuminosity) public view onlyControlledAccess returns (int128 realInnerRadius, int128 realOuterRadius) { 146 | // Light per unit area scales with the square of the distance, so if we move twice as far out we get 1/4 the light. 147 | // So if our star is half as bright as the sun, the habitable zone radius is 1/sqrt(2) = sqrt(1/2) as big 148 | // So we scale this by the square root of the luminosity. 149 | int128 realScale = RealMath.sqrt(realLuminosity); 150 | // Wikipedia says nobody knows the bounds for Sol, but let's say 0.75 to 2.0 AU to be nice and round and also sort of average 151 | realInnerRadius = RealMath.toReal(112198400000).mul(realScale); 152 | realOuterRadius = RealMath.toReal(299195700000).mul(realScale); 153 | } 154 | 155 | /** 156 | * Get the Y and X axis angles for the rotational axis of the object, relative to galactic up. 157 | * 158 | * Defines a vector normal to the XY plane for the star system's local 159 | * coordinates, relative to which orbital inclinations are measured. 160 | * 161 | * The object's rotation axis starts straight up towards galactic +Z. 162 | * Then the object is rotated in Y, around the axis by the Y angle. 163 | * Then it is rotated forward (what would be toward the viewer) in the 164 | * object's transformed X by the X axis angle. 165 | * Both angles are in radians. 166 | * The X angle is never negative, because the Y angle would just be the opposite direction. 167 | * It is also never greater than Pi, because otherwise we would just measure around the other way. 168 | * 169 | * Most users won't need this unless they want to be able to work out 170 | * directions from things in one system to other systems. 171 | */ 172 | function getObjectYXAxisAngles(bytes32 seed) public view onlyControlledAccess returns (int128 realYRadians, int128 realXRadians) { 173 | // The Y angle should be uniform over all angles. 174 | realYRadians = RNG.RandNode(seed).derive("axisy").getRealBetween(-REAL_PI, REAL_PI); 175 | 176 | // The X angle will also be uniform from 0 to pi. 177 | // This makes us pick a point in a flat 2d angle plane, so we will, on the sphere, have more density towards the poles. 178 | // See http://corysimon.github.io/articles/uniformdistn-on-sphere/ 179 | // Being uniform on the sphere would require some trig, and non-uniformity makes sense since the galaxy has a preferred plane. 180 | realXRadians = RNG.RandNode(seed).derive("axisx").getRealBetween(0, REAL_PI); 181 | 182 | } 183 | 184 | 185 | 186 | } 187 | 188 | // SPDX-License-Identifier: UNLICENSED 189 | -------------------------------------------------------------------------------- /contracts/MacroverseStarRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./MRVToken.sol"; 4 | import "openzeppelin-solidity/contracts/access/Ownable.sol"; 5 | import "./HasNoEther.sol"; 6 | import "./HasNoContracts.sol"; 7 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 8 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 9 | 10 | /** 11 | * The Macroverse Star Registry keeps track of who currently owns virtual real estate in the 12 | * Macroverse world, at the scale of star systems and other star-like objects. 13 | * 14 | * Ownership is handled by having the first person who wants to own an object claim it, by 15 | * putting up a deposit in MRV tokens of a certain minimum size. Note that the owner of the 16 | * contract reserves the right to adjust this minimum size at any time, for any reason, and 17 | * without notice. Since the size of the deposit to be made is specified by the claimant, 18 | * trying to claim something when the minimum deposit size has been increased without your 19 | * knowledge should at worst result in wasted gas. 20 | * 21 | * Owners of objects can send them to other addresses, and an owner can abdicate ownership of 22 | * an object and collect the original MRV deposit used to claim it. 23 | * 24 | * Note that the claiming system used here is not protected against front-running. Anyone can 25 | * see your claim transaction before it is mined, and claim the object you were going for first. 26 | * However, they would then need to find it among the many billions of objects in the Macroverse 27 | * universe, because this contract only records ownership by seed. 28 | * 29 | * Note that ownership of a star system does not necessarily imply ownership of everything in it. 30 | * Just as one person can own a condo in another person's building, one person can own a planet in 31 | * another person's star system. 32 | * 33 | * All ownership is recorded using star seeds; no attempt is made to enforce that a given 34 | * seed actually exists in the Macroverse world. It is possible to purchase (and sell!) 35 | * nonexistent star systems, or star systems that you don't know where they are. Caveat emptor. 36 | * 37 | * The deployer of this contract reserves the right to supersede it with a new version at any time, 38 | * for any reason, and without notice. The deployer of this contract reserves the right to leave it 39 | * in place as is indefinitely. 40 | * 41 | * The deployer of this contract reserves the right to claim and keep any tokens or ETH or contracts 42 | * sent to this contract, in excess of the MRV balance that this contract thinks it is supposed to 43 | * have. 44 | * 45 | * TODO: Note that the deployed version of this contract on the mainnet is slightly different! 46 | * In particular, this version reverts where the deployed version throws, to make newer compilers 47 | * stop warning whenever you build the Macroverse project. 48 | */ 49 | contract MacroverseStarRegistry is Ownable, HasNoEther, HasNoContracts { 50 | using SafeMath for *; 51 | 52 | // This is the token in which star ownership deposits have to be paid. 53 | MRVToken public tokenAddress; 54 | // This is the minimum ownership deposit in atomic token units. 55 | uint public minDepositInAtomicUnits; 56 | 57 | // This maps from star or other body seed to the address that owns it. 58 | // This can be queried without meeting the access control requirements. 59 | mapping(bytes32 => address) public ownerOf; 60 | 61 | // This holds what deposit was paid for each owned item. 62 | mapping(bytes32 => uint) public depositFor; 63 | 64 | // This tracks how much MRV the contract is supposed to have. 65 | // If it ends up with extra (because someone incorrectly used transfer() instead of approve()), the owner can remove it. 66 | uint public expectedMrvBalance; 67 | 68 | // This event is fired when ownership of a star system. Giving up ownership transfers to the 0 address. 69 | event StarOwnershipChange(bytes32 indexed starSeed, address indexed newOwner); 70 | 71 | /** 72 | * Deploy a new copy of the Macroverse Star Registry. 73 | * The given token will be used to pay deposits, and the given minimum 74 | * deposit size will be required. 75 | */ 76 | constructor(address payable depositTokenAddress, uint initialMinDepositInAtomicUnits) public { 77 | // We can only use one token for the lifetime of the contract. 78 | tokenAddress = MRVToken(depositTokenAddress); 79 | // But the minimum deposit for new claims can change 80 | minDepositInAtomicUnits = initialMinDepositInAtomicUnits; 81 | } 82 | 83 | /** 84 | * Allow the owner to set the minimum deposit amount for granting new 85 | * ownership claims. 86 | */ 87 | function setMinimumDeposit(uint newMinimumDepositInAtomicUnits) external onlyOwner { 88 | minDepositInAtomicUnits = newMinimumDepositInAtomicUnits; 89 | } 90 | 91 | /** 92 | * Acquire ownership of an unclaimed star. 93 | * To claim a star, you need to put up a deposit of MRV. You need to call 94 | * approve() on the MRV token contract to allow this contract to debit the 95 | * requested deposit from your account. The deposit must be more than the 96 | * current minimum deposit. 97 | * 98 | * YOU and ONLY YOU are responsible for remembering the seeds of stars you 99 | * own, so you can get your deposits back when you are done with them. You 100 | * can't easily get a listing from this contract. 101 | */ 102 | function claimOwnership(bytes32 starSeed, uint depositInAtomicUnits) external { 103 | // You can't claim things that are already owned. 104 | if (ownerOf[starSeed] != address(0)) revert(); 105 | 106 | // You can claim things that don't exist, if you really want to. 107 | 108 | // You have to put up at least the minimum deposit 109 | if (depositInAtomicUnits < minDepositInAtomicUnits) revert(); 110 | 111 | // Go ahead and do the state changes 112 | ownerOf[starSeed] = msg.sender; 113 | depositFor[starSeed] = depositInAtomicUnits; 114 | expectedMrvBalance = expectedMrvBalance.add(depositInAtomicUnits); 115 | 116 | // Announce it 117 | emit StarOwnershipChange(starSeed, msg.sender); 118 | 119 | // After state changes, try to take the money 120 | tokenAddress.transferFrom(msg.sender, address(this), depositInAtomicUnits); 121 | // The MRV token will throw if transferFrom fails. 122 | } 123 | 124 | /** 125 | * Transfer ownership of a star from the sender to the given address. 126 | * You don't need to meet the access control requirements to get rid of 127 | * your owned stars, or to own stars. But you might not be able to 128 | * query anything about them. 129 | */ 130 | function transferOwnership(bytes32 starSeed, address newOwner) external { 131 | // You can't send things you don't own. 132 | if (ownerOf[starSeed] != msg.sender) revert(); 133 | 134 | // Don't try to burn star ownership; use abdicateOwnership instead. 135 | if (newOwner == address(0)) revert(); 136 | // Don't send stars to the contract either 137 | if (newOwner == address(this)) revert(); 138 | // Or to the token 139 | if (newOwner == address(tokenAddress)) revert(); 140 | 141 | // Transfer owenership 142 | ownerOf[starSeed] = newOwner; 143 | 144 | // Announce it 145 | emit StarOwnershipChange(starSeed, newOwner); 146 | } 147 | 148 | // In a future version, we might want an ERC20-style authorization system, to let a contract move your things for you. 149 | 150 | /** 151 | * Give up ownership of an owned star. 152 | * The MRV deposit originally paid to acquire ownership of the star will 153 | * be paid out to the sender of the message. 154 | */ 155 | function abdicateOwnership(bytes32 starSeed) external { 156 | // You can't give up things you don't own. 157 | if (ownerOf[starSeed] != msg.sender) revert(); 158 | 159 | // How much should we return? 160 | uint depositSize = depositFor[starSeed]; 161 | // And to whom? 162 | address oldOwner = ownerOf[starSeed]; 163 | 164 | // Clear ownership 165 | ownerOf[starSeed] = address(0); 166 | // Clear the deposit value 167 | depositFor[starSeed] = 0; 168 | 169 | // Update expected balance 170 | expectedMrvBalance = expectedMrvBalance.sub(depositSize); 171 | 172 | // Announce lack of ownership of the thing 173 | emit StarOwnershipChange(starSeed, address(0)); 174 | 175 | // Pay back deposit 176 | tokenAddress.transfer(oldOwner, depositSize); 177 | // We know MRVToken throws on a failed transfer 178 | } 179 | 180 | /** 181 | * Allow the owner to collect any non-MRV tokens, or any excess MRV, that ends up in this contract. 182 | */ 183 | function reclaimToken(address otherToken) external onlyOwner { 184 | IERC20 other = IERC20(otherToken); 185 | 186 | // We will send our whole balance 187 | uint excessBalance = other.balanceOf(address(this)); 188 | 189 | // Unless we're talking about the MRV token 190 | if (address(other) == address(tokenAddress)) { 191 | // In which case we send only any balance that we shouldn't have 192 | excessBalance = excessBalance.sub(expectedMrvBalance); 193 | } 194 | 195 | // Make the transfer. If it doesn't work, we can try again later. 196 | other.transfer(owner(), excessBalance); 197 | } 198 | } 199 | 200 | // SPDX-License-Identifier: UNLICENSED 201 | -------------------------------------------------------------------------------- /contracts/MacroverseSystemGenerator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./AccessControl.sol"; 4 | import "./ControlledAccess.sol"; 5 | 6 | import "./Macroverse.sol"; 7 | import "./MacroverseSystemGeneratorPart1.sol"; 8 | import "./MacroverseSystemGeneratorPart2.sol"; 9 | 10 | /** 11 | * Represents a Macroverse generator for planetary systems around stars and 12 | * other stellar objects. 13 | * 14 | * Because of contract size limitations, some code in this contract is shared 15 | * between planets and moons, while some code is planet-specific. Moon-specific 16 | * code lives in the MacroverseMoonGenerator. 17 | * 18 | * Permission to call methods on this contract is regulated by a configurable 19 | * AccessControl contract. One such set of terms might be to require that the 20 | * account initiating a transaction have a certain minimum MRV token balance. 21 | * 22 | * The owner of this contract reserves the right to supersede it with a new 23 | * version, and to modify the terms for accessing this contract, at any time, 24 | * for any reason, and without notice. This includes the right to indefinitely 25 | * or permanently suspend or terminate access to this contract for any person, 26 | * account, or other contract, or for all persons, accounts, or other 27 | * contracts. The owner also reserves the right to not do any of the above. 28 | */ 29 | contract MacroverseSystemGenerator is ControlledAccess { 30 | 31 | 32 | /** 33 | * Deploy a new copy of the MacroverseSystemGenerator. 34 | */ 35 | constructor(address accessControlAddress) ControlledAccess(accessControlAddress) public { 36 | // Nothing to do! 37 | } 38 | 39 | /** 40 | * Get the seed for a planet or moon from the seed for its parent (star or planet) and its child number. 41 | */ 42 | function getWorldSeed(bytes32 parentSeed, uint16 childNumber) public view onlyControlledAccess returns (bytes32) { 43 | return MacroverseSystemGeneratorPart1.getWorldSeed(parentSeed, childNumber); 44 | } 45 | 46 | /** 47 | * Decide what kind of planet a given planet is. 48 | * It depends on its place in the order. 49 | * Takes the *planet*'s seed, its number, and the total planets in the system. 50 | */ 51 | function getPlanetClass(bytes32 seed, uint16 planetNumber, uint16 totalPlanets) public view onlyControlledAccess returns (Macroverse.WorldClass) { 52 | return MacroverseSystemGeneratorPart1.getPlanetClass(seed, planetNumber, totalPlanets); 53 | } 54 | 55 | /** 56 | * Decide what the mass of the planet or moon is. We can't do even the mass of 57 | * Jupiter in the ~88 bits we have in a real (should we have used int256 as 58 | * the backing type?) so we work in Earth masses. 59 | * 60 | * Also produces the masses for moons. 61 | */ 62 | function getWorldMass(bytes32 seed, Macroverse.WorldClass class) public view onlyControlledAccess returns (int128) { 63 | return MacroverseSystemGeneratorPart1.getWorldMass(seed, class); 64 | } 65 | 66 | // Define the orbit shape 67 | 68 | /** 69 | * Given the parent star's habitable zone bounds, the planet seed, the planet class 70 | * to be generated, and the "clearance" radius around the previous planet 71 | * in meters, produces orbit statistics (periapsis, apoapsis, and 72 | * clearance) in meters. 73 | * 74 | * The first planet uses a previous clearance of 0. 75 | * 76 | * TODO: realOuterRadius from the habitable zone never gets used. We should remove it. 77 | */ 78 | function getPlanetOrbitDimensions(int128 realInnerRadius, int128 realOuterRadius, bytes32 seed, Macroverse.WorldClass class, int128 realPrevClearance) 79 | public view onlyControlledAccess returns (int128 realPeriapsis, int128 realApoapsis, int128 realClearance) { 80 | 81 | return MacroverseSystemGeneratorPart1.getPlanetOrbitDimensions(realInnerRadius, realOuterRadius, seed, class, realPrevClearance); 82 | } 83 | 84 | /** 85 | * Convert from periapsis and apoapsis to semimajor axis and eccentricity. 86 | */ 87 | function convertOrbitShape(int128 realPeriapsis, int128 realApoapsis) public view onlyControlledAccess returns (int128 realSemimajor, int128 realEccentricity) { 88 | return MacroverseSystemGeneratorPart2.convertOrbitShape(realPeriapsis, realApoapsis); 89 | } 90 | 91 | // Define the orbital plane 92 | 93 | /** 94 | * Get the longitude of the ascending node for a planet or moon. For 95 | * planets, this is the angle from system +X to ascending node. For 96 | * moons, we use system +X transformed into the planet's equatorial plane 97 | * by the equatorial plane/rotation axis angles. 98 | */ 99 | function getWorldLan(bytes32 seed) public view onlyControlledAccess returns (int128) { 100 | return MacroverseSystemGeneratorPart2.getWorldLan(seed); 101 | } 102 | 103 | /** 104 | * Get the inclination (angle from system XZ plane to orbital plane at the ascending node) for a planet. 105 | * For a moon, this is done in the moon generator instead. 106 | * Inclination is always positive. If it were negative, the ascending node would really be the descending node. 107 | * Result is a real in radians. 108 | */ 109 | function getPlanetInclination(bytes32 seed, Macroverse.WorldClass class) public view onlyControlledAccess returns (int128) { 110 | return MacroverseSystemGeneratorPart2.getPlanetInclination(seed, class); 111 | } 112 | 113 | // Define the orbit's embedding in the plane (and in time) 114 | 115 | /** 116 | * Get the argument of periapsis (angle from ascending node to periapsis position, in the orbital plane) for a planet or moon. 117 | */ 118 | function getWorldAop(bytes32 seed) public view onlyControlledAccess returns (int128) { 119 | return MacroverseSystemGeneratorPart2.getWorldAop(seed); 120 | } 121 | 122 | /** 123 | * Get the mean anomaly (which sweeps from 0 at periapsis to 2 pi at the next periapsis) at epoch (time 0) for a planet or moon. 124 | */ 125 | function getWorldMeanAnomalyAtEpoch(bytes32 seed) public view onlyControlledAccess returns (int128) { 126 | return MacroverseSystemGeneratorPart2.getWorldMeanAnomalyAtEpoch(seed); 127 | } 128 | 129 | /** 130 | * Determine if the world is tidally locked, given its seed and its number 131 | * out from the parent, starting with 0. 132 | * Overrides getWorldZXAxisAngles and getWorldSpinRate. 133 | * Not used for asteroid belts or rings. 134 | */ 135 | function isTidallyLocked(bytes32 seed, uint16 worldNumber) public view onlyControlledAccess returns (bool) { 136 | return MacroverseSystemGeneratorPart2.isTidallyLocked(seed, worldNumber); 137 | } 138 | 139 | /** 140 | * Get the Y and X axis angles for a world, in radians. 141 | * The world's rotation axis starts straight up in its orbital plane. 142 | * Then the planet is rotated in Y, around the axis by the Y angle. 143 | * Then it is rotated forward (what would be toward the viewer) in the 144 | * world's transformed X by the X axis angle. 145 | * Both angles are in radians. 146 | * The X angle is never negative, because the Y angle would just be the opposite direction. 147 | * It is also never greater than Pi, because otherwise we would just measure around the other way. 148 | * Not used for asteroid belts or rings. 149 | * For a tidally locked world, ignore these values and use 0 for both angles. 150 | */ 151 | function getWorldYXAxisAngles(bytes32 seed) public view onlyControlledAccess returns (int128 realYRadians, int128 realXRadians) { 152 | return MacroverseSystemGeneratorPart2.getWorldYXAxisAngles(seed); 153 | } 154 | 155 | /** 156 | * Get the spin rate of the world in radians per Julian year around its axis. 157 | * For a tidally locked world, ignore this value and use the mean angular 158 | * motion computed by the OrbitalMechanics contract, given the orbit 159 | * details. 160 | * Not used for asteroid belts or rings. 161 | */ 162 | function getWorldSpinRate(bytes32 seed) public view onlyControlledAccess returns (int128) { 163 | return MacroverseSystemGeneratorPart2.getWorldSpinRate(seed); 164 | } 165 | 166 | } 167 | 168 | // SPDX-License-Identifier: UNLICENSED 169 | -------------------------------------------------------------------------------- /contracts/MacroverseSystemGeneratorPart2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RNG.sol"; 4 | import "./RealMath.sol"; 5 | 6 | import "./AccessControl.sol"; 7 | import "./ControlledAccess.sol"; 8 | 9 | import "./Macroverse.sol"; 10 | 11 | 12 | /** 13 | * Contains a portion of the MacroverseStstemGenerator implementation code. 14 | * The contract is split up due to contract size limitations. 15 | * We can't do access control here sadly. 16 | */ 17 | library MacroverseSystemGeneratorPart2 { 18 | using RNG for *; 19 | using RealMath for *; 20 | // No SafeMath or it might confuse RealMath 21 | 22 | /**@dev 23 | * It is useful to have Pi around. 24 | * We can't pull it in from the library. 25 | */ 26 | int128 constant REAL_PI = 3454217652358; 27 | 28 | /**@dev 29 | * Also perpare pi/2 30 | */ 31 | int128 constant REAL_HALF_PI = REAL_PI >> 1; 32 | 33 | /**@dev 34 | * How many fractional bits are there? 35 | */ 36 | int256 constant REAL_FBITS = 40; 37 | 38 | /**@dev 39 | * What's the first non-fractional bit 40 | */ 41 | int128 constant REAL_ONE = int128(1) << int128(REAL_FBITS); 42 | 43 | /**@dev 44 | * What's the last fractional bit? 45 | */ 46 | int128 constant REAL_HALF = REAL_ONE >> 1; 47 | 48 | /**@dev 49 | * What's two? Two is pretty useful. 50 | */ 51 | int128 constant REAL_TWO = REAL_ONE << int128(1); 52 | 53 | /**@dev 54 | * And zero 55 | */ 56 | int128 constant REAL_ZERO = 0; 57 | 58 | /** 59 | * Convert from periapsis and apoapsis to semimajor axis and eccentricity. 60 | */ 61 | function convertOrbitShape(int128 realPeriapsis, int128 realApoapsis) public pure returns (int128 realSemimajor, int128 realEccentricity) { 62 | // Semimajor axis is average of apoapsis and periapsis 63 | realSemimajor = RealMath.div(realApoapsis + realPeriapsis, RealMath.toReal(2)); 64 | 65 | // Eccentricity is ratio of difference and sum 66 | realEccentricity = RealMath.div(realApoapsis - realPeriapsis, realApoapsis + realPeriapsis); 67 | } 68 | 69 | // Define the orbital plane 70 | 71 | /** 72 | * Get the longitude of the ascending node for a planet or moon. For 73 | * planets, this is the angle from system +X to ascending node. For 74 | * moons, we use system +X transformed into the planet's equatorial plane 75 | * by the equatorial plane/rotation axis angles. 76 | */ 77 | function getWorldLan(bytes32 seed) public pure returns (int128) { 78 | RNG.RandNode memory node = RNG.RandNode(seed).derive("LAN"); 79 | // Angles should be uniform from 0 to 2 PI 80 | return node.getRealBetween(RealMath.toReal(0), RealMath.mul(RealMath.toReal(2), REAL_PI)); 81 | } 82 | 83 | /** 84 | * Get the inclination (angle from system XZ plane to orbital plane at the ascending node) for a planet. 85 | * For a moon, this is done in the moon generator instead. 86 | * Inclination is always positive. If it were negative, the ascending node would really be the descending node. 87 | * Result is a real in radians. 88 | */ 89 | function getPlanetInclination(bytes32 seed, Macroverse.WorldClass class) public pure returns (int128) { 90 | RNG.RandNode memory node = RNG.RandNode(seed).derive("inclination"); 91 | 92 | // Define minimum and maximum inclinations in milliradians 93 | // 175 milliradians = ~ 10 degrees 94 | int88 minimum; 95 | int88 maximum; 96 | if (class == Macroverse.WorldClass.Lunar || class == Macroverse.WorldClass.Europan) { 97 | minimum = 0; 98 | maximum = 175; 99 | } else if (class == Macroverse.WorldClass.Terrestrial || class == Macroverse.WorldClass.Panthalassic) { 100 | minimum = 0; 101 | maximum = 87; 102 | } else if (class == Macroverse.WorldClass.Neptunian) { 103 | minimum = 0; 104 | maximum = 35; 105 | } else if (class == Macroverse.WorldClass.Jovian) { 106 | minimum = 0; 107 | maximum = 52; 108 | } else if (class == Macroverse.WorldClass.AsteroidBelt) { 109 | minimum = 0; 110 | maximum = 262; 111 | } else { 112 | // Not real! 113 | revert(); 114 | } 115 | 116 | // Decide if we should be retrograde (PI-ish inclination) 117 | int128 real_retrograde_offset = 0; 118 | if (node.derive("retrograde").d(1, 100, 0) < 3) { 119 | // This planet ought to move retrograde 120 | real_retrograde_offset = REAL_PI; 121 | } 122 | 123 | return real_retrograde_offset + RealMath.div(node.getRealBetween(RealMath.toReal(minimum), RealMath.toReal(maximum)), RealMath.toReal(1000)); 124 | } 125 | 126 | // Define the orbit's embedding in the plane (and in time) 127 | 128 | /** 129 | * Get the argument of periapsis (angle from ascending node to periapsis position, in the orbital plane) for a planet or moon. 130 | */ 131 | function getWorldAop(bytes32 seed) public pure returns (int128) { 132 | RNG.RandNode memory node = RNG.RandNode(seed).derive("AOP"); 133 | // Angles should be uniform from 0 to 2 PI. 134 | // We already made sure planets/moons wouldn't get too close together when laying out the orbits. 135 | return node.getRealBetween(RealMath.toReal(0), RealMath.mul(RealMath.toReal(2), REAL_PI)); 136 | } 137 | 138 | /** 139 | * Get the mean anomaly (which sweeps from 0 at periapsis to 2 pi at the next periapsis) at epoch (time 0) for a planet or moon. 140 | */ 141 | function getWorldMeanAnomalyAtEpoch(bytes32 seed) public pure returns (int128) { 142 | RNG.RandNode memory node = RNG.RandNode(seed).derive("MAE"); 143 | // Angles should be uniform from 0 to 2 PI. 144 | return node.getRealBetween(RealMath.toReal(0), RealMath.mul(RealMath.toReal(2), REAL_PI)); 145 | } 146 | 147 | /** 148 | * Determine if the world is tidally locked, given its seed and its number 149 | * out from the parent, starting with 0. 150 | * Overrides getWorldZXAxisAngles and getWorldSpinRate. 151 | * Not used for asteroid belts or rings. 152 | */ 153 | function isTidallyLocked(bytes32 seed, uint16 worldNumber) public pure returns (bool) { 154 | // Tidal lock should be common near the parent and less common further out. 155 | return RNG.RandNode(seed).derive("tidal_lock").getReal() < RealMath.fraction(1, int88(worldNumber + 1)); 156 | } 157 | 158 | /** 159 | * Get the Y and X axis angles for a world, in radians. 160 | * The world's rotation axis starts straight up in its orbital plane. 161 | * Then the planet is rotated in Y, around the axis by the Y angle. 162 | * Then it is rotated forward (what would be toward the pureer) in the 163 | * world's transformed X by the X axis angle. 164 | * Both angles are in radians. 165 | * The X angle is never negative, because the Y angle would just be the opposite direction. 166 | * It is also never greater than Pi, because otherwise we would just measure around the other way. 167 | * Not used for asteroid belts or rings. 168 | * For a tidally locked world, ignore these values and use 0 for both angles. 169 | */ 170 | function getWorldYXAxisAngles(bytes32 seed) public pure returns (int128 realYRadians, int128 realXRadians) { 171 | 172 | // The Y angle should be uniform over all angles. 173 | realYRadians = RNG.RandNode(seed).derive("axisy").getRealBetween(-REAL_PI, REAL_PI); 174 | 175 | // The X angle will be mostly small positive or negative, with some sideways and some near Pi/2 (meaning retrograde rotation) 176 | int16 tilt_die = RNG.RandNode(seed).derive("tilt").d(1, 6, 0); 177 | 178 | // Start with low tilt, right side up 179 | // Earth is like 0.38 radians overall 180 | int128 real_tilt_limit = REAL_HALF; 181 | if (tilt_die >= 5) { 182 | // Be high tilt 183 | real_tilt_limit = REAL_HALF_PI; 184 | } 185 | 186 | RNG.RandNode memory x_node = RNG.RandNode(seed).derive("axisx"); 187 | realXRadians = x_node.getRealBetween(0, real_tilt_limit); 188 | 189 | if (tilt_die == 4 || tilt_die == 5) { 190 | // Flip so the tilt we have is relative to upside-down 191 | realXRadians = REAL_PI - realXRadians; 192 | } 193 | 194 | // So we should have 1/2 low tilt prograde, 1/6 low tilt retrograde, 1/6 high tilt retrograde, and 1/6 high tilt prograde 195 | } 196 | 197 | /** 198 | * Get the spin rate of the world in radians per Julian year around its axis. 199 | * For a tidally locked world, ignore this value and use the mean angular 200 | * motion computed by the OrbitalMechanics contract, given the orbit 201 | * details. 202 | * Not used for asteroid belts or rings. 203 | */ 204 | function getWorldSpinRate(bytes32 seed) public pure returns (int128) { 205 | // Earth is something like 2k radians per Julian year. 206 | return RNG.RandNode(seed).derive("spin").getRealBetween(REAL_ZERO, RealMath.toReal(8000)); 207 | } 208 | 209 | } 210 | 211 | // SPDX-License-Identifier: UNLICENSED 212 | -------------------------------------------------------------------------------- /contracts/MacroverseTerrainGenerator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RNG.sol"; 4 | import "./RealMath.sol"; 5 | 6 | import "./AccessControl.sol"; 7 | import "./ControlledAccess.sol"; 8 | 9 | import "./Macroverse.sol"; 10 | import "./MacroverseNFTUtils.sol"; 11 | 12 | 13 | /** 14 | * Contains the terrain generation logic for Macroverse 15 | * 16 | * Permission to call methods on this contract is regulated by a configurable 17 | * AccessControl contract. One such set of terms might be to require that the 18 | * account initiating a transaction have a certain minimum MRV token balance. 19 | * 20 | * The owner of this contract reserves the right to supersede it with a new 21 | * version, and to modify the terms for accessing this contract, at any time, 22 | * for any reason, and without notice. This includes the right to indefinitely 23 | * or permanently suspend or terminate access to this contract for any person, 24 | * account, or other contract, or for all persons, accounts, or other 25 | * contracts. The owner also reserves the right to not do any of the above. 26 | */ 27 | contract MacroverseTerrainGenerator is ControlledAccess { 28 | using RNG for *; 29 | using MacroverseNFTUtils for *; 30 | using RealMath for *; 31 | 32 | /**@dev 33 | * How many fractional bits are there? 34 | */ 35 | int256 constant REAL_FBITS = 40; 36 | 37 | /**@dev 38 | * What's the first non-fractional bit 39 | */ 40 | int128 constant REAL_ONE = int128(1) << int128(REAL_FBITS); 41 | 42 | /**@dev 43 | * What's the last fractional bit? 44 | */ 45 | int128 constant REAL_HALF = REAL_ONE >> 1; 46 | 47 | // Define the packing format from the NFT utils (again) 48 | uint8 constant TOKEN_SECTOR_X_SHIFT = 5; 49 | uint8 constant TOKEN_SECTOR_X_BITS = 16; 50 | uint8 constant TOKEN_SECTOR_Y_SHIFT = TOKEN_SECTOR_X_SHIFT + TOKEN_SECTOR_X_BITS; 51 | uint8 constant TOKEN_SECTOR_Y_BITS = 16; 52 | uint8 constant TOKEN_SECTOR_Z_SHIFT = TOKEN_SECTOR_Y_SHIFT + TOKEN_SECTOR_Y_BITS; 53 | uint8 constant TOKEN_SECTOR_Z_BITS = 16; 54 | uint8 constant TOKEN_SYSTEM_SHIFT = TOKEN_SECTOR_Z_SHIFT + TOKEN_SECTOR_Z_BITS; 55 | uint8 constant TOKEN_SYSTEM_BITS = 16; 56 | uint8 constant TOKEN_PLANET_SHIFT = TOKEN_SYSTEM_SHIFT + TOKEN_SYSTEM_BITS; 57 | uint8 constant TOKEN_PLANET_BITS = 16; 58 | uint8 constant TOKEN_MOON_SHIFT = TOKEN_PLANET_SHIFT + TOKEN_PLANET_BITS; 59 | uint8 constant TOKEN_MOON_BITS = 16; 60 | uint8 constant TOKEN_TRIXEL_SHIFT = TOKEN_MOON_SHIFT + TOKEN_MOON_BITS; 61 | uint8 constant TOKEN_TRIXEL_EACH_BITS = 3; 62 | 63 | /** 64 | * Deploy a new copy of the MacroverseTerrainGenerator. 65 | */ 66 | constructor(address accessControlAddress) ControlledAccess(accessControlAddress) public { 67 | // Nothing to do! 68 | } 69 | 70 | /** 71 | * Get the height of the given trixel, as represented by a token. 72 | * Height is returned on the range -1 to 1. 73 | */ 74 | function getTrixelHeight(uint256 trixelToken, bytes32 worldSeed) external view onlyControlledAccess returns (int128) { 75 | uint totalDepth = trixelToken.getTokenTrixelCount(); 76 | RNG.RandNode memory node = RNG.RandNode(worldSeed).derive("terrain"); 77 | 78 | // Accumulate height offsets into here 79 | int128 realHeight = 0; 80 | 81 | // And how much by to scale every added height change by 82 | int128 realScale = REAL_ONE; 83 | 84 | // Now we use the token as scratch and shift off all the trixel child 85 | // indexes we care about. 86 | trixelToken = trixelToken >> TOKEN_TRIXEL_SHIFT; 87 | for (uint i = 0; i < totalDepth; i++) { 88 | // Derive the node for this child 89 | node = node.derive(trixelToken & 0x7); 90 | // Compute and add the height 91 | realHeight += (node.getReal() - REAL_HALF).mul(realScale); 92 | // Shift the next child into place 93 | trixelToken = trixelToken >> TOKEN_TRIXEL_EACH_BITS; 94 | // And reduce the scale of the next offset 95 | realScale = realScale >> 1; 96 | // We don't need any clamping because even if we have all +1/2 or 97 | // -1/2, the scaling by 1/2 at each step makes the infinite sum be 98 | // 1 or -1. 99 | } 100 | 101 | return realHeight; 102 | } 103 | 104 | } 105 | 106 | // SPDX-License-Identifier: UNLICENSED 107 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | 25 | // SPDX-License-Identifier: UNLICENSED 26 | -------------------------------------------------------------------------------- /contracts/MinimumBalanceAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 4 | 5 | import "./AccessControl.sol"; 6 | 7 | 8 | /** 9 | * Represents an access control strategy where any end user (origin) with a minimum balance in an ERC20 token is allowed. 10 | */ 11 | contract MinimumBalanceAccessControl { 12 | IERC20 tokenAddress; 13 | uint minBalanceInAtomicUnits; 14 | 15 | /** 16 | * Make a new MinimumBalanceAccessControl that requires the specified minimum balance of the specified token. 17 | */ 18 | constructor(address tokenAddress_, uint minBalanceInAtomicUnits_) public { 19 | tokenAddress = IERC20(tokenAddress_); 20 | minBalanceInAtomicUnits = minBalanceInAtomicUnits_; 21 | } 22 | 23 | /** 24 | * Allow all queries resulting from a transaction initiated from an origin address with at least the required minimum balance. 25 | * This means that any contract you use can make queries on your behalf, but that no contract with the minimum balance can proxy 26 | * queries for others. 27 | */ 28 | function allowQuery(address /* sender */, address origin) public view returns (bool) { 29 | if (tokenAddress.balanceOf(origin) >= minBalanceInAtomicUnits) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | } 35 | 36 | // SPDX-License-Identifier: UNLICENSED 37 | -------------------------------------------------------------------------------- /contracts/OZ1BasicToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | 4 | import './OZ1ERC20Basic.sol'; 5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 6 | 7 | 8 | /** 9 | * @title Old OpenZeppelin 1 BasicToken as used in mainnet Macroverse deployment 10 | */ 11 | contract OZ1BasicToken is OZ1ERC20Basic { 12 | using SafeMath for uint256; 13 | 14 | mapping(address => uint256) balances; 15 | 16 | /** 17 | * @dev Fix for the ERC20 short address attack. 18 | */ 19 | modifier onlyPayloadSize(uint256 size) { 20 | if(msg.data.length < size + 4) { 21 | revert(); 22 | } 23 | _; 24 | } 25 | 26 | /** 27 | * @dev transfer token for a specified address 28 | * @param _to The address to transfer to. 29 | * @param _value The amount to be transferred. 30 | */ 31 | function transfer(address _to, uint256 _value) public override onlyPayloadSize(2 * 32) { 32 | balances[msg.sender] = balances[msg.sender].sub(_value); 33 | balances[_to] = balances[_to].add(_value); 34 | Transfer(msg.sender, _to, _value); 35 | } 36 | 37 | /** 38 | * @dev Gets the balance of the specified address. 39 | * @param _owner The address to query the the balance of. 40 | * return An uint256 representing the amount owned by the passed address. 41 | */ 42 | function balanceOf(address _owner) public override view returns (uint256 balance) { 43 | return balances[_owner]; 44 | } 45 | 46 | } 47 | 48 | // SPDX-License-Identifier: MIT 49 | -------------------------------------------------------------------------------- /contracts/OZ1ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | 4 | import './OZ1ERC20Basic.sol'; 5 | 6 | 7 | /** 8 | * @title Old OpenZeppelin 1 ERC20 as used in mainnet Macroverse deployment 9 | */ 10 | abstract contract OZ1ERC20 is OZ1ERC20Basic { 11 | function allowance(address owner, address spender) public virtual view returns (uint256); 12 | function transferFrom(address from, address to, uint256 value) public virtual; 13 | function approve(address spender, uint256 value) public virtual; 14 | event Approval(address indexed owner, address indexed spender, uint256 value); 15 | } 16 | 17 | // SPDX-License-Identifier: MIT 18 | -------------------------------------------------------------------------------- /contracts/OZ1ERC20Basic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | 4 | /** 5 | * @title OZ1ERC20Basic 6 | * @dev Old OpenZeppelin 1 ERC20Basic as used in mainnet Macroverse deployment 7 | */ 8 | abstract contract OZ1ERC20Basic { 9 | uint256 public totalSupply; 10 | function balanceOf(address who) public virtual view returns (uint256); 11 | function transfer(address to, uint256 value) public virtual; 12 | event Transfer(address indexed from, address indexed to, uint256 value); 13 | } 14 | 15 | // SPDX-License-Identifier: MIT 16 | -------------------------------------------------------------------------------- /contracts/OZ1StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | 4 | import './OZ1BasicToken.sol'; 5 | import './OZ1ERC20.sol'; 6 | 7 | 8 | /** 9 | * @title Old OpenZeppelin 1 StandardToken as used in mainnet Macroverse deployment 10 | */ 11 | contract OZ1StandardToken is OZ1BasicToken, OZ1ERC20 { 12 | 13 | mapping (address => mapping (address => uint256)) allowed; 14 | 15 | 16 | /** 17 | * @dev Transfer tokens from one address to another 18 | * @param _from address The address which you want to send tokens from 19 | * @param _to address The address which you want to transfer to 20 | * @param _value uint256 the amout of tokens to be transfered 21 | */ 22 | function transferFrom(address _from, address _to, uint256 _value) public override onlyPayloadSize(3 * 32) { 23 | uint256 _allowance = allowed[_from][msg.sender]; 24 | 25 | // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met 26 | // if (_value > _allowance) throw; 27 | 28 | balances[_to] = balances[_to].add(_value); 29 | balances[_from] = balances[_from].sub(_value); 30 | allowed[_from][msg.sender] = _allowance.sub(_value); 31 | Transfer(_from, _to, _value); 32 | } 33 | 34 | /** 35 | * @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender. 36 | * @param _spender The address which will spend the funds. 37 | * @param _value The amount of tokens to be spent. 38 | */ 39 | function approve(address _spender, uint256 _value) public override { 40 | 41 | // To change the approve amount you first have to reduce the addresses` 42 | // allowance to zero by calling `approve(_spender, 0)` if it is not 43 | // already 0 to mitigate the race condition described here: 44 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 45 | if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) revert(); 46 | 47 | allowed[msg.sender][_spender] = _value; 48 | Approval(msg.sender, _spender, _value); 49 | } 50 | 51 | /** 52 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 53 | * @param _owner address The address which owns the funds. 54 | * @param _spender address The address which will spend the funds. 55 | * return A uint256 specifing the amount of tokens still avaible for the spender. 56 | */ 57 | function allowance(address _owner, address _spender) public override view returns (uint256 remaining) { 58 | return allowed[_owner][_spender]; 59 | } 60 | 61 | } 62 | 63 | // SPDX-License-Identifier: MIT 64 | -------------------------------------------------------------------------------- /contracts/OrbitalMechanics.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RealMath.sol"; 4 | 5 | /** 6 | * Provides methods for doing orbital mechanics calculations, based on 7 | * RealMath. 8 | * 9 | * DON'T use these functions for computing positions for things every frame; 10 | * the RealMath library isn't really accurate enough for that. Use these in 11 | * smart contract game logic, on a time scale of one or more blocks. 12 | */ 13 | contract OrbitalMechanics { 14 | using RealMath for *; 15 | 16 | /**@dev 17 | * We need the gravitational constant. Calculated by solving the mean 18 | * motion equation for Earth. We can be mostly precise here, because we 19 | * know the semimajor axis and year length (in Julian years) to a few 20 | * places. 21 | * 22 | * This is 132712875029098577920 m^3 s^-2 sols^-1 23 | */ 24 | int128 constant REAL_G_PER_SOL = 145919349250077040774785972305920; 25 | 26 | // TODO: We have to copy-paste constants from RealMath because Solidity doesn't expose them by import. 27 | 28 | /**@dev 29 | * It is also useful to have Pi around. 30 | * We can't pull it in from the library. 31 | */ 32 | int128 constant REAL_PI = 3454217652358; 33 | 34 | /**@dev 35 | * And two pi, which happens to be odd in its most accurate representation. 36 | */ 37 | int128 constant REAL_TWO_PI = 6908435304715; 38 | 39 | /**@dev 40 | * How many fractional bits are there? 41 | */ 42 | int256 constant REAL_FBITS = 40; 43 | 44 | /**@dev 45 | * What's the first non-fractional bit 46 | */ 47 | int128 constant REAL_ONE = int128(1) << int128(REAL_FBITS); 48 | 49 | /**@dev 50 | * What's the last fractional bit? 51 | */ 52 | int128 constant REAL_HALF = REAL_ONE >> int128(1); 53 | 54 | /**@dev 55 | * We need 2 for constants in numerical methods. 56 | */ 57 | int128 constant REAL_TWO = REAL_ONE * 2; 58 | 59 | /**@dev 60 | * We need 3 for constants in numerical methods. 61 | */ 62 | int128 constant REAL_THREE = REAL_ONE * 3; 63 | 64 | /**@dev 65 | * A "year" is 365.25 days. We use Julian years. 66 | */ 67 | int128 constant REAL_SECONDS_PER_YEAR = 34697948144703898000; 68 | 69 | 70 | 71 | // Functions for orbital mechanics. Maybe should be a library? 72 | // Are NOT controlled access, since they don't talk to the RNG. 73 | // Please don't do these in Solidity unless you have to; you can do orbital mechanics in JS just fine with actual floats. 74 | // The steps to compute an orbit are: 75 | // 76 | // 1. Compute the semimajor axis as (apoapsis + periapsis) / 2 (do this yourself) 77 | // 2. Compute the mean angular motion, n = sqrt(central mass * gravitational constant / semimajor axis^3) 78 | // 3. Compute the Mean Anomaly, as n * time since epoch + MA at epoch, and wrap to an angle 0 to 2 pi 79 | // 4. Compute the Eccentric Anomaly numerically to solve MA = EA - eccentricity * sin(EA) 80 | // 5. Compute the True Anomaly as 2 * atan2(sqrt(1 - eccentricity) * cos(EA / 2), sqrt(1 + eccentricity) * sin(EA / 2)) 81 | // 6. Compute the current radius as r = semimajor * (1 - eccentricity^2) / (1 + eccentricity * cos(TA)) 82 | // 7. Compute Cartesian X (toward longitude 0) = radius * (cos(LAN) * cos(AOP + TA) - sin(LAN) * sin(AOP + TA) * cos(inclination)) 83 | // 8. Compute Cartesian Y (in plane) = radius * (sin(LAN) * cos(AOP + TA) + cos(LAN) * sin(AOP + TA) * cos(inclination)) 84 | // 9. Compute Cartesian Z (above plane) = radius * sin(inclination) * sin(AOP + TA) 85 | 86 | 87 | /** 88 | * Compute the mean angular motion, in radians per Julian year (365.25 89 | * days), given a star mass in sols and a semimajor axis in meters. 90 | */ 91 | function computeMeanAngularMotion(int128 real_central_mass_in_sols, int128 real_semimajor_axis) public pure returns (int128) { 92 | // REAL_G_PER_SOL is big, but nothing masses more than 100s of sols, so we can do the multiply. 93 | // But the semimajor axis in meters may be very big so we can't really do the cube for the denominator. 94 | // And since values in radians per second are tiny, their squares are even tinier and probably out of range. 95 | // So we scale up to radians per year 96 | return real_central_mass_in_sols.mul(REAL_G_PER_SOL) 97 | .div(real_semimajor_axis) 98 | .mul(REAL_SECONDS_PER_YEAR) 99 | .div(real_semimajor_axis) 100 | .mul(REAL_SECONDS_PER_YEAR).div(real_semimajor_axis).sqrt(); 101 | } 102 | 103 | /** 104 | * Compute the mean anomaly, from 0 to 2 PI, given the mean anomaly at 105 | * epoch, mean angular motion (in radians per Julian year) and the time (in 106 | * Julian years) since epoch. 107 | */ 108 | function computeMeanAnomaly(int128 real_mean_anomaly_at_epoch, int128 real_mean_angular_motion, int128 real_years_since_epoch) public pure returns (int128) { 109 | return (real_mean_anomaly_at_epoch + real_mean_angular_motion.mul(real_years_since_epoch)) % REAL_TWO_PI; 110 | } 111 | 112 | /** 113 | * Compute the eccentric anomaly, given the mean anomaly and eccentricity. 114 | * Uses numerical methods to solve MA = EA - eccentricity * sin(EA). Limit to a certain iteration count. 115 | */ 116 | function computeEccentricAnomalyLimited(int128 real_mean_anomaly, int128 real_eccentricity, int88 max_iterations) public pure returns (int128) { 117 | // We are going to find the root of EA - eccentricity * sin(EA) - MA, in EA. 118 | // We use Newton's Method. 119 | // f(EA) = EA - eccentricity * sin(EA) - MA 120 | // f'(EA) = 1 - eccentricity * cos(EA) 121 | // x_n = x_n-1 - f(x_n) / f'(x_n) 122 | 123 | // Start with the 3rd-order approximation from http://alpheratz.net/dynamics/twobody/KeplerIterations_summary.pdf 124 | // "A Practical Method for Solving the Kepler Equation" 125 | int128 e2 = real_eccentricity.mul(real_eccentricity); 126 | int128 e3 = e2.mul(real_eccentricity); 127 | int128 cosMA = real_mean_anomaly.cos(); 128 | int128 real_guess = real_mean_anomaly + ((-REAL_HALF).mul(e3) + real_eccentricity + 129 | (e2 + cosMA.mul(e3).mul(REAL_THREE).div(REAL_TWO)).mul(cosMA)).mul(real_mean_anomaly.sin()); 130 | 131 | for (int88 i = 0; i < max_iterations; i++) { 132 | int128 real_value = real_guess - real_eccentricity.mul(real_guess.sin()) - real_mean_anomaly; 133 | 134 | if (real_value.abs() <= 5) { 135 | // We found the root within epsilon. 136 | // Note that we are implicitly turning this random small number into a tiny real. 137 | break; 138 | } 139 | 140 | // Otherwise we update 141 | int128 real_derivative = REAL_ONE - real_eccentricity.mul(real_guess.cos()); 142 | // The derivative can never be 0. If it were, since cos() is <= 1, the eccentricity would be 1. 143 | // Eccentricity must be <= 1 144 | assert(real_derivative != 0); 145 | real_guess = real_guess - real_value.div(real_derivative); 146 | } 147 | 148 | // Bound to 0 to 2 pi angle range. 149 | return real_guess % REAL_TWO_PI; 150 | } 151 | 152 | /** 153 | * Compute the eccentric anomaly, given the mean anomaly and eccentricity. 154 | * Uses numerical methods to solve MA = EA - eccentricity * sin(EA). Internally limited to a reasonable iteration count. 155 | */ 156 | function computeEccentricAnomaly(int128 real_mean_anomaly, int128 real_eccentricity) public pure returns (int128) { 157 | return computeEccentricAnomalyLimited(real_mean_anomaly, real_eccentricity, 10); 158 | } 159 | 160 | /** 161 | * Compute the true anomaly from the eccentric anomaly and the eccentricity. 162 | */ 163 | function computeTrueAnomaly(int128 real_eccentric_anomaly, int128 real_eccentricity) public pure returns (int128) { 164 | int128 real_half_ea = real_eccentric_anomaly.div(REAL_TWO); 165 | return RealMath.atan2((REAL_ONE - real_eccentricity).sqrt().mul(real_half_ea.cos()), (REAL_ONE + real_eccentricity).sqrt().mul(real_half_ea.sin())).mul(REAL_TWO); 166 | } 167 | 168 | /** 169 | * Compute the current orbit radius (distance from parent body) from the true anomaly, semimajor axis, and eccentricity. 170 | * Operates on distances in meters. 171 | */ 172 | function computeRadius(int128 real_true_anomaly, int128 real_semimajor_axis, int128 real_eccentricity) public pure returns (int128) { 173 | return real_semimajor_axis.mul(REAL_ONE - real_eccentricity.mul(real_eccentricity)).div(REAL_ONE + real_eccentricity.mul(real_true_anomaly.cos())); 174 | } 175 | 176 | /** 177 | * Compute Cartesian X, Y, and Z offset from center of parent body to orbiting body. 178 | * Operates on distances in meters. 179 | * X is toward longitude 0 of the parent body. 180 | * Y is the other in-plane direction. 181 | * Z is up out of the plane. 182 | */ 183 | function computeCartesianOffset(int128 real_radius, int128 real_true_anomaly, int128 real_lan, int128 real_inclination, 184 | int128 real_aop) public pure returns (int128 real_x, int128 real_y, int128 real_z) { 185 | 186 | // Compute some intermediates 187 | int128 cos_lan = real_lan.cos(); 188 | int128 sin_lan = real_lan.sin(); 189 | int128 aop_ta_cos = (real_aop + real_true_anomaly).cos(); 190 | int128 aop_ta_sin = (real_aop + real_true_anomaly).sin(); 191 | int128 inclination_cos = real_inclination.cos(); 192 | int128 inclination_sin = real_inclination.sin(); 193 | 194 | // Compute the actual coordinates 195 | real_x = real_radius.mul(cos_lan.mul(aop_ta_cos) - sin_lan.mul(aop_ta_sin).mul(inclination_cos)); 196 | real_y = real_radius.mul(sin_lan.mul(aop_ta_cos) + cos_lan.mul(aop_ta_sin).mul(inclination_cos)); 197 | real_z = real_radius.mul(inclination_sin).mul(aop_ta_sin); 198 | 199 | } 200 | } 201 | 202 | // SPDX-License-Identifier: UNLICENSED 203 | -------------------------------------------------------------------------------- /contracts/RNG.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./RealMath.sol"; 4 | 5 | library RNG { 6 | using RealMath for *; 7 | 8 | /** 9 | * We are going to define a RandNode struct to allow for hash chaining. 10 | * You can extend a RandNode with a bunch of different stuff and get a new RandNode. 11 | * You can then use a RandNode to get a single, repeatable random value. 12 | * This eliminates the need for concatenating string selfs, which is a huge pain in Solidity. 13 | */ 14 | struct RandNode { 15 | // We hash this together with whatever we're mixing in to get the child hash. 16 | bytes32 _hash; 17 | } 18 | 19 | // All the functions that touch RandNodes need to be internal. 20 | // If you want to pass them in and out of contracts just use the bytes32. 21 | 22 | // You can get all these functions as methods on RandNodes by "using RNG for *" in your library/contract. 23 | 24 | /** 25 | * Mix string data into a RandNode. Returns a new RandNode. 26 | */ 27 | function derive(RandNode memory self, string memory entropy) internal pure returns (RandNode memory) { 28 | // Hash what's there now with the new stuff. 29 | return RandNode(sha256(abi.encodePacked(self._hash, entropy))); 30 | } 31 | 32 | /** 33 | * Mix signed int data into a RandNode. Returns a new RandNode. 34 | */ 35 | function derive(RandNode memory self, int256 entropy) internal pure returns (RandNode memory) { 36 | return RandNode(sha256(abi.encodePacked(self._hash, entropy))); 37 | } 38 | 39 | /** 40 | * Mix unsigned int data into a RandNode. Returns a new RandNode. 41 | */ 42 | function derive(RandNode memory self, uint256 entropy) internal pure returns (RandNode memory) { 43 | return RandNode(sha256(abi.encodePacked(self._hash, entropy))); 44 | } 45 | 46 | /** 47 | * Returns the base RNG hash for the given RandNode. 48 | * Does another round of hashing in case you made a RandNode("Stuff"). 49 | */ 50 | function getHash(RandNode memory self) internal pure returns (bytes32) { 51 | return sha256(abi.encodePacked(self._hash)); 52 | } 53 | 54 | /** 55 | * Return true or false with 50% probability. 56 | */ 57 | function getBool(RandNode memory self) internal pure returns (bool) { 58 | return uint256(getHash(self)) & 0x1 > 0; 59 | } 60 | 61 | /** 62 | * Get an int128 full of random bits. 63 | */ 64 | function getInt128(RandNode memory self) internal pure returns (int128) { 65 | // Just cast to int and truncate 66 | return int128(int256(getHash(self))); 67 | } 68 | 69 | /** 70 | * Get a real88x40 between 0 (inclusive) and 1 (exclusive). 71 | */ 72 | function getReal(RandNode memory self) internal pure returns (int128) { 73 | return getInt128(self).fpart(); 74 | } 75 | 76 | /** 77 | * Get an integer between low, inclusive, and high, exclusive. Represented as a normal int, not a real. 78 | */ 79 | function getIntBetween(RandNode memory self, int88 low, int88 high) internal pure returns (int88) { 80 | return RealMath.fromReal((getReal(self).mul(RealMath.toReal(high) - RealMath.toReal(low))) + RealMath.toReal(low)); 81 | } 82 | 83 | /** 84 | * Get a real between realLow (inclusive) and realHigh (exclusive). 85 | * Only actually has the bits of entropy from getReal, so some values will not occur. 86 | */ 87 | function getRealBetween(RandNode memory self, int128 realLow, int128 realHigh) internal pure returns (int128) { 88 | return getReal(self).mul(realHigh - realLow) + realLow; 89 | } 90 | 91 | /** 92 | * Roll a number of die of the given size, add/subtract a bonus, and return the result. 93 | * Max size is 100. 94 | */ 95 | function d(RandNode memory self, int8 count, int8 size, int8 bonus) internal pure returns (int16) { 96 | if (count == 1) { 97 | // Base case 98 | return int16(getIntBetween(self, 1, size)) + bonus; 99 | } else { 100 | // Loop and sum 101 | int16 sum = bonus; 102 | for(int8 i = 0; i < count; i++) { 103 | // Roll each die with no bonus 104 | sum += d(derive(self, i), 1, size, 0); 105 | } 106 | return sum; 107 | } 108 | } 109 | } 110 | 111 | // SPDX-License-Identifier: MIT 112 | 113 | -------------------------------------------------------------------------------- /contracts/TestnetMRVToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./MRVToken.sol"; 4 | 5 | /** 6 | * MRVToken contract which adds unrestricted minting by anyone. 7 | * 8 | * Only useful on a testnet. 9 | */ 10 | contract TestnetMRVToken is MRVToken { 11 | 12 | /** 13 | * Make a new TestnetMRVToken. 14 | * Passes through arguments to the base MRVToken constructor. 15 | */ 16 | constructor(address payable sendProceedsTo, address sendTokensTo) MRVToken(sendProceedsTo, sendTokensTo) public { 17 | // Send the event we forget to send in the base implementation. 18 | Transfer(address(0), sendTokensTo, totalSupply); 19 | } 20 | 21 | 22 | /** 23 | * Allow anyone to mint themselves any amount of tokens, for testing. 24 | * Unless it's truly huge and going to DoS the contract by pegging total supply. 25 | */ 26 | function mint(uint256 amount) public { 27 | if (amount > 0x0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { 28 | revert(); 29 | } 30 | totalSupply = totalSupply.add(amount); 31 | balances[msg.sender] = balances[msg.sender].add(amount); 32 | Transfer(address(0), msg.sender, amount); 33 | } 34 | 35 | } 36 | 37 | // SPDX-License-Identifier: UNLICENSED 38 | -------------------------------------------------------------------------------- /contracts/UnrestrictedAccessControl.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "./AccessControl.sol"; 4 | 5 | /** 6 | * Represents an access control strategy where all requests are accepted. 7 | */ 8 | contract UnrestrictedAccessControl { 9 | /** 10 | * Always approve access, ignoring the addresses passed in. 11 | * Note that this raises solidity compiler warnings about unused variables. 12 | */ 13 | function allowQuery(address /* sender */, address /* origin */) public pure returns (bool) { 14 | return true; 15 | } 16 | } 17 | 18 | // SPDX-License-Identifier: UNLICENSED 19 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | INFURA_PROJECT="xxxxx" 2 | KEYSTORE_DIR="/path/to/directory" 3 | KEYSTORE_NAME="xxxxx.json in /keystore under that directory" 4 | ETHERSCAN_API_KEY="xxxxx" 5 | -------------------------------------------------------------------------------- /migrations/10_deploy_existence_checker.js: -------------------------------------------------------------------------------- 1 | var MacroverseStarGenerator = artifacts.require("./MacroverseStarGenerator.sol") 2 | var MacroverseStarGeneratorPatch1 = artifacts.require("./MacroverseStarGeneratorPatch1.sol") 3 | var MacroverseSystemGenerator = artifacts.require("./MacroverseSystemGenerator.sol") 4 | var MacroverseMoonGenerator = artifacts.require("./MacroverseMoonGenerator.sol") 5 | var MacroverseNFTUtils = artifacts.require("./MacroverseNFTUtils.sol") 6 | var MacroverseExistenceChecker = artifacts.require("./MacroverseExistenceChecker.sol") 7 | 8 | module.exports = async function(deployer, network, accounts) { 9 | 10 | deployer.link(MacroverseNFTUtils, MacroverseExistenceChecker) 11 | 12 | // Deploy the existence checker. 13 | return deployer.deploy(MacroverseExistenceChecker, MacroverseStarGenerator.address, 14 | MacroverseStarGeneratorPatch1.address, MacroverseSystemGenerator.address, MacroverseMoonGenerator.address) 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/11_deactivate_old_registry.js: -------------------------------------------------------------------------------- 1 | var MacroverseStarRegistry = artifacts.require("./MacroverseStarRegistry.sol") 2 | 3 | // new Truffle doesn't give us free toWei 4 | const Web3Utils = require('web3-utils') 5 | 6 | module.exports = async function(deployer, network, accounts) { 7 | 8 | let instance = await MacroverseStarRegistry.deployed() 9 | 10 | // The all-1s uint256 didn't work here, so settle for a number that is merely 11 | // larger than the number of MRV actually in existence. 12 | return instance.setMinimumDeposit(Web3Utils.toWei("999999999999", "ether")) 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /migrations/12_deploy_universal_registry.js: -------------------------------------------------------------------------------- 1 | var MRVToken = artifacts.require("./MRVToken.sol") 2 | var TestnetMRVToken = artifacts.require("./TestnetMRVToken.sol") 3 | var MacroverseNFTUtils = artifacts.require("./MacroverseNFTUtils.sol") 4 | var MacroverseExistenceChecker = artifacts.require("./MacroverseExistenceChecker.sol") 5 | var MacroverseUniversalRegistry = artifacts.require("./MacroverseUniversalRegistry.sol") 6 | var MacroverseRealEstate = artifacts.require("./MacroverseRealEstate.sol") 7 | 8 | // new Truffle doesn't give us free toWei 9 | const Web3Utils = require('web3-utils') 10 | 11 | module.exports = async function(deployer, network, accounts) { 12 | 13 | deployer.link(MacroverseNFTUtils, MacroverseUniversalRegistry) 14 | 15 | // Determine the token to use for the registry 16 | // On testnet we use one that lets anyone mint tokens for free 17 | // This has to pick the same contract we deployed in 2_deploy_contracts.js, 18 | // so it should match the code there. 19 | let token_contract = (network.startsWith("rinkeby") ? TestnetMRVToken : MRVToken) 20 | 21 | // Deploy the registry, using the existence checker and the existing token 22 | // and a starting min star deposit, with a 5 minute commitment maturation time. 23 | // Buying out the whole network as of this writing costs ~$11.82 per block, 24 | // with ~15 seconds per block, so this gives us an attack cost of ~$236.40, 25 | // which is more than stealing someone's virtual real estate out from under 26 | // them ought to be worth, when you could have just claimed it before. Plus, 27 | // for extra security, a legit reveal can use a higher gas price, which an 28 | // attacker has to beat for several blocks. 29 | return deployer.deploy(MacroverseRealEstate, "api.macroverse.io").then(function() { 30 | return deployer.deploy(MacroverseUniversalRegistry, MacroverseRealEstate.address, 31 | MacroverseExistenceChecker.address, token_contract.address, 32 | Web3Utils.toWei("1000", "ether"), 5 * 60) 33 | }).then(function() { 34 | return MacroverseRealEstate.deployed() 35 | }).then(function(backend) { 36 | // Give the backend to the frontend 37 | return backend.transferOwnership(MacroverseUniversalRegistry.address) 38 | }) 39 | 40 | 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /migrations/13_reclaim_real_estate.js: -------------------------------------------------------------------------------- 1 | var MacroverseUniversalRegistry = artifacts.require("./MacroverseUniversalRegistry.sol") 2 | var MacroverseRealEstate = artifacts.require("./MacroverseRealEstate.sol") 3 | 4 | // new Truffle doesn't give us free toWei 5 | const Web3Utils = require('web3-utils') 6 | 7 | module.exports = async function(deployer, network, accounts) { 8 | 9 | let reg = await MacroverseUniversalRegistry.deployed() 10 | let real = await MacroverseRealEstate.deployed() 11 | 12 | // Take back the real estate token from the registry 13 | return reg.reclaimContract(real.address) 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/14_redeploy_universal_registry.js: -------------------------------------------------------------------------------- 1 | var MRVToken = artifacts.require("./MRVToken.sol") 2 | var TestnetMRVToken = artifacts.require("./TestnetMRVToken.sol") 3 | var MacroverseNFTUtils = artifacts.require("./MacroverseNFTUtils.sol") 4 | var MacroverseExistenceChecker = artifacts.require("./MacroverseExistenceChecker.sol") 5 | var MacroverseUniversalRegistry = artifacts.require("./MacroverseUniversalRegistry.sol") 6 | var MacroverseRealEstate = artifacts.require("./MacroverseRealEstate.sol") 7 | 8 | // new Truffle doesn't give us free toWei 9 | const Web3Utils = require('web3-utils') 10 | 11 | module.exports = async function(deployer, network, accounts) { 12 | 13 | deployer.link(MacroverseNFTUtils, MacroverseUniversalRegistry) 14 | 15 | // Determine the token to use for the registry 16 | // On testnet we use one that lets anyone mint tokens for free 17 | // This has to pick the same contract we deployed in 2_deploy_contracts.js, 18 | // so it should match the code there. 19 | let token_contract = (network.startsWith("rinkeby") ? TestnetMRVToken : MRVToken) 20 | 21 | // Use the existing real estate token 22 | let backend = await MacroverseRealEstate.deployed() 23 | 24 | // Deploy a new frontend 25 | return deployer.deploy(MacroverseUniversalRegistry, MacroverseRealEstate.address, 26 | MacroverseExistenceChecker.address, token_contract.address, 27 | Web3Utils.toWei("1000", "ether"), 5 * 60).then(function() { 28 | 29 | return MacroverseRealEstate.deployed() 30 | }).then(function(backend) { 31 | // Give the backend to the frontend 32 | return backend.transferOwnership(MacroverseUniversalRegistry.address) 33 | }) 34 | 35 | 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /migrations/15_deploy_terrain_generator.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var MacroverseTerrainGenerator = artifacts.require("./MacroverseTerrainGenerator.sol") 4 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 5 | 6 | module.exports = async function(deployer, network, accounts) { 7 | 8 | // Link 9 | deployer.link(RNG, MacroverseTerrainGenerator) 10 | deployer.link(RealMath, MacroverseTerrainGenerator) 11 | 12 | // And deploy the terrain generator, using the existing access control. 13 | await deployer.deploy(MacroverseTerrainGenerator, MinimumBalanceAccessControl.address) 14 | 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var MacroverseStarGenerator = artifacts.require("./MacroverseStarGenerator.sol") 4 | var MacroverseStarRegistry = artifacts.require("./MacroverseStarRegistry.sol") 5 | 6 | var MRVToken = artifacts.require("./MRVToken.sol") 7 | var TestnetMRVToken = artifacts.require("./TestnetMRVToken.sol") 8 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 9 | var UnrestrictedAccessControl = artifacts.require("./UnrestrictedAccessControl.sol") 10 | 11 | // new Truffle doesn't give us free toWei 12 | const Web3Utils = require('web3-utils') 13 | 14 | var LIVE_BENEFICIARY="0x2fe5bdc68d73b1f570b97422021a0c9cdccae79f" 15 | var LIVE_TOKEN_ACCOUNT="0x368651F6c2b3a7174ac30A5A062b65F2342Fb6F1" 16 | 17 | module.exports = async function(deployer, network, accounts) { 18 | deployer.deploy(RealMath) 19 | deployer.link(RealMath, RNG) 20 | deployer.deploy(RNG) 21 | deployer.link(RNG, MacroverseStarGenerator) 22 | deployer.link(RealMath, MacroverseStarGenerator) 23 | 24 | // Figure out where crowdsale proceeds belong 25 | let beneficiary = (network.startsWith("live") ? LIVE_BENEFICIARY : accounts[0]) 26 | // And the reserved tokens 27 | let tokenAccount = (network.startsWith("live") ? LIVE_TOKEN_ACCOUNT : accounts[0]) 28 | 29 | console.log("On network " + network + " and sending ETH to " + beneficiary + " and MRV to " + tokenAccount) 30 | 31 | deployer.deploy(UnrestrictedAccessControl) 32 | 33 | // Determine the token to use. 34 | // On testnet we want to launch one that lets anyone mint tokens for free 35 | // Make sure to check startsWith to match the -fork network Truffle dry runs the migrations against. 36 | // This has to pick the same contract we reference in 12_deploy_universal_registry.js, 37 | // so it should match the code there. 38 | // TODO: Tests get upset if they try and test MRVToken but TestnetMRVToken was deployed instead. 39 | let token_contract = (network.startsWith("rinkeby") ? TestnetMRVToken : MRVToken) 40 | 41 | // Deploy the token 42 | await deployer.deploy(token_contract, beneficiary, tokenAccount).then(function() { 43 | 44 | // Deploy a minimum balance access control strategy, with a 100 MRV minimum balance requirement. 45 | return deployer.deploy(MinimumBalanceAccessControl, token_contract.address, Web3Utils.toWei("100", "ether")).then(async function() { 46 | // Deploy the actual MG prototype and point it initially at that access control contract. 47 | // New Truffle no longer lets a string just become bytes32 if not hex. 48 | await deployer.deploy(MacroverseStarGenerator, "0x46696174426c6f636b73", MinimumBalanceAccessControl.address) 49 | // Deploy the star ownership registry, with a 1000 MRV minimum ownership deposit. 50 | await deployer.deploy(MacroverseStarRegistry, token_contract.address, Web3Utils.toWei("1000", "ether")) 51 | }) 52 | }) 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /migrations/3_replace_star_generator.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var MacroverseStarGenerator = artifacts.require("./MacroverseStarGenerator.sol") 4 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 5 | 6 | module.exports = async function(deployer, network, accounts) { 7 | 8 | // Link 9 | deployer.link(RNG, MacroverseStarGenerator) 10 | deployer.link(RealMath, MacroverseStarGenerator) 11 | 12 | // And deploy updated MacroverseStarGenerator with original parameters but new code. 13 | await deployer.deploy(MacroverseStarGenerator, "0x46696174426c6f636b73", MinimumBalanceAccessControl.address) 14 | 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/4_replace_real_math.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | 3 | module.exports = async function(deployer, network, accounts) { 4 | // Redeploy RealMath for Kepler Phase since it has been upgraded 5 | let oldAddress = RealMath.address 6 | await deployer.deploy(RealMath) 7 | if (RealMath.address == oldAddress) { 8 | throw new Error("Failed to replace RealMath!") 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /migrations/5_deploy_star_generator_patch.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var MacroverseStarGeneratorPatch1 = artifacts.require("./MacroverseStarGeneratorPatch1.sol") 4 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 5 | 6 | module.exports = async function(deployer, network, accounts) { 7 | 8 | // Link 9 | deployer.link(RNG, MacroverseStarGeneratorPatch1) 10 | deployer.link(RealMath, MacroverseStarGeneratorPatch1) 11 | 12 | // And deploy the star generator patch contract, using the existing access control. 13 | await deployer.deploy(MacroverseStarGeneratorPatch1, MinimumBalanceAccessControl.address) 14 | 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/6_deploy_system_generator.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var Macroverse = artifacts.require("./Macroverse.sol") 4 | var MacroverseSystemGeneratorPart1 = artifacts.require("./MacroverseSystemGeneratorPart1.sol") 5 | var MacroverseSystemGeneratorPart2 = artifacts.require("./MacroverseSystemGeneratorPart2.sol") 6 | var MacroverseSystemGenerator = artifacts.require("./MacroverseSystemGenerator.sol") 7 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 8 | 9 | module.exports = function(deployer, network, accounts) { 10 | return deployer.deploy(Macroverse).then(function() { 11 | // Link 12 | deployer.link(RNG, MacroverseSystemGeneratorPart1) 13 | deployer.link(RealMath, MacroverseSystemGeneratorPart1) 14 | 15 | deployer.link(RNG, MacroverseSystemGeneratorPart2) 16 | deployer.link(RealMath, MacroverseSystemGeneratorPart2) 17 | 18 | return deployer.deploy(MacroverseSystemGeneratorPart1) 19 | }).then(function() { 20 | return deployer.deploy(MacroverseSystemGeneratorPart2) 21 | }).then(function() { 22 | deployer.link(MacroverseSystemGeneratorPart1, MacroverseSystemGenerator) 23 | deployer.link(MacroverseSystemGeneratorPart2, MacroverseSystemGenerator) 24 | 25 | // And deploy the system generator, using the existing access control. 26 | return deployer.deploy(MacroverseSystemGenerator, MinimumBalanceAccessControl.address) 27 | }) 28 | }; 29 | -------------------------------------------------------------------------------- /migrations/7_deploy_orbital_mechanics.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var OrbitalMechanics = artifacts.require("./OrbitalMechanics.sol") 3 | 4 | module.exports = async function(deployer, network, accounts) { 5 | 6 | // Link 7 | deployer.link(RealMath, OrbitalMechanics) 8 | 9 | // And deploy the orbital mechanics code 10 | await deployer.deploy(OrbitalMechanics) 11 | 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/8_deploy_moon_generator.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("./RealMath.sol"); 2 | var RNG = artifacts.require("./RNG.sol"); 3 | var MacroverseMoonGenerator = artifacts.require("./MacroverseMoonGenerator.sol") 4 | var MinimumBalanceAccessControl = artifacts.require("./MinimumBalanceAccessControl.sol") 5 | 6 | module.exports = async function(deployer, network, accounts) { 7 | 8 | // Link 9 | deployer.link(RNG, MacroverseMoonGenerator) 10 | deployer.link(RealMath, MacroverseMoonGenerator) 11 | 12 | // And deploy the moon generator, using the existing access control. 13 | await deployer.deploy(MacroverseMoonGenerator, MinimumBalanceAccessControl.address) 14 | 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/9_deploy_nft_utils.js: -------------------------------------------------------------------------------- 1 | var MacroverseNFTUtils = artifacts.require("./MacroverseNFTUtils.sol") 2 | 3 | module.exports = async function(deployer, network, accounts) { 4 | 5 | return deployer.deploy(MacroverseNFTUtils) 6 | 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "macroverse", 3 | "version": "2.2.1", 4 | "description": "An entire universe on the Ethereum blockchain", 5 | "keywords": [ 6 | "macroverse", 7 | "Ethereum", 8 | "procedural", 9 | "generation" 10 | ], 11 | "homepage": "https://macroverse.io/", 12 | "bugs": { 13 | "url": "https://github.com/novakdistributed/macroverse/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/NovakDistributed/macroverse.git" 18 | }, 19 | "main": "src/index.js", 20 | "dependencies": { 21 | "@codechecks/client": "^0.1.10", 22 | "bn.js": "^5.1.1", 23 | "degrees-radians": "^1.0.3", 24 | "radians-degrees": "^1.0.0", 25 | "truffle-plugin-verify": "^0.3.11", 26 | "web3-utils": "^1.2.4" 27 | }, 28 | "license": "SEE LICENSE IN LICENSE", 29 | "scripts": { 30 | "prepack": "rm -f build/contracts/* && git checkout build/contracts" 31 | }, 32 | "devDependencies": { 33 | "dotenv": "^8.2.0", 34 | "eth-gas-reporter": "^0.2.17", 35 | "openzeppelin-solidity": "^3.0.2", 36 | "truffle-keystore-provider": "1.0.6" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/SizeMacroverseUniversalRegistry.js: -------------------------------------------------------------------------------- 1 | // This contract is at risk of being too big. 2 | // See https://ethereum.stackexchange.com/a/48568 for sizing code 3 | var MacroverseUniversalRegistry = artifacts.require('MacroverseUniversalRegistry') 4 | 5 | contract('MacroverseUniversalRegistry', function(accounts) { 6 | it("get the size of the contract", function() { 7 | return MacroverseUniversalRegistry.deployed().then(function(instance) { 8 | var bytecode = instance.constructor._json.bytecode 9 | var deployed = instance.constructor._json.deployedBytecode 10 | var sizeOfB = bytecode.length / 2 11 | var sizeOfD = deployed.length / 2 12 | console.log("size of bytecode in bytes = ", sizeOfB) 13 | console.log("size of deployed in bytes = ", sizeOfD) 14 | console.log("initialisation and constructor code in bytes = ", sizeOfB - sizeOfD) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/TestERC20Returns.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.10; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | 6 | // The real ERC20 standard has approve, transfer, and transferFrom returning bools, but the 7 | // OpenZeppelin implementation that was used for the MRV token just has them throw/not throw. 8 | // It's recommented for an ERC20 token to throw when it would return false, so we just have 9 | // to make sure that when it doesn't throw that's the same to callers expecting the 10 | // standard-compliant ABI as having returned true. 11 | 12 | abstract contract FakeERC20 { 13 | function totalSupply() virtual public view returns (uint totalSupplyOut); 14 | function balanceOf(address _owner) virtual public view returns (uint balance); 15 | function transfer(address _to, uint _value) virtual public; 16 | function transferFrom(address _from, address _to, uint _value) virtual public; 17 | function approve(address _spender, uint _value) virtual public; 18 | function allowance(address _owner, address _spender) virtual public view returns (uint remaining); 19 | event Transfer(address indexed _from, address indexed _to, uint _value); 20 | event Approval(address indexed _owner, address indexed _spender, uint _value); 21 | } 22 | 23 | abstract contract RealERC20 { 24 | function totalSupply() virtual public view returns (uint totalSupplyOut); 25 | function balanceOf(address _owner) virtual public view returns (uint balance); 26 | function transfer(address _to, uint _value) virtual public returns (bool success); 27 | function transferFrom(address _from, address _to, uint _value) virtual public returns (bool success); 28 | function approve(address _spender, uint _value) virtual public returns (bool success); 29 | function allowance(address _owner, address _spender) virtual public view returns (uint remaining); 30 | event Transfer(address indexed _from, address indexed _to, uint _value); 31 | event Approval(address indexed _owner, address indexed _spender, uint _value); 32 | } 33 | 34 | contract SuccessCoin is FakeERC20 { 35 | function totalSupply() override public view returns (uint totalSupplyOut) { 36 | return 99999; 37 | } 38 | function transfer(address to, uint value) override public { 39 | // Say it worked by not throwing 40 | } 41 | function transferFrom(address from, address to, uint value) override public { 42 | // Say it worked by not throwing 43 | } 44 | function approve(address spender, uint value) override public { 45 | // Say it worked by not throwing 46 | } 47 | function balanceOf(address /*who*/) override public view returns (uint) { 48 | return 99999; 49 | } 50 | function allowance(address /*owner*/, address /*spender*/) override public view returns (uint) { 51 | return 99999; 52 | } 53 | } 54 | 55 | contract TestERC20Returns { 56 | 57 | function testTransferSuccessReturnsTrue() public { 58 | SuccessCoin success = new SuccessCoin(); 59 | 60 | RealERC20 wrapped = RealERC20(address(success)); 61 | 62 | Assert.equal(wrapped.transfer(tx.origin, 100), true, "Not throwing is interpreted as returning true"); 63 | Assert.equal(wrapped.transfer(address(0), 0), true, "Not throwing is interpreted as returning true even when all arguments are zero"); 64 | } 65 | 66 | function testTransferFromSuccessReturnsTrue() public { 67 | SuccessCoin success = new SuccessCoin(); 68 | 69 | RealERC20 wrapped = RealERC20(address(success)); 70 | 71 | Assert.equal(wrapped.transferFrom(address(this), tx.origin, 100), true, "Not throwing is interpreted as returning true"); 72 | Assert.equal(wrapped.transferFrom(address(0), address(0), 0), true, "Not throwing is interpreted as returning true even when all arguments are zero"); 73 | } 74 | 75 | function testApproveSuccessReturnsTrue() public { 76 | SuccessCoin success = new SuccessCoin(); 77 | 78 | RealERC20 wrapped = RealERC20(address(success)); 79 | 80 | Assert.equal(wrapped.approve(tx.origin, 100), true, "Not throwing is interpreted as returning true"); 81 | Assert.equal(wrapped.approve(address(0), 0), true, "Not throwing is interpreted as returning true even when all arguments are zero"); 82 | } 83 | 84 | } 85 | 86 | // SPDX-License-Identifier: UNLICENSED 87 | 88 | -------------------------------------------------------------------------------- /test/TestMacroverseExistenceChecker.js: -------------------------------------------------------------------------------- 1 | let MacroverseExistenceChecker = artifacts.require("MacroverseExistenceChecker"); 2 | 3 | // Load the Macroverse module JavaScript 4 | let mv = require('../src') 5 | 6 | contract('MacroverseExistenceChecker', function(accounts) { 7 | it("should say that star 0 in sector 0,0,0 exists", async function() { 8 | let instance = await MacroverseExistenceChecker.deployed() 9 | 10 | assert.equal(await instance.systemExists.call(0, 0, 0, 0), true, "We get the right result checking manually") 11 | assert.equal(await instance.exists.call(mv.keypathToToken('0.0.0.0')), true, "We get the right result checking by token") 12 | 13 | }) 14 | 15 | it("should say that a series of locations used in other tests exist", async function() { 16 | let instance = await MacroverseExistenceChecker.deployed() 17 | 18 | for (let keypath of [ 19 | '0.0.0.0', 20 | '0.0.0.0.0.-1.7.2.2.2', 21 | '0.0.0.0.0.-1.7.2.2.2.0', 22 | '0.0.0.0.0.-1.7.2.2.2.1', 23 | '0.0.0.0.0.-1.7.2.2.2.2', 24 | '0.0.0.0.0.-1.7.2.2.2.3' 25 | ]) { 26 | 27 | let token = mv.keypathToToken(keypath) 28 | let keypath2 = mv.tokenToKeypath(token) 29 | assert.equal(keypath, keypath2, "We pack and unpack the keypath correctly") 30 | 31 | assert.equal(await instance.exists.call(token), true, "We get the right result for existence of " + keypath) 32 | } 33 | 34 | }) 35 | 36 | it("should work for this particular token value", async function() { 37 | let instance = await MacroverseExistenceChecker.deployed() 38 | 39 | let token = '2981514173051169324652112348446727' 40 | 41 | let keypath = mv.tokenToKeypath(token) 42 | assert.equal(keypath, '0.0.0.0.0.-1.7.2.2.2', "Token decodes to the correct keypath") 43 | 44 | assert.equal(await instance.exists.call(token), true, "We get the right result for existence of " + keypath) 45 | }) 46 | 47 | // TODO: we could check for rejecting queries with below the minimum balance, 48 | // but I can't seem to write a test that doesn't hang. 49 | 50 | it("should say that the corner sectors exist and the past-the-corner sectors don't", async function() { 51 | let instance = await MacroverseExistenceChecker.deployed() 52 | 53 | for (let corner of [ 54 | [10000, 10000, 10000], 55 | [10000, 10000, -10000], 56 | [10000, -10000, 10000], 57 | [10000, -10000, -10000], 58 | [-10000, 10000, 10000], 59 | [-10000, 10000, -10000], 60 | [-10000, -10000, 10000], 61 | [-10000, -10000, -10000]]) { 62 | 63 | 64 | { 65 | assert.equal(await instance.sectorExists.call(corner[0], corner[1], corner[2]), true, "We get the right result checking manually") 66 | 67 | let keypath = corner[0] + '.' + corner[1] + '.' + corner[2] 68 | let token = mv.keypathToToken(keypath) 69 | let keypath2 = mv.tokenToKeypath(token) 70 | assert.equal(keypath, keypath2, "We pack and unpack the keypath correctly") 71 | 72 | assert.equal(await instance.exists.call(mv.keypathToToken(corner[0] + '.' + corner[1] + '.' + corner[2])), true, "We get the right result checking by token") 73 | } 74 | 75 | for (let dim of [0, 1, 2]) { 76 | let outside = [corner[0], corner[1], corner[2]] 77 | 78 | outside[dim] = outside[dim] + outside[dim]/10000 79 | 80 | assert.equal(await instance.sectorExists.call(outside[0], outside[1], outside[2]), false, "We get the right result checking manually") 81 | 82 | let keypath = outside[0] + '.' + outside[1] + '.' + outside[2] 83 | let token = mv.keypathToToken(keypath) 84 | let keypath2 = mv.tokenToKeypath(token) 85 | assert.equal(keypath, keypath2, "We pack and unpack the keypath correctly") 86 | 87 | assert.equal(await instance.exists.call(token), false, "We get the right result checking by token") 88 | } 89 | 90 | } 91 | }) 92 | 93 | it("should say that a particulat plot of land we use in other tests exists", async function() { 94 | let instance = await MacroverseExistenceChecker.deployed() 95 | 96 | let keypath = '0.0.0.0.0.-1.7.2.2.2' 97 | let token = mv.keypathToToken(keypath) 98 | let keypath2 = mv.tokenToKeypath(token) 99 | assert.equal(keypath, keypath2, "We pack and unpack the keypath correctly") 100 | 101 | assert.equal(await instance.exists.call(token), true, "We get the right result checking by token") 102 | }) 103 | 104 | 105 | }) 106 | -------------------------------------------------------------------------------- /test/TestMacroverseMoonGenerator.js: -------------------------------------------------------------------------------- 1 | let MacroverseSystemGenerator = artifacts.require('MacroverseSystemGenerator') 2 | let MacroverseMoonGenerator = artifacts.require('MacroverseMoonGenerator') 3 | let UnrestrictedAccessControl = artifacts.require('UnrestrictedAccessControl') 4 | 5 | // Load the Macroverse module JavaScript 6 | let mv = require('../src') 7 | 8 | // Define the parameters of our test planet 9 | const TEST_SEED = '0x6e617461736861' 10 | const TEST_CLASS = mv.worldClass['Terrestrial'] 11 | 12 | contract('MacroverseMoonGenerator', function(accounts) { 13 | it("should initially reject queries", async function() { 14 | let instance = await MacroverseMoonGenerator.deployed() 15 | 16 | let failure_found = false 17 | 18 | await (instance.getPlanetMoonCount.call(TEST_SEED, TEST_CLASS, {from: accounts[1]}).catch(async function () { 19 | failure_found = true 20 | })) 21 | 22 | assert.equal(failure_found, true, "Unauthorized query should fail") 23 | }) 24 | 25 | it("should let us change access control to unrestricted", async function() { 26 | let instance = await MacroverseMoonGenerator.deployed() 27 | let unrestricted = await UnrestrictedAccessControl.deployed() 28 | await instance.changeAccessControl(unrestricted.address) 29 | 30 | assert.ok(true, "Access control can be changed without error") 31 | 32 | }) 33 | 34 | it("should then accept queries", async function() { 35 | let instance = await MacroverseMoonGenerator.deployed() 36 | 37 | let failure_found = false 38 | 39 | await (instance.getPlanetMoonCount.call(TEST_SEED, TEST_CLASS, {from: accounts[1]}).catch(async function () { 40 | failure_found = true 41 | })) 42 | 43 | assert.equal(failure_found, false, "Authorized query should succeed") 44 | }) 45 | 46 | it("should have a moon around our test planet", async function() { 47 | let instance = await MacroverseMoonGenerator.deployed() 48 | let count = (await instance.getPlanetMoonCount.call(TEST_SEED, TEST_CLASS)).toNumber() 49 | assert.equal(count, 1); 50 | 51 | }) 52 | 53 | it("should let us dump all the moons", async function() { 54 | let instance = await MacroverseMoonGenerator.deployed() 55 | 56 | // We also need the system generator for some moon properties 57 | let sysgen = await MacroverseSystemGenerator.deployed() 58 | 59 | // Parent mass is in Earth masses 60 | let parentRealMass = mv.toReal(1.0) 61 | 62 | let count = (await instance.getPlanetMoonCount.call(TEST_SEED, TEST_CLASS)).toNumber() 63 | 64 | // Get the moon scale which defines the basic size of the moon system. 65 | let realMoonScale = await instance.getPlanetMoonScale.call(TEST_SEED, parentRealMass) 66 | 67 | var prevClearance = mv.toReal(0) 68 | 69 | for (let i = 0; i < count; i++) { 70 | // Define the moon 71 | let moonSeed = await sysgen.getWorldSeed.call(TEST_SEED, i) 72 | let moonClassNum = (await instance.getMoonClass.call(TEST_CLASS, moonSeed, i)).toNumber() 73 | let realMass = await sysgen.getWorldMass.call(moonSeed, moonClassNum) 74 | let moonMass = mv.fromReal(realMass) 75 | 76 | // Define the orbit shape 77 | let orbitShape = await instance.getMoonOrbitDimensions.call(realMoonScale, 78 | moonSeed, moonClassNum, prevClearance) 79 | console.log('Periapsis: ', mv.fromReal(orbitShape.realPeriapsis)) 80 | console.log('Apoapsis: ', mv.fromReal(orbitShape.realApoapsis)) 81 | console.log('Clearance: ', mv.fromReal(orbitShape.realClearance)) 82 | let [realPeriapsis, realApoapsis, newClearance] = [orbitShape[0], orbitShape[1], orbitShape[2]] 83 | prevClearance = newClearance 84 | 85 | // Compute useful versions of this in lunar distances 86 | let moonPeriapsis = mv.fromReal(realPeriapsis) / mv.LD; 87 | let moonApoapsis = mv.fromReal(realApoapsis) / mv.LD; 88 | 89 | let converted = await sysgen.convertOrbitShape.call(realPeriapsis, realApoapsis) 90 | let [realSemimajor, realEccentricity] = [converted[0], converted[1]] 91 | let moonEccentricity = mv.fromReal(realEccentricity); 92 | 93 | // Define the orbital plane. Make sure to convert everything to degrees for display. 94 | let realLan = await sysgen.getWorldLan.call(moonSeed) 95 | let moonLan = mv.degrees(mv.fromReal(realLan)) 96 | // TODO: Inclination and LAN ought to group for moons, for a common orbital plane out of the elliptic 97 | let realInclination = await instance.getMoonInclination.call(moonSeed, moonClassNum) 98 | let moonInclination = mv.degrees(mv.fromReal(realInclination)) 99 | 100 | // Define the position in the orbital plane 101 | let realAop = await sysgen.getWorldAop.call(moonSeed) 102 | let moonAop = mv.degrees(mv.fromReal(realAop)) 103 | let realMeanAnomalyAtEpoch = await sysgen.getWorldMeanAnomalyAtEpoch.call(moonSeed) 104 | let moonMeanAnomalyAtEpoch = mv.degrees(mv.fromReal(realMeanAnomalyAtEpoch)) 105 | 106 | console.log('Moon ' + i + ': ' + mv.worldClasses[moonClassNum] + ' with mass ' + 107 | moonMass + ' Earths between ' + moonPeriapsis + ' and ' + moonApoapsis + ' LD') 108 | console.log('\tEccentricity: ' + moonEccentricity + ' LAN: ' + moonLan + '° Inclination: ' + moonInclination + '°') 109 | console.log('\tAOP: ' + moonAop + '° Mean Anomaly at Epoch: ' + moonMeanAnomalyAtEpoch + '°') 110 | } 111 | 112 | 113 | }) 114 | 115 | }) 116 | -------------------------------------------------------------------------------- /test/TestMacroverseRealEstate.js: -------------------------------------------------------------------------------- 1 | let MacroverseRealEstate = artifacts.require("MacroverseRealEstate") 2 | let MacroverseUniversalRegistry = artifacts.require("MacroverseUniversalRegistry") 3 | let MRVToken = artifacts.require("MRVToken") 4 | 5 | // Load the Macroverse module JavaScript 6 | let mv = require('../src') 7 | 8 | const BN = require('bn.js') 9 | const Web3Utils = require('web3-utils') 10 | 11 | async function assert_throws(promise, message) { 12 | try { 13 | await promise 14 | assert.ok(false, message) 15 | } catch { 16 | // OK 17 | } 18 | } 19 | 20 | async function getChainID() { 21 | var net = undefined; 22 | if (typeof web3.eth.getChainId !== 'undefined') { 23 | // We actually have the API that lets us query this 24 | net = await web3.eth.getChainId() 25 | } else if (typeof web3.version.getNetwork !== 'undefined') { 26 | // We have getNetwork, so guess based on that. 27 | // In many cases the chain ID and the network are the same. 28 | net = await web3.version.getNetwork() 29 | } else if (typeof web3.eth.net.getNetworkType !== 'undefined') { 30 | // Try and go form string to network ID 31 | let nettype = await web3.eth.net.getNetworkType() 32 | console.log('Network type: ', nettype) 33 | switch(nettype) { 34 | case 'main': 35 | net = 1 36 | break 37 | case 'rinkeby': 38 | net = 4 39 | break 40 | case 'private': 41 | // Truffle test claims to be chain 1... 42 | // TODO: What does ganache say/look like? 43 | net = 1 44 | break 45 | default: 46 | net = 12345 47 | break 48 | } 49 | } else { 50 | throw new Error('No way to inspect network') 51 | } 52 | 53 | if (net == 1337 || net.toString(10) == '1337') { 54 | // This Truffle test network really looks like 1 to the on-chain code. 55 | net = 1 56 | } 57 | return net 58 | } 59 | 60 | contract('MacroverseRealEstate', function(accounts) { 61 | it("should report the correct URL for a token that exists", async function() { 62 | let instance = await MacroverseRealEstate.deployed() 63 | let reg = await MacroverseUniversalRegistry.deployed() 64 | let mrv = await MRVToken.deployed() 65 | 66 | let chainId = (await getChainID()).toString(10) 67 | let keypath = '0.0.0.33.2' 68 | let tokenNumber = mv.keypathToToken(keypath).toString(10) 69 | 70 | // With OpenZeppelin 3.0 we need the token to actually exist before we can get its URI. 71 | assert.equal((await mrv.balanceOf.call(accounts[0])), Web3Utils.toWei("5000", "ether"), "We start with the expected amount of MRV for the test") 72 | await mrv.approve(reg.address, await mrv.balanceOf.call(accounts[0])) 73 | let nonce = mv.generateNonce() 74 | let data_hash = mv.hashTokenAndNonce(tokenNumber, nonce) 75 | await reg.commit(data_hash, Web3Utils.toWei("1000", "ether"), {from: accounts[0]}) 76 | await mv.advanceTime(10) 77 | await reg.reveal(tokenNumber, nonce) 78 | 79 | let url = await instance.tokenURI.call(tokenNumber) 80 | assert.equal(url, 'https://api.macroverse.io/vre/v1/chain/' + chainId + '/token/' + tokenNumber, "We got the expected URL") 81 | 82 | }) 83 | 84 | it("should prohibit non-owners changing token URI domain", async function() { 85 | let instance = await MacroverseRealEstate.deployed() 86 | 87 | let chainId = (await getChainID()).toString(10) 88 | let keypath = '0.0.0.33.2' 89 | let tokenNumber = mv.keypathToToken(keypath).toString(10) 90 | 91 | await assert_throws(instance.setTokenMetadataDomain('google.biz'), "Changed token domain") 92 | 93 | let url = await instance.tokenURI.call(tokenNumber) 94 | assert.equal(url, 'https://api.macroverse.io/vre/v1/chain/' + chainId + '/token/' + tokenNumber, "We got the expected URL") 95 | }) 96 | 97 | it("should prohibit non-owners changing token URI domain via the MacroverseUniversalRegistry", async function() { 98 | let instance = await MacroverseRealEstate.deployed() 99 | let reg = await MacroverseUniversalRegistry.deployed() 100 | 101 | let chainId = (await getChainID()).toString(10) 102 | let keypath = '0.0.0.33.2' 103 | let tokenNumber = mv.keypathToToken(keypath).toString(10) 104 | 105 | await assert_throws(reg.setTokenMetadataDomain('google.biz', {from: accounts[1]}), "Changed token domain") 106 | 107 | let url = await instance.tokenURI.call(tokenNumber) 108 | assert.equal(url, 'https://api.macroverse.io/vre/v1/chain/' + chainId + '/token/' + tokenNumber, "We got the expected URL") 109 | }) 110 | 111 | it("should allow owners changing token URI domain via the MacroverseUniversalRegistry", async function() { 112 | let instance = await MacroverseRealEstate.deployed() 113 | let reg = await MacroverseUniversalRegistry.deployed() 114 | 115 | let chainId = (await getChainID()).toString(10) 116 | let keypath = '0.0.0.33.2' 117 | let tokenNumber = mv.keypathToToken(keypath).toString(10) 118 | 119 | await reg.setTokenMetadataDomain('google.biz') 120 | 121 | let url = await instance.tokenURI.call(tokenNumber) 122 | assert.equal(url, 'https://google.biz/vre/v1/chain/' + chainId + '/token/' + tokenNumber, "We got the expected URL") 123 | 124 | // Make sure to change it back 125 | await reg.setTokenMetadataDomain('api.macroverse.io') 126 | 127 | let url2 = await instance.tokenURI.call(tokenNumber) 128 | assert.equal(url2, 'https://api.macroverse.io/vre/v1/chain/' + chainId + '/token/' + tokenNumber, "We got the expected URL") 129 | 130 | // Release token we've been holding for all the tests. 131 | await reg.release(tokenNumber) 132 | }) 133 | }) 134 | -------------------------------------------------------------------------------- /test/TestMacroverseStarGenerator.js: -------------------------------------------------------------------------------- 1 | let MacroverseStarGenerator = artifacts.require("MacroverseStarGenerator"); 2 | let UnrestrictedAccessControl = artifacts.require("UnrestrictedAccessControl"); 3 | 4 | // Load the Macroverse module JavaScript 5 | let mv = require('../src') 6 | 7 | contract('MacroverseStarGenerator', function(accounts) { 8 | it("should initially reject queries", async function() { 9 | let instance = await MacroverseStarGenerator.deployed() 10 | 11 | let failure_found = false 12 | 13 | await instance.getGalaxyDensity.call(0, 0, 0, {from: accounts[1]}).catch(async function () { 14 | failure_found = true 15 | }) 16 | 17 | assert.equal(failure_found, true, "Unauthorized query should fail") 18 | }) 19 | 20 | it("should let us change access control to unrestricted", async function() { 21 | let instance = await MacroverseStarGenerator.deployed() 22 | let unrestricted = await UnrestrictedAccessControl.deployed() 23 | await instance.changeAccessControl(unrestricted.address) 24 | 25 | assert.ok(true, "Access control can be changed without error") 26 | 27 | }) 28 | 29 | it("should then accept queries", async function() { 30 | let instance = await MacroverseStarGenerator.deployed() 31 | 32 | let failure_found = false 33 | 34 | await instance.getGalaxyDensity.call(0, 0, 0, {from: accounts[1]}).catch(async function () { 35 | failure_found = true 36 | }) 37 | 38 | assert.equal(failure_found, false, "Authorized query should succeed") 39 | }) 40 | 41 | it("should let us read the density", async function() { 42 | let instance = await MacroverseStarGenerator.deployed() 43 | let density = mv.fromReal(await instance.getGalaxyDensity.call(0, 0, 0)) 44 | 45 | assert.isAbove(density, 0.899999, "Density at the center of the galaxy is not too small") 46 | assert.isBelow(density, 0.900001, "Density at the center of the galaxy is not too big") 47 | }) 48 | 49 | it("should report 0.9 density out to a radius of 500", async function() { 50 | let instance = await MacroverseStarGenerator.deployed() 51 | let density = mv.fromReal(await instance.getGalaxyDensity.call(999, 0, 0)) 52 | assert.isAbove(density, 0.899999, "Density just inside the central bubble is not too small") 53 | assert.isBelow(density, 0.900001, "Density just inside the central bubble is not too big") 54 | 55 | density = mv.fromReal(await instance.getGalaxyDensity.call(250, 866, 433)) 56 | assert.isAbove(density, 0.899999, "Density just inside the central bubble is not too small") 57 | assert.isBelow(density, 0.900001, "Density just inside the central bubble is not too big") 58 | 59 | density = mv.fromReal(await instance.getGalaxyDensity.call(250, 866, 433)) 60 | assert.isAbove(density, 0.899999, "Density just inside the central bubble is not too small") 61 | assert.isBelow(density, 0.900001, "Density just inside the central bubble is not too big") 62 | 63 | density = mv.fromReal(await instance.getGalaxyDensity.call(35, 872, 488)) 64 | assert.isAbove(density, 0.899999, "Density just inside the central bubble is not too small") 65 | assert.isBelow(density, 0.900001, "Density just inside the central bubble is not too big") 66 | 67 | density = mv.fromReal(await instance.getGalaxyDensity.call(131, 0, 0)) 68 | assert.isAbove(density, 0.899999, "Density well inside the central bubble is not too small") 69 | assert.isBelow(density, 0.900001, "Density well inside the central bubble is not too big") 70 | 71 | density = mv.fromReal(await instance.getGalaxyDensity.call(35, 872, 489)) 72 | assert.isBelow(density, 0.899999, "Density just outside the central bubble is smaller") 73 | 74 | density = mv.fromReal(await instance.getGalaxyDensity.call(0, 1000, 0)) 75 | assert.isBelow(density, 0.499999, "Density just above the central bubble is smaller still") 76 | }) 77 | 78 | it("should report 1/60 density way out", async function() { 79 | let instance = await MacroverseStarGenerator.deployed() 80 | let density = mv.fromReal(await instance.getGalaxyDensity.call(999, 999, 999)) 81 | assert.isAbove(density, 0.016, "Density way out is not too small") 82 | assert.isBelow(density, 0.017, "Density way out is not too big") 83 | }) 84 | 85 | it("should report 1/2 density in the main disk", async function() { 86 | let instance = await MacroverseStarGenerator.deployed() 87 | let density = mv.fromReal(await instance.getGalaxyDensity.call(-6000, -5, -13)) 88 | assert.isAbove(density, 0.499999, "Density in disk is not too small") 89 | assert.isBelow(density, 0.500001, "Density in disk is not too big") 90 | }) 91 | 92 | it("should have lots of things in the disk", async function() { 93 | let instance = await MacroverseStarGenerator.deployed() 94 | let objectCount = 0; 95 | let sectorCount = 0; 96 | for (let x = -1; x < 2; x++) { 97 | for(let y = -1; y < 2; y++) { 98 | for (let z = -1; z < 2; z++) { 99 | let sectorObjects = (await instance.getSectorObjectCount.call(x, y, z)).toNumber() 100 | console.log(x, y, z, sectorObjects) 101 | objectCount += sectorObjects 102 | sectorCount += 1 103 | } 104 | } 105 | } 106 | 107 | let average = objectCount / sectorCount; 108 | 109 | assert.isAbove(average, 20, "Enough objects exist in the core") 110 | assert.isBelow(average, 30, "Too many objects don't exist in the core") 111 | }) 112 | 113 | it("should have some objects way out", async function() { 114 | let instance = await MacroverseStarGenerator.deployed() 115 | let objectCount = 0; 116 | let sectorCount = 0; 117 | for (let x = -1; x < 2; x++) { 118 | for(let y = -1; y < 2; y++) { 119 | for (let z = -1; z < 2; z++) { 120 | let sectorObjects = (await instance.getSectorObjectCount.call(x + 999, y + 999, z + 999)).toNumber() 121 | console.log(x, y, z, sectorObjects) 122 | objectCount += sectorObjects 123 | sectorCount += 1 124 | } 125 | } 126 | } 127 | 128 | let average = objectCount / sectorCount; 129 | 130 | assert.isAbove(average, 0, "Enough objects exist in deep space") 131 | assert.isBelow(average, 1, "Too many objects don't exist in deep space") 132 | }) 133 | 134 | it("should have a moderate number of objects in the disk", async function() { 135 | let instance = await MacroverseStarGenerator.deployed() 136 | let objectCount = 0; 137 | let sectorCount = 0; 138 | for (let x = -1; x < 2; x++) { 139 | for(let y = -1; y < 2; y++) { 140 | for (let z = -1; z < 2; z++) { 141 | let sectorObjects = (await instance.getSectorObjectCount.call(x - 3000, y, z + 3000)).toNumber() 142 | console.log(x, y, z, sectorObjects) 143 | objectCount += sectorObjects 144 | sectorCount += 1 145 | } 146 | } 147 | } 148 | 149 | let average = objectCount / sectorCount; 150 | 151 | assert.isAbove(average, 10, "Enough objects exist in the disk") 152 | assert.isBelow(average, 20, "Too many objects don't exist in the disk") 153 | }) 154 | 155 | it("should produce stars of reasonable mass", async function() { 156 | let instance = await MacroverseStarGenerator.deployed() 157 | let seed = await instance.getSectorObjectSeed.call(0, 0, 0, 0) 158 | let objClass = (await instance.getObjectClass.call(seed)).toNumber() 159 | let objType = (await instance.getObjectSpectralType.call(seed, objClass)).toNumber() 160 | let objMass = mv.fromReal(await instance.getObjectMass.call(seed, objClass, objType)) 161 | 162 | assert.isBelow(objMass, 100, "A star is <100 solar masses") 163 | 164 | }) 165 | 166 | it("should let us scan sector 0", async function() { 167 | let instance = await MacroverseStarGenerator.deployed() 168 | 169 | let starCount = (await instance.getSectorObjectCount.call(0, 0, 0)).toNumber() 170 | console.log("Stars in origin sector: ", starCount) 171 | 172 | let starPromises = [] 173 | 174 | let foundPlanets = false; 175 | 176 | for (let star = 0; star < starCount; star++) { 177 | 178 | starPromises.push(async function() { 179 | 180 | // Generate each star 181 | // Make a seed 182 | let seed = await instance.getSectorObjectSeed.call(0, 0, 0, star) 183 | 184 | // Decide on a position 185 | let pos = await instance.getObjectPosition.call(seed) 186 | let [ x, y, z] = [pos[0], pos[1], pos[2]] 187 | x = mv.fromReal(x) 188 | y = mv.fromReal(y) 189 | z = mv.fromReal(z) 190 | 191 | // Then get the class 192 | let objClass = (await instance.getObjectClass.call(seed)).toNumber() 193 | // Then make the spectral type 194 | let objType = (await instance.getObjectSpectralType.call(seed, objClass)).toNumber() 195 | // Then make the mass 196 | let objMass = mv.fromReal(await instance.getObjectMass.call(seed, objClass, objType)) 197 | // And decide if it has planets 198 | let hasPlanets = await instance.getObjectHasPlanets.call(seed, objClass, objType) 199 | 200 | console.log("Star " + star + " at " + x + "," + y + "," + z + " ly is a " + mv.objectClasses[objClass] + " " + mv.spectralTypes[objType] + " of " + objMass + " solar masses" + (hasPlanets ? " with planets" : "")) 201 | 202 | if (hasPlanets) { 203 | foundPlanets = true; 204 | } 205 | 206 | }()) 207 | } 208 | 209 | await Promise.all(starPromises) 210 | 211 | assert.ok(true, "Report can be printed without error") 212 | assert.equal(foundPlanets, true, "Planets are found") 213 | 214 | }) 215 | 216 | 217 | 218 | }) 219 | -------------------------------------------------------------------------------- /test/TestMacroverseStarRegistry.js: -------------------------------------------------------------------------------- 1 | let MacroverseStarRegistry = artifacts.require("MacroverseStarRegistry"); 2 | let MRVToken = artifacts.require("MRVToken"); 3 | 4 | contract('MacroverseStarRegistry', function(accounts) { 5 | it("should not allow claiming with actually possible funds", async function() { 6 | let instance = await MacroverseStarRegistry.deployed() 7 | let token = await MRVToken.deployed() 8 | 9 | // Approve all the funds. 10 | // Don't do any more approvals in the test because the token requires us 11 | // to reduce approved amount to 0 defore approving any more. 12 | await token.approve(MacroverseStarRegistry.address, await token.balanceOf.call(accounts[0])) 13 | 14 | await instance.claimOwnership("0xDEADBEEF", await token.balanceOf.call(accounts[0]) ).then(function () { 15 | assert.ok(false, "Successfully claimed star on deactivated registry") 16 | }).catch(async function () { 17 | assert.ok(true, "Failed to claim star on deactivated registry") 18 | }) 19 | 20 | assert.equal(await instance.ownerOf.call("0xDEADBEEF"), 0, "The star is still unowned") 21 | 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /test/TestMacroverseSystemGenerator.js: -------------------------------------------------------------------------------- 1 | let MacroverseSystemGenerator = artifacts.require('MacroverseSystemGenerator') 2 | let UnrestrictedAccessControl = artifacts.require('UnrestrictedAccessControl') 3 | 4 | // Load the Macroverse module JavaScript 5 | let mv = require('../src') 6 | 7 | // TODO: Some of these tests really test MacroverseStarGeneratorPatch1 8 | let MacroverseStarGeneratorPatch1 = artifacts.require('MacroverseStarGeneratorPatch1') 9 | 10 | contract('MacroverseSystemGenerator', function(accounts) { 11 | it("should initially reject queries", async function() { 12 | let instance = await MacroverseSystemGenerator.deployed() 13 | 14 | let failure_found = false 15 | 16 | await (instance.getWorldSeed.call('0x776f6d626174', 5, {from: accounts[1]}).catch(async function () { 17 | failure_found = true 18 | })) 19 | 20 | assert.equal(failure_found, true, "Unauthorized query should fail") 21 | }) 22 | 23 | it("should let us change access control to unrestricted", async function() { 24 | let instance = await MacroverseSystemGenerator.deployed() 25 | let unrestricted = await UnrestrictedAccessControl.deployed() 26 | await instance.changeAccessControl(unrestricted.address) 27 | 28 | assert.ok(true, "Access control can be changed without error") 29 | 30 | }) 31 | 32 | it("should then accept queries", async function() { 33 | let instance = await MacroverseSystemGenerator.deployed() 34 | 35 | let failure_found = false 36 | 37 | await (instance.getWorldSeed.call('0x776f6d626174', 5, {from: accounts[1]}).catch(async function () { 38 | failure_found = true 39 | })) 40 | 41 | assert.equal(failure_found, false, "Authorized query should succeed") 42 | }) 43 | 44 | it("should have Y and X Euler angles for the fred system", async function() { 45 | let instance = await MacroverseStarGeneratorPatch1.deployed(); 46 | let realAngles = (await instance.getObjectYXAxisAngles('0x66726564')) 47 | let eulerY = mv.fromReal(realAngles[0]) 48 | let eulerX = mv.fromReal(realAngles[1]) 49 | 50 | // Y angle (applied first) must be from -pi to pi 51 | assert.isAbove(eulerY, -Math.PI); 52 | assert.isBelow(eulerY, Math.PI); 53 | 54 | // X angle (applied second) must be from 0 to pi 55 | assert.isAbove(eulerX, 0); 56 | assert.isBelow(eulerX, Math.PI); 57 | 58 | // We don't pretend to be uniform. We will over-represent upwards (+Z) and downwards poles. 59 | 60 | }) 61 | 62 | it("should have 8 planets in the fred system", async function() { 63 | let instance = await MacroverseStarGeneratorPatch1.deployed() 64 | let count = (await instance.getObjectPlanetCount.call('0x66726564', mv.objectClass['MainSequence'], mv.spectralType['TypeG'])).toNumber() 65 | assert.equal(count, 8); 66 | 67 | }) 68 | 69 | it("should have a luminosity that is reasonable", async function() { 70 | let instance = await MacroverseStarGeneratorPatch1.deployed() 71 | let luminosity = mv.fromReal(await instance.getObjectLuminosity.call('0x66726564', mv.objectClass['MainSequence'], mv.toReal(1.0))) 72 | // Luminosities are randomized to between 95% and 105% of expected 73 | assert.isAbove(luminosity, 0.95) 74 | assert.isBelow(luminosity, 1.05) 75 | }) 76 | 77 | it("should have a habitable zone that is reasonable", async function() { 78 | let instance = await MacroverseStarGeneratorPatch1.deployed() 79 | let realBounds = await instance.getObjectHabitableZone.call(mv.toReal(1.0)) 80 | 81 | let habStart = mv.fromReal(realBounds[0]) 82 | let habEnd = mv.fromReal(realBounds[1]) 83 | 84 | // This should scale with the square root of the luminosity (so we scale the bounds) 85 | assert.isAbove(habStart / mv.AU, 0.75 * Math.sqrt(0.95)) 86 | assert.isBelow(habStart / mv.AU, 0.75 * Math.sqrt(1.05)) 87 | assert.isAbove(habEnd / mv.AU, 2.0 * Math.sqrt(0.95)) 88 | assert.isBelow(habEnd / mv.AU, 2.0 * Math.sqrt(1.05)) 89 | }) 90 | 91 | it("should have a Terrestrial planet first", async function() { 92 | let instance = await MacroverseSystemGenerator.deployed() 93 | 94 | let planetSeed = await instance.getWorldSeed.call('0x66726564', 0) 95 | let planetClass = mv.worldClasses[(await instance.getPlanetClass.call(planetSeed, 0, 8)).toNumber()] 96 | assert.equal(planetClass, 'Terrestrial') 97 | }) 98 | 99 | it("should be a super-earth", async function() { 100 | let instance = await MacroverseSystemGenerator.deployed() 101 | let planetSeed = await instance.getWorldSeed.call('0x66726564', 0) 102 | let planetClassNum = mv.worldClass['Terrestrial'] 103 | let planetMass = mv.fromReal(await instance.getWorldMass.call(planetSeed, planetClassNum)) 104 | 105 | assert.isAbove(planetMass, 6.27) 106 | assert.isBelow(planetMass, 6.29) 107 | }) 108 | 109 | it("should have an orbit from about 0.24 to 0.29 AU", async function() { 110 | let instance = await MacroverseSystemGenerator.deployed() 111 | let stargen_patch = await MacroverseStarGeneratorPatch1.deployed() 112 | let planetSeed = await instance.getWorldSeed.call('0x66726564', 0) 113 | let planetClassNum = mv.worldClass['Terrestrial'] 114 | let parentClassNum = mv.objectClass['MainSequence'] 115 | let parentTypeNum = mv.spectralType['TypeG'] 116 | let parentRealMass = mv.toReal(1.0) 117 | let parentRealLuminosity = await stargen_patch.getObjectLuminosity.call('0x66726564', parentClassNum, parentRealMass) 118 | 119 | let realBounds = await stargen_patch.getObjectHabitableZone.call(parentRealLuminosity) 120 | 121 | let realOrbit = await instance.getPlanetOrbitDimensions.call(realBounds[0], realBounds[1], 122 | planetSeed, planetClassNum, mv.toReal(0)) 123 | let [realPeriapsis, realApoapsis, realClearance] = [realOrbit[0], realOrbit[1], realOrbit[2]] 124 | 125 | assert.isAbove(mv.fromReal(realPeriapsis) / mv.AU, 0.24) 126 | assert.isBelow(mv.fromReal(realPeriapsis) / mv.AU, 0.25) 127 | 128 | assert.isAbove(mv.fromReal(realApoapsis) / mv.AU, 0.29) 129 | assert.isBelow(mv.fromReal(realApoapsis) / mv.AU, 0.30) 130 | 131 | // We sould also have reasonably symmetric-ish clearance 132 | assert.isAbove(mv.fromReal(realClearance) / mv.AU, 0.60) 133 | assert.isBelow(mv.fromReal(realPeriapsis) / mv.AU, 0.70) 134 | }) 135 | 136 | it("should have a semimajor axis of 0.27 AU and an eccentricity of about 0.08", async function() { 137 | 138 | let instance = await MacroverseSystemGenerator.deployed() 139 | let stargen_patch = await MacroverseStarGeneratorPatch1.deployed() 140 | let planetSeed = await instance.getWorldSeed.call('0x66726564', 0) 141 | let planetClassNum = mv.worldClass['Terrestrial'] 142 | let parentClassNum = mv.objectClass['MainSequence'] 143 | let parentTypeNum = mv.spectralType['TypeG'] 144 | let parentRealMass = mv.toReal(1.0) 145 | let parentRealLuminosity = await stargen_patch.getObjectLuminosity.call('0x66726564', parentClassNum, parentRealMass) 146 | 147 | let realBounds = await stargen_patch.getObjectHabitableZone.call(parentRealLuminosity) 148 | 149 | let realOrbit = await instance.getPlanetOrbitDimensions.call(realBounds[0], realBounds[1], 150 | planetSeed, planetClassNum, mv.toReal(0)) 151 | let [realPeriapsis, realApoapsis, realClearance] = [realOrbit[0], realOrbit[1], realOrbit[2]] 152 | 153 | let realShape = await instance.convertOrbitShape.call(realPeriapsis, realApoapsis) 154 | let [realSemimajor, realEccentricity] = [realShape[0], realShape[1]] 155 | 156 | assert.isAbove(mv.fromReal(realSemimajor) / mv.AU, 0.27) 157 | assert.isBelow(mv.fromReal(realSemimajor) / mv.AU, 0.28) 158 | 159 | assert.isAbove(mv.fromReal(realEccentricity), 0.08) 160 | assert.isBelow(mv.fromReal(realEccentricity), 0.09) 161 | 162 | }) 163 | 164 | it("should let us dump the whole system for not too much gas", async function() { 165 | let instance = await MacroverseSystemGenerator.deployed() 166 | let stargen_patch = await MacroverseStarGeneratorPatch1.deployed() 167 | let parentClassNum = mv.objectClass['MainSequence'] 168 | let parentTypeNum = mv.spectralType['TypeG'] 169 | 170 | let totalGas = 0 171 | 172 | let parentRealMass = mv.toReal(1.0) 173 | let parentRealLuminosity = await stargen_patch.getObjectLuminosity.call('0x66726564', parentClassNum, parentRealMass) 174 | totalGas += await stargen_patch.getObjectLuminosity.estimateGas('0x66726564', parentClassNum, parentRealMass) 175 | 176 | let realBounds = await stargen_patch.getObjectHabitableZone.call(parentRealLuminosity) 177 | let [realHabStart, realHabEnd] = [realBounds[0], realBounds[1]] 178 | 179 | totalGas += await stargen_patch.getObjectHabitableZone.estimateGas(parentRealLuminosity) 180 | 181 | let count = (await stargen_patch.getObjectPlanetCount.call('0x66726564', parentClassNum, parentTypeNum)).toNumber() 182 | totalGas += await stargen_patch.getObjectPlanetCount.estimateGas('0x66726564', parentClassNum, parentTypeNum) 183 | 184 | var prevClearance = mv.toReal(0) 185 | 186 | for (let i = 0; i < count; i++) { 187 | // Define the planet 188 | let planetSeed = await instance.getWorldSeed.call('0x66726564', i) 189 | totalGas += await instance.getWorldSeed.estimateGas('0x66726564', i) 190 | let planetClassNum = (await instance.getPlanetClass.call(planetSeed, i, count)).toNumber() 191 | totalGas += await instance.getPlanetClass.estimateGas(planetSeed, i, count) 192 | let realMass = await instance.getWorldMass.call(planetSeed, planetClassNum) 193 | totalGas += await instance.getWorldMass.estimateGas(planetSeed, planetClassNum) 194 | let planetMass = mv.fromReal(realMass) 195 | 196 | // Define the orbit shape 197 | let realOrbit = await instance.getPlanetOrbitDimensions.call(realHabStart, realHabEnd, 198 | planetSeed, planetClassNum, prevClearance) 199 | let [realPeriapsis, realApoapsis, newClearance] = [realOrbit[0], realOrbit[1], realOrbit[2]] 200 | totalGas += await instance.getPlanetOrbitDimensions.estimateGas(realHabStart, realHabEnd, 201 | planetSeed, planetClassNum, prevClearance) 202 | prevClearance = newClearance 203 | 204 | let planetPeriapsis = mv.fromReal(realPeriapsis) / mv.AU; 205 | let planetApoapsis = mv.fromReal(realApoapsis) / mv.AU; 206 | 207 | let realShape = await instance.convertOrbitShape.call(realPeriapsis, realApoapsis) 208 | let [realSemimajor, realEccentricity] = [realShape[0], realShape[1]] 209 | totalGas += await instance.convertOrbitShape.estimateGas(realPeriapsis, realApoapsis) 210 | let planetEccentricity = mv.fromReal(realEccentricity); 211 | 212 | // Define the orbital plane. Make sure to convert everything to degrees for display. 213 | let realLan = await instance.getWorldLan.call(planetSeed) 214 | totalGas += await instance.getWorldLan.estimateGas(planetSeed) 215 | let planetLan = mv.degrees(mv.fromReal(realLan)) 216 | let realInclination = await instance.getPlanetInclination.call(planetSeed, planetClassNum) 217 | totalGas += await instance.getPlanetInclination.estimateGas(planetSeed, planetClassNum) 218 | let planetInclination = mv.degrees(mv.fromReal(realInclination)) 219 | 220 | // Define the position in the orbital plane 221 | let realAop = await instance.getWorldAop.call(planetSeed) 222 | totalGas += await instance.getWorldAop.estimateGas(planetSeed) 223 | let planetAop = mv.degrees(mv.fromReal(realAop)) 224 | let realMeanAnomalyAtEpoch = await instance.getWorldMeanAnomalyAtEpoch.call(planetSeed) 225 | totalGas += await instance.getWorldMeanAnomalyAtEpoch.estimateGas(planetSeed) 226 | let planetMeanAnomalyAtEpoch = mv.degrees(mv.fromReal(realMeanAnomalyAtEpoch)) 227 | 228 | let isTidallyLocked = await instance.isTidallyLocked(planetSeed, i) 229 | totalGas += await instance.isTidallyLocked.estimateGas(planetSeed, i) 230 | 231 | let xAngle = 0 232 | let yAngle = 0 233 | let spinRate = 0 234 | 235 | if (!isTidallyLocked) { 236 | // Define the spin parameters 237 | let realAngles = await instance.getWorldYXAxisAngles(planetSeed) 238 | totalGas += await instance.getWorldYXAxisAngles.estimateGas(planetSeed) 239 | yAngle = mv.fromReal(realAngles[0]) 240 | xAngle = mv.fromReal(realAngles[1]) 241 | 242 | let realSpinRate = await instance.getWorldSpinRate(planetSeed) 243 | totalGas += await instance.getWorldSpinRate.estimateGas(planetSeed) 244 | // Spin rate is in radians per Julian year to match mean motion units 245 | spinRate = mv.fromReal(realSpinRate) 246 | } 247 | 248 | console.log('Planet ' + i + ': ' + mv.worldClasses[planetClassNum] + ' with mass ' + 249 | planetMass + ' Earths between ' + planetPeriapsis + ' and ' + planetApoapsis + ' AU') 250 | console.log('\tEccentricity: ' + planetEccentricity + ' LAN: ' + planetLan + '° Inclination: ' + planetInclination + '°') 251 | console.log('\tAOP: ' + planetAop + '° Mean Anomaly at Epoch: ' + planetMeanAnomalyAtEpoch + '°') 252 | if (isTidallyLocked) { 253 | console.log('\tTidally Locked') 254 | } else { 255 | console.log('\tObliquity: ' + mv.degrees(xAngle) + '° Ecliptic Equator Angle: ' + mv.degrees(yAngle) + 256 | '° Spin rate: ' + spinRate/(Math.PI * 2) + ' rev/Julian year') 257 | } 258 | } 259 | 260 | console.log('Gas to generate system: ' + totalGas) 261 | assert.isBelow(totalGas, 6721975) 262 | 263 | 264 | }) 265 | 266 | }) 267 | -------------------------------------------------------------------------------- /test/TestMacroverseTerrainGenerator.js: -------------------------------------------------------------------------------- 1 | let MacroverseTerrainGenerator = artifacts.require("MacroverseTerrainGenerator"); 2 | 3 | // Load the Macroverse module JavaScript 4 | let mv = require('../src') 5 | 6 | contract('MacroverseTerrainGenerator', function(accounts) { 7 | 8 | it("should give trixel heights that reflect midpoint displacement with a scaling factor of 1/2", async function() { 9 | let instance = await MacroverseTerrainGenerator.deployed() 10 | 11 | // Prepare token IDs for smaller and smaller trixels 12 | let trixels = [] 13 | let keypath = '0.1.2.0.0.-1' 14 | for (let i = 0; i < 27; i++) { 15 | keypath = keypath + '.1' 16 | trixels.push(mv.keypathToToken(keypath)) 17 | } 18 | 19 | let heights = [] 20 | for (let token of trixels) { 21 | let height = instance.getTrixelHeight.call(token, '0x12345').then(mv.fromReal) 22 | heights.push(height) 23 | } 24 | for (let i = 0; i < heights.length; i++) { 25 | heights[i] = await heights[i] 26 | } 27 | 28 | for (let i = 0; i < heights.length; i++) { 29 | console.log(heights[i]) 30 | if (i > 0) { 31 | assert.isBelow(Math.abs(heights[i] - heights[i - 1]), Math.pow(2, -i), "Difference from previous height is not too large") 32 | } 33 | } 34 | 35 | }) 36 | 37 | }) 38 | -------------------------------------------------------------------------------- /test/TestMinimumBalanceAccessControl.js: -------------------------------------------------------------------------------- 1 | let MinimumBalanceAccessControl = artifacts.require("MinimumBalanceAccessControl"); 2 | 3 | contract('MinimumBalanceAccessControl', function(accounts) { 4 | 5 | // Remember that we deployed the MRVToken with the beneficiary as account 0, granting it some tokens. 6 | 7 | it("should reject queries for addresses with zero balance", async function() { 8 | let instance = await MinimumBalanceAccessControl.deployed() 9 | 10 | assert.equal(await instance.allowQuery.call(accounts[1], accounts[1]), false, "Query from empty account is rejected") 11 | 12 | }) 13 | 14 | it("should allow queries for addresses with sufficient balance", async function() { 15 | let instance = await MinimumBalanceAccessControl.deployed() 16 | 17 | assert.equal(await instance.allowQuery.call(accounts[0], accounts[0]), true, "Query from full account is accepted") 18 | 19 | }) 20 | }) -------------------------------------------------------------------------------- /test/TestOrbitalMechanics.js: -------------------------------------------------------------------------------- 1 | let OrbitalMechanics = artifacts.require('OrbitalMechanics') 2 | 3 | // Load the Macroverse module JavaScript 4 | let mv = require('../src') 5 | 6 | contract('OrbitalMechanics', function(accounts) { 7 | 8 | it("should compute correct mean angular motions", async function() { 9 | let instance = await OrbitalMechanics.deployed() 10 | 11 | // Earth semimajor axis is 1.00000011 AU from https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html 12 | let real_semimajor_meters = mv.toReal(1.00000011 * mv.AU) 13 | // The sun is exactly 1 solar mass. Ignore the fact that this means the unit gets smaller over time... 14 | let real_sols = mv.toReal(1) 15 | let meanAngularMotion = mv.fromReal(await instance.computeMeanAngularMotion.call(real_sols, real_semimajor_meters)) 16 | 17 | assert.approximately(meanAngularMotion, 2 * Math.PI / mv.SIDERIAL_YEAR * mv.JULIAN_YEAR, 1E-10, 18 | "mean angular motion of Earth should be 2 pi radians per Siderial year") 19 | }) 20 | 21 | it("should compute correct mean anomalies", async function() { 22 | let instance = await OrbitalMechanics.deployed() 23 | 24 | // Set mean anomaly at epoch to 0 25 | let real_ma0 = mv.toReal(0); 26 | 27 | // Compute Earth's mean motion 28 | let real_motion = mv.toReal(2 * Math.PI / mv.SIDERIAL_YEAR * mv.JULIAN_YEAR) 29 | 30 | // Multiply by this to get Julian years in a given number of complete orbits 31 | let s2j = mv.SIDERIAL_YEAR / mv.JULIAN_YEAR 32 | 33 | 34 | 35 | assert.approximately(mv.fromReal(await instance.computeMeanAnomaly.call(real_ma0, real_motion, mv.toReal(0))), 0, 1E-10, 36 | "at time 0 we are at 0 rads relative to epoch") 37 | 38 | assert.approximately(mv.fromReal(await instance.computeMeanAnomaly.call(mv.toReal(5), real_motion, mv.toReal(0))), 5, 1E-10, 39 | "mean anomaly at epoch is added in") 40 | 41 | // TODO: 1 siderial year comes out just slightly short of wrapping to 0 radians. 42 | assert.approximately(Math.cos(mv.fromReal(await instance.computeMeanAnomaly.call(real_ma0, real_motion, mv.toReal(1 * s2j)))), 1, 1E-10, 43 | "after 1 siderail year we are back at the start or nearly there") 44 | 45 | assert.approximately(Math.cos(mv.fromReal(await instance.computeMeanAnomaly.call(real_ma0, real_motion, mv.toReal(100000 * s2j)))), 1, 1E-10, 46 | "after 100k siderail years we are back at the start or nearly there") 47 | 48 | assert.isAtMost(Math.cos(mv.fromReal(await instance.computeMeanAnomaly.call(real_ma0, real_motion, mv.toReal(100000 * s2j)))), 2 * Math.PI, 49 | "angles actually wrap around") 50 | 51 | assert.approximately(mv.fromReal(await instance.computeMeanAnomaly.call(real_ma0, real_motion, mv.toReal(0.5 * s2j))), Math.PI, 1E-10, 52 | "after half a year we are halfway around") 53 | 54 | }) 55 | 56 | it("should compute correct eccentric anomalies", async function() { 57 | let instance = await OrbitalMechanics.deployed() 58 | 59 | for (let eccentricity of [0, 0.0001, 0.1, 0.5, 0.8, 0.9, 0.967, 0.99999]) { 60 | let real_eccentricity = mv.toReal(eccentricity) 61 | 62 | for (let correct_ea of [0, 1, Math.PI/2, 4/3 * Math.PI, 2 * Math.PI - 0.0001]) { 63 | // For each eccentric anomaly we want 64 | 65 | // Compute the corresponding mean anomaly 66 | let ma = correct_ea - eccentricity * Math.sin(correct_ea) 67 | let real_ma = mv.toReal(ma) 68 | 69 | // Back-compute the eccentric anomaly 70 | let real_computed_ea = await instance.computeEccentricAnomalyLimited.call(real_ma, real_eccentricity, 10) 71 | let computed_ea = mv.fromReal(real_computed_ea) 72 | 73 | // Make sure we got it right (within looser bounds for high and hard to generate eccentricities) 74 | assert.approximately(computed_ea, correct_ea, eccentricity < 0.9 ? 1E-8 : 1E-5, 75 | "EA of " + correct_ea + " should be computed from MA of " + ma + " at eccentricity " + eccentricity) 76 | 77 | } 78 | } 79 | 80 | }) 81 | 82 | it("should compute correct true anomalies", async function() { 83 | let instance = await OrbitalMechanics.deployed() 84 | 85 | for (let eccentricity of [0, 0.0001, 0.1, 0.5, 0.8, 0.9, 0.967, 0.99999]) { 86 | let real_eccentricity = mv.toReal(eccentricity) 87 | 88 | for (let ea of [0, 1, Math.PI/2, 4/3 * Math.PI, 2 * Math.PI - 0.0001]) { 89 | // For each eccentric anomaly 90 | let real_ea = mv.toReal(ea) 91 | 92 | // Compute the target true anomaly 93 | let correct_ta = 2 * Math.atan2(Math.sqrt(1 - eccentricity) * Math.cos(ea / 2), Math.sqrt(1 + eccentricity) * Math.sin(ea / 2)) 94 | 95 | // Compute the true anomaly in Solidity 96 | let real_computed_ta = await instance.computeTrueAnomaly.call(real_ea, real_eccentricity) 97 | let computed_ta = mv.fromReal(real_computed_ta) 98 | 99 | // Make sure we got it right 100 | // TODO: Make this more accurate by improving atan2 and sqrt 101 | assert.approximately(computed_ta, correct_ta, 1E-5, 102 | "TA of " + correct_ta + " should be computed from EA of " + ea + " at eccentricity " + eccentricity) 103 | 104 | } 105 | } 106 | }) 107 | 108 | it("should compute correct radius", async function() { 109 | let instance = await OrbitalMechanics.deployed() 110 | 111 | for (let eccentricity of [0, 0.0001, 0.1, 0.5, 0.8, 0.9, 0.967, 0.99999]) { 112 | let real_eccentricity = mv.toReal(eccentricity) 113 | 114 | for (let ta of [0, 1, Math.PI/2, 4/3 * Math.PI, 2 * Math.PI - 0.0001]) { 115 | let real_ta = mv.toReal(ta) 116 | 117 | for (let semimajor_meters of [0.001, 1, 10, 1.00000011 * mv.AU, 10 * mv.AU, 100 * mv.AU]) { 118 | let real_semimajor_meters = mv.toReal(semimajor_meters) 119 | 120 | let correct_radius = semimajor_meters * (1 - Math.pow(eccentricity, 2)) / (1 + eccentricity * Math.cos(ta)) 121 | 122 | let real_computed_radius = await instance.computeRadius.call(real_ta, real_semimajor_meters, real_eccentricity) 123 | let computed_radius = mv.fromReal(real_computed_radius) 124 | 125 | assert.approximately(computed_radius, correct_radius, real_semimajor_meters/1E10, 126 | "Radius of " + correct_radius + " should be computed from TA of " + ta + " at semimajor " + semimajor_meters + " and eccentricity " + eccentricity) 127 | 128 | 129 | } 130 | 131 | } 132 | } 133 | }) 134 | 135 | it("should compute a current orbital position", async function() { 136 | let instance = await OrbitalMechanics.deployed() 137 | 138 | /** 139 | * I got this from the system generator test 140 | * 141 | * Terrestrial with mass 5.250290167260573 Earths between 1.0983088145299555 and 1.1140059916272032 AU 142 | * Eccentricity: 0.007095363215739781 LAN: 107.55660144903162° Inclination: 0.17810602784688181° 143 | * AOP: 265.32128537763356° Mean Anomaly at Epoch: 70.03141466547464° 144 | */ 145 | 146 | // Define orbital parameters 147 | let semimajor_meters = (1.0983088145299555 + 1.1140059916272032) / 2 * mv.AU 148 | let eccentricity = 0.007095363215739781 149 | let lan = mv.radians(107.55660144903162) 150 | let inclination = mv.radians(0.17810602784688181) 151 | let aop = mv.radians(265.32128537763356) 152 | let ma0 = mv.radians(70.03141466547464) 153 | 154 | // Pretend it's around the sun 155 | let central_mass = 1.0 156 | 157 | // Decide what time it is 158 | let block = await web3.eth.getBlock(web3.eth.blockNumber) 159 | let mv_time = mv.yearsSinceEpoch(block.timestamp) 160 | console.log("The time is " + mv_time + " years since Macroverse epoch") 161 | 162 | // Convert to real 163 | let real_semimajor_meters = mv.toReal(semimajor_meters) 164 | let real_eccentricity = mv.toReal(eccentricity) 165 | let real_lan = mv.toReal(lan) 166 | let real_inclination = mv.toReal(inclination) 167 | let real_aop = mv.toReal(aop) 168 | let real_ma0 = mv.toReal(ma0) 169 | let real_central_mass = mv.toReal(central_mass) 170 | let real_time = mv.toReal(mv_time) 171 | 172 | // Track gas 173 | let totalGas = 0 174 | 175 | // Do all the orbit steps 176 | let real_mean_angular_motion = await instance.computeMeanAngularMotion.call(real_central_mass, real_semimajor_meters) 177 | totalGas += await instance.computeMeanAngularMotion.estimateGas(real_central_mass, real_semimajor_meters) 178 | let real_mean_anomaly = await instance.computeMeanAnomaly.call(real_ma0, real_mean_angular_motion, real_time) 179 | totalGas += await instance.computeMeanAnomaly.estimateGas(real_ma0, real_mean_angular_motion, real_time) 180 | let real_eccentric_anomaly = await instance.computeEccentricAnomaly.call(real_mean_anomaly, real_eccentricity) 181 | totalGas += await instance.computeEccentricAnomaly.estimateGas(real_mean_anomaly, real_eccentricity) 182 | let real_true_anomaly = await instance.computeTrueAnomaly.call(real_eccentric_anomaly, real_eccentricity) 183 | totalGas += await instance.computeTrueAnomaly.estimateGas(real_eccentric_anomaly, real_eccentricity) 184 | let real_radius = await instance.computeRadius.call(real_true_anomaly, real_semimajor_meters, real_eccentricity) 185 | totalGas += await instance.computeRadius.estimateGas(real_true_anomaly, real_semimajor_meters, real_eccentricity) 186 | let offset = await instance.computeCartesianOffset.call(real_radius, real_true_anomaly, real_lan, real_inclination, real_aop) 187 | let [real_x, real_y, real_z] = [offset[0], offset[1], offset[2]] 188 | totalGas += await instance.computeCartesianOffset.estimateGas(real_radius, real_true_anomaly, real_lan, real_inclination, real_aop) 189 | 190 | console.log("Planet currently at <" + mv.fromReal(real_x) + "," + mv.fromReal(real_y) + "," + mv.fromReal(real_z) + "> computed for " + totalGas + " gas") 191 | 192 | assert.isBelow(totalGas, 6721975) 193 | 194 | }) 195 | }) 196 | -------------------------------------------------------------------------------- /test/TestRealMath.js: -------------------------------------------------------------------------------- 1 | var RealMath = artifacts.require("RealMath") 2 | 3 | // Load the Macroverse module JavaScript 4 | let mv = require('../src') 5 | 6 | contract('RealMath', function(accounts) { 7 | it("should come with working accessory functions", async function() { 8 | let epsilon = 1/Math.pow(2, 40) 9 | for (let val of [0, 1, -1, 0.1, -0.1, 1.1, -1.1, 1e14, -1e14, 3.14159, 1e14 + 0.1, 1e14 - 0.1, -1e14 - 0.1, -1e14 + 0.1, 1e-10, -1e-10]) { 10 | let real = mv.toReal(val) 11 | let roundtrip = mv.fromReal(real) 12 | assert.approximately(roundtrip, val, epsilon, "Converting to and from reals in JS works") 13 | } 14 | 15 | assert.equal(mv.fromReal(mv.REAL_ONE), 1, "Converting known real to number produces expected value") 16 | }) 17 | 18 | it("should do multiplication", async function() { 19 | let instance = await RealMath.deployed() 20 | 21 | assert.equal(mv.fromReal(await instance.mul.call(mv.toReal(7), mv.toReal(8))), 56, "Multiplication of small integers works") 22 | }) 23 | 24 | it("should do integer exponentiation", async function() { 25 | let instance = await RealMath.deployed() 26 | 27 | assert.equal(mv.fromReal(await instance.ipow.call(mv.toReal(10), 3)), Math.pow(10, 3), "Exponentiation to small powers works") 28 | 29 | assert.approximately(mv.fromReal(await instance.ipow.call(mv.toReal(1.001), 100)), 1.105, 0.001, "Exponentiation to large powers works") 30 | 31 | }) 32 | 33 | it("should compute logarithms", async function() { 34 | let instance = await RealMath.deployed() 35 | 36 | for(let val of [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.5, 2.0, 3.0, 3.67, Math.PI, Math.E, 1000, 1E10, 4.28374782E25]) { 37 | let result = mv.fromReal(await instance.ln.call(mv.toReal(val))) 38 | 39 | // Make sure we get the right answer 40 | assert.approximately(result, Math.log(val), 2E-11, "log of " + val + " should be approximately right") 41 | 42 | // Make sure we got it fast 43 | const MAX_ITERATIONS = 15 44 | let resultAtLimit = mv.fromReal(await instance.lnLimited.call(mv.toReal(val), MAX_ITERATIONS)) 45 | assert.equal(result, resultAtLimit, "At most " + MAX_ITERATIONS + " iterations are sufficient for convergence") 46 | } 47 | }) 48 | 49 | it("should compute exp", async function() { 50 | let instance = await RealMath.deployed() 51 | 52 | for(let val of [-10, -Math.PI, -1, -0.1, 0, 0.1, 0.2, 0.9, 1.0, 1.1, 1.5, 2.0, 3.0, 3.67, Math.PI, Math.E]) { 53 | let result = mv.fromReal(await instance.exp.call(mv.toReal(val))) 54 | 55 | // Make sure we get the right answer 56 | assert.approximately(result, Math.exp(val), 1E-6, "exp of " + val + " should be approximately right") 57 | } 58 | 59 | // TODO: Test larger values (with less accuracy required?) 60 | // TODO: Make more accurate? 61 | 62 | }) 63 | 64 | it("should compute pow", async function() { 65 | let instance = await RealMath.deployed() 66 | 67 | for (let base of [0, 1, -1, 0.5, 15.7, 1000, 36]) { 68 | for (let exponent of [0, 1, -1, 0.5, 1.9999, 2, 2.0001, 15.7]) { 69 | // For every base-exponent combination to test 70 | 71 | if (base == 0 && exponent < 0) { 72 | // Disallow division by 0 73 | continue 74 | } 75 | 76 | if (base < 0 && exponent != Math.trunc(exponent)) { 77 | // Negative numbers to fractional powers is not allowed 78 | continue 79 | } 80 | 81 | let truth = Math.pow(base, exponent) 82 | 83 | if (truth > Math.pow(2, 87)) { 84 | // Would be out of range 85 | continue 86 | } 87 | 88 | let result = mv.fromReal(await instance.pow.call(mv.toReal(base), mv.toReal(exponent))) 89 | 90 | // Make sure we get the right answer. 91 | // Make sure to give more slack for really big numbers. 92 | // TODO: Make this more accurate too somehow? 93 | assert.approximately(result, truth, 94 | Math.max(Math.abs(truth / 10000), 1E-8), 95 | "pow of " + base + "^" + exponent + " should be approximately right") 96 | } 97 | } 98 | }) 99 | 100 | it("should compute sqrt", async function() { 101 | let instance = await RealMath.deployed() 102 | 103 | for (let arg of [0, 0.5, 1, 1.61234, 25, 36, 458344 * 458344]) { 104 | let truth = Math.sqrt(arg) 105 | 106 | let result = mv.fromReal(await instance.sqrt.call(mv.toReal(arg))) 107 | 108 | // Make sure we get the right answer. 109 | // Make sure to give more slack for really big numbers. 110 | // TODO: Make this more accurate too somehow? 111 | assert.approximately(result, truth, 112 | Math.max(Math.abs(truth / 10000), 1E-8), 113 | "square root of " + arg + " should be approximately right") 114 | } 115 | }) 116 | 117 | it("should compute sin", async function() { 118 | let instance = await RealMath.deployed() 119 | 120 | for (let arg of [0.5, 0, -0.5, Math.PI, 2 * Math.PI, -2 * Math.PI, -1 * Math.PI, 1000, -999.3]) { 121 | let truth = Math.sin(arg) 122 | let result = mv.fromReal(await instance.sin.call(mv.toReal(arg))) 123 | 124 | // Make sure we get the right answer. 125 | assert.approximately(result, truth, 4E-11, 126 | "sin of " + arg + " should be approximately right") 127 | } 128 | }) 129 | 130 | it("should compute cos", async function() { 131 | let instance = await RealMath.deployed() 132 | 133 | for (let arg of [0.5, 0, -0.5, Math.PI, 2 * Math.PI, -2 * Math.PI, -1 * Math.PI, 1000, -999.3]) { 134 | let truth = Math.cos(arg) 135 | let result = mv.fromReal(await instance.cos.call(mv.toReal(arg))) 136 | 137 | // Make sure we get the right answer. 138 | assert.approximately(result, truth, 4E-11, 139 | "cos of " + arg + " should be approximately right") 140 | } 141 | }) 142 | 143 | it("should compute tan", async function() { 144 | let instance = await RealMath.deployed() 145 | 146 | for (let arg of [0.5, 0.0001, -0.5, Math.PI, 2 * Math.PI + 1E-5, -2 * Math.PI - 1E-6, -1 * Math.PI, 1000, -999.3]) { 147 | let truth = Math.tan(arg) 148 | let result = mv.fromReal(await instance.tan.call(mv.toReal(arg))) 149 | 150 | // Make sure we get the right answer. 151 | assert.approximately(result, truth, 2E-10, 152 | "tan of " + arg + " should be approximately right") 153 | } 154 | }) 155 | 156 | it("should compute atan for small numbers", async function() { 157 | let instance = await RealMath.deployed() 158 | let truth = Math.atan(0.5) 159 | let result = mv.fromReal(await instance.atanSmall.call(mv.toReal(0.5))) 160 | assert.approximately(result, truth, 2E-6, 161 | "atan of 0.5 should be approximately right") 162 | // TODO: this needs to be WAY more accurate! 163 | }) 164 | 165 | it("should compute atan2", async function() { 166 | let instance = await RealMath.deployed() 167 | 168 | for (let y of [0, 5, 0.01, 1, 10, -1, -10]) { 169 | for (let x of [0, 2, 0.01, 1, 10, -1, -10]) { 170 | 171 | if (x == 0 && y == 0) { 172 | // Not valid inputs 173 | continue; 174 | } 175 | 176 | let truth = Math.atan2(y, x) 177 | let result = mv.fromReal(await instance.atan2.call(mv.toReal(y), mv.toReal(x))) 178 | 179 | // Make sure we get the right answer. 180 | assert.approximately(result, truth, 2E-6, 181 | "atan2 of " + y + " and " + x + " should be approximately right") 182 | } 183 | } 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | // We have a rinkeby-infura network, for which we try and find some secret Infura credentials in .env 2 | // We don't use the Infura "secret" (passed as basic auth user), just the ID (which we keep secret) 3 | // You can get your own from Infura by signing up with them, or use a different node to deploy 4 | require('dotenv').config() 5 | 6 | // We want to be able to deploy from keys in keystore files 7 | const KeystoreProvider = require('truffle-keystore-provider') 8 | 9 | // We need memoization or we will be prompted for a password every time. 10 | // See https://github.com/yondonfu/truffle-keystore-provider 11 | const memoizeKeystoreProviderCreator = () => { 12 | let providers = {} 13 | 14 | // KEYSTORE_DIR must have a ./keystore under it. 15 | // KEYSTORE_NAME is a file under that keystore directory and must be lowercase (supposed to be an address) 16 | 17 | return (network, account, dataDir, providerUrl) => { 18 | let key = JSON.stringify([dataDir, account, providerUrl]) 19 | console.log('Get provider ' + network + ' with key: ' + key) 20 | if (key in providers) { 21 | return providers[key] 22 | } else { 23 | const provider = new KeystoreProvider(account, dataDir, providerUrl) 24 | providers[key] = provider 25 | return provider 26 | } 27 | } 28 | } 29 | 30 | const createKeystoreProvider = memoizeKeystoreProviderCreator() 31 | 32 | 33 | 34 | // We need to get env vars somewhere where our provider getter can see them 35 | const env = process.env 36 | 37 | module.exports = { 38 | networks: { 39 | // Development network is now all in-memory in Truffle 4. 40 | truffle: { 41 | host: "localhost", 42 | port: 9545, 43 | network_id: "4447", 44 | gas: 8000000, 45 | gasPrice: 4000000000, 46 | timeoutBlocks: 1000 47 | }, 48 | live: { 49 | network_id: 1, 50 | host: "localhost", 51 | port: 8546, // Different than the default 52 | from: "0x368651F6c2b3a7174ac30A5A062b65F2342Fb6F1", 53 | gas: 8000000, // Knock down because it has to be less than block gas limit 54 | gasPrice: 20000000000, // 100 Gwei = 100000000000, 10 Gwei = 10000000000 55 | timeoutBlocks: 1000 56 | }, 57 | live_local: { 58 | network_id: 1, 59 | host: "localhost", // Ignored 60 | provider: () => { return createKeystoreProvider('live_local', env['LIVE_KEYSTORE_NAME'], env['KEYSTORE_DIR'], 'http://localhost:8545/') }, 61 | gas: 8000000, // Knock down because it has to be less than block gas limit 62 | gasPrice: 20000000000, 63 | timeoutBlocks: 1000 64 | }, 65 | rinkeby_local: { 66 | network_id: 4, 67 | host: "localhost", // Ignored 68 | provider: () => { return createKeystoreProvider('rinkeby_local', env['RINKEBY_KEYSTORE_NAME'], env['KEYSTORE_DIR'], 'http://localhost:8546/') }, 69 | gas: 8000000, // Knock down because it has to be less than block gas limit 70 | gasPrice: 4000000000, 71 | timeoutBlocks: 1000 72 | }, 73 | rinkeby_infura: { 74 | network_id: 4, 75 | host: "localhost", // Ignored 76 | provider: () => { return createKeystoreProvider('rinkeby_infura', env['RINKEBY_KEYSTORE_NAME'], env['KEYSTORE_DIR'], 'https://rinkeby.infura.io/v3/' + env['INFURA_PROJECT']) }, 77 | gas: 8000000, // Knock down because it has to be less than block gas limit 78 | gasPrice: 4000000000, 79 | timeoutBlocks: 1000 80 | }, 81 | live_infura: { 82 | network_id: 1, 83 | host: "localhost", // Ignored 84 | provider: () => { return createKeystoreProvider('live_infura', env['LIVE_KEYSTORE_NAME'], env['KEYSTORE_DIR'], 'https://mainnet.infura.io/v3/' + env['INFURA_PROJECT']) }, 85 | gas: 8000000, // Knock down because it has to be less than block gas limit 86 | gasPrice: 100000000000, 87 | timeoutBlocks: 1000 88 | }, 89 | ganacheFork: { 90 | network_id: 1, 91 | host: "127.0.0.1", 92 | port: 8549 93 | }, 94 | ganacheLiveFork: { 95 | network_id: 1, 96 | host: "127.0.0.1", 97 | port: 8549, 98 | provider: () => { return createKeystoreProvider('ganacheLiveFork', env['LIVE_KEYSTORE_NAME'], env['KEYSTORE_DIR'], 'http://localhost:8549/') } 99 | } 100 | }, 101 | mocha: { 102 | reporter: 'eth-gas-reporter', 103 | reporterOptions : { 104 | currency: 'USD' 105 | } 106 | }, 107 | plugins: ['truffle-plugin-verify'], 108 | verify: { 109 | preamble: "SPDX-License-Identifier: UNLICENSED\nSee https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2a0f2a8ba807b41360e7e092c3d5bb1bfbeb8b50/LICENSE and https://github.com/NovakDistributed/macroverse/blob/eea161aff5dba9d21204681a3b0f5dbe1347e54b/LICENSE" 110 | }, 111 | api_keys: { 112 | // Verification is "Powered by Etherscan.io APIs" 113 | etherscan: env['ETHERSCAN_API_KEY'] 114 | }, 115 | compilers: { 116 | solc: { 117 | version: '0.6.10', 118 | settings: { 119 | optimizer: { 120 | enabled: true, 121 | runs: 200 122 | } 123 | } 124 | } 125 | } 126 | }; 127 | --------------------------------------------------------------------------------