├── old-test ├── .gitkeep ├── SVGGenerator2Test.js ├── SVGGeneratorTest.js ├── keccak256.test.js ├── NFGasNames.test.js ├── Regenz.test.js ├── DoomRewarderTests.js ├── MonImageRegistryTests.js ├── MonMinterTests.js ├── NFGasTest.js ├── ERC721SenderTests.js └── ImageRegistryTests.js ├── .gitignore ├── coverage ├── sort-arrow-sprite.png ├── lcov-report │ ├── sort-arrow-sprite.png │ ├── prettify.css │ ├── index.html │ ├── contracts │ │ ├── UsesMon.sol.html │ │ ├── MonCreatorInstance.sol.html │ │ └── IMonMinter.sol.html │ ├── sorter.js │ └── base.css ├── prettify.css ├── index.html ├── contracts │ ├── UsesMon.sol.html │ ├── MonCreatorInstance.sol.html │ ├── IMonMinter.sol.html │ └── XMON.sol.html ├── sorter.js ├── base.css └── lcov.info ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── contracts ├── ISVGGenerator.sol ├── IWhitelist.sol ├── ILotteryReference.sol ├── IDoomAdmin.sol ├── Test20.sol ├── Test721.sol ├── IDistributor.sol ├── INFTLotteryPool.sol ├── Migrations.sol ├── ImageRegistry.sol ├── TestDistributor.sol ├── NFGasNames.sol ├── S3KS.sol ├── UsesMon.sol ├── ERC721Batcher.sol ├── MonCreatorInstance.sol ├── LinkTokenInterface.sol ├── IMonMinter.sol ├── VRFRequestIDBase.sol ├── ProtoCards0.sol ├── XMON.sol ├── NFTLocker.sol ├── DoomRewarder.sol ├── NFGas.sol ├── MonImageRegistry.sol ├── NFTLotteryPoolFactory.sol ├── ERC721Sender.sol ├── SVGGenerator2.sol ├── SVGGenerator1.sol ├── NFTLottery.sol ├── SafeMathChainlink.sol ├── MonSpawner.sol ├── MonMinter.sol ├── NFTLotteryPool.sol ├── MultiAssetSwapper.sol ├── MonStaker2.sol ├── MonStaker.sol └── MonStaker3.sol ├── helpers └── timeTravel.js └── README.md /old-test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | truffle-config.js 2 | node_modules 3 | build/ -------------------------------------------------------------------------------- /coverage/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xmons/0xmons-contracts-new/HEAD/coverage/sort-arrow-sprite.png -------------------------------------------------------------------------------- /coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xmons/0xmons-contracts-new/HEAD/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | // deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/ISVGGenerator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface ISVGGenerator { 6 | 7 | function createSVG(uint256 id, uint256 gas) external returns (string memory); 8 | } -------------------------------------------------------------------------------- /contracts/IWhitelist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | interface IWhitelist { 7 | function whitelist(address a) external returns (bool); 8 | } -------------------------------------------------------------------------------- /contracts/ILotteryReference.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface ILotteryReference { 6 | function LINK_ADDRESS() external returns(address); 7 | function RNG_DISTRIBUTOR_ADDRESS() external returns(address); 8 | function LINK_FEE() external returns(uint256); 9 | } -------------------------------------------------------------------------------- /old-test/SVGGenerator2Test.js: -------------------------------------------------------------------------------- 1 | const svgArtifact = artifacts.require('./SVGGenerator2.sol'); 2 | 3 | contract("SVG tests", async accounts => { 4 | it ("returns the right thing", async() => { 5 | let svg = await svgArtifact.deployed(); 6 | let result = await svg.createSVG(24, 130); 7 | console.log(result); 8 | }); 9 | }); -------------------------------------------------------------------------------- /old-test/SVGGeneratorTest.js: -------------------------------------------------------------------------------- 1 | const svgArtifact = artifacts.require('./SVGGenerator1.sol'); 2 | 3 | contract("SVG tests", async accounts => { 4 | it ("returns the right thing", async() => { 5 | let svg = await svgArtifact.deployed(); 6 | let result = await svg.createSVG(24, 1); 7 | console.log(result); 8 | }); 9 | }); -------------------------------------------------------------------------------- /contracts/IDoomAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface IDoomAdmin { 6 | function pendingDoom(address a) external view returns(uint256); 7 | function doomBalances(address a) external returns (uint256); 8 | function setDoomBalances(address a, uint256 d) external; 9 | } -------------------------------------------------------------------------------- /contracts/Test20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract Test20 is ERC20 { 8 | 9 | constructor() ERC20("Test", "TEST") public {} 10 | 11 | function mint(address a, uint256 amount) public { 12 | _mint(a, amount); 13 | } 14 | } -------------------------------------------------------------------------------- /contracts/Test721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 4 | 5 | contract Test721 is ERC721 { 6 | 7 | constructor() ERC721("Test721", "T721") public {} 8 | 9 | function mint(address to) public { 10 | _mint(to, totalSupply()+1); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/IDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface IDistributor { 6 | function distributeToNftHolders( 7 | uint256 fee, 8 | address _nftRecipientAddress, 9 | uint256 startIndex, 10 | uint256 endIndex, 11 | address _rewardAddress, 12 | uint256 _rewardId 13 | ) external; 14 | } -------------------------------------------------------------------------------- /old-test/keccak256.test.js: -------------------------------------------------------------------------------- 1 | // It checks permissions properly 2 | contract("keccak256 tests", async accounts => { 3 | it ("handles keccak256 correctly", async() => { 4 | for (let i = 240; i < 340; i++) { 5 | let ans = web3.utils.toBN(web3.utils.keccak256(i.toString())); 6 | ans = ans.mod(web3.utils.toBN(1025)); 7 | console.log(i, ans.toString()); 8 | } 9 | }); 10 | }); -------------------------------------------------------------------------------- /contracts/INFTLotteryPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface INFTLotteryPool { 6 | function initialize( 7 | address _prizeAddress, 8 | uint256 _prizeId, 9 | uint64 _startDate, 10 | uint64 _endDate, 11 | uint32 _minTicketsToSell, 12 | uint32 _maxTickets, 13 | uint32 _maxTicketsPerAddress, 14 | uint256 _ticketPrice 15 | ) external; 16 | 17 | function transferOwnership(address newOwner) external; 18 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.8.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/ImageRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | 4 | // import "@openzeppelin/contracts/access/Ownable.sol"; 5 | // import "@openzeppelin/contracts/math/SafeMath.sol"; 6 | 7 | contract ImageRegistry { 8 | 9 | event BytesE(bytes b); 10 | event UintsE(uint256[] a); 11 | 12 | function saveBytes(bytes calldata b) external { 13 | } 14 | 15 | function saveBytesE(bytes calldata b) external { 16 | emit BytesE(b); 17 | } 18 | 19 | function saveUints(uint256[] calldata d) external { 20 | } 21 | 22 | function saveUintsE(uint256[] calldata d) external { 23 | emit UintsE(d); 24 | } 25 | } -------------------------------------------------------------------------------- /contracts/TestDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | contract TestDistributor { 8 | 9 | address public recipient; 10 | 11 | function setRecipient(address a) public { 12 | recipient = a; 13 | } 14 | 15 | function distributeToNftHolders( 16 | uint256 fee, 17 | address _nftRecipientAddress, 18 | uint256 startIndex, 19 | uint256 endIndex, 20 | address _rewardAddress, 21 | uint256 _rewardId) external { 22 | IERC721(_rewardAddress).safeTransferFrom(msg.sender, recipient, _rewardId); 23 | } 24 | } -------------------------------------------------------------------------------- /coverage/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /contracts/NFGasNames.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | contract NFGasNames { 8 | 9 | uint256 public test1; 10 | bytes32 public test2; 11 | 12 | mapping(uint256 => string) public names; 13 | IERC721 public nfgas; 14 | 15 | constructor(address a) public { 16 | nfgas = IERC721(a); 17 | } 18 | 19 | function setName(uint256 id, string memory name) public { 20 | require(nfgas.ownerOf(id) == msg.sender, "Not owner"); 21 | uint256 hashValue = uint(keccak256(abi.encodePacked(name))) % 1025; 22 | require(hashValue == id, "hashValue incorrect"); 23 | names[id] = name; 24 | } 25 | } -------------------------------------------------------------------------------- /old-test/NFGasNames.test.js: -------------------------------------------------------------------------------- 1 | const namesArtifact = artifacts.require('./NFGasNames.sol'); 2 | const test721Artifact = artifacts.require('./Test721.sol'); 3 | 4 | // It checks permissions properly 5 | contract("NFGasNames tests", async accounts => { 6 | it ("handles keccak256 correctly", async() => { 7 | let test721 = await test721Artifact.deployed(); 8 | let names = await namesArtifact.deployed(); 9 | 10 | for (let i = 0; i < 12; i++) { 11 | await test721.mint(accounts[0]); 12 | } 13 | 14 | await names.setName(12, "6"); 15 | 16 | // for (let i = 0; i < 40; i++) { 17 | // let ans = web3.utils.toBN(web3.utils.keccak256(i.toString())); 18 | // console.log(i, ans.mod(web3.utils.toBN(1025)).toString()); 19 | // } 20 | }); 21 | }); -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const Test721Artifact = artifacts.require('./Test721.sol'); 2 | const TestDistributorArtifact = artifacts.require('./TestDistributor.sol'); 3 | const Test20Artifact = artifacts.require('./Test20.sol'); 4 | const NFTLotteryPoolArtifact = artifacts.require('./NFTLotteryPool.sol'); 5 | const NFTLotteryPoolFactoryArtifact = artifacts.require('./NFTLotteryPoolFactory.sol'); 6 | 7 | module.exports = async(deployer) => { 8 | await deployer.deploy(Test721Artifact); 9 | await deployer.deploy(Test20Artifact); 10 | await deployer.deploy(TestDistributorArtifact); 11 | await deployer.deploy(NFTLotteryPoolArtifact); 12 | await deployer.deploy( 13 | NFTLotteryPoolFactoryArtifact, 14 | Test20Artifact.address, 15 | TestDistributorArtifact.address, 16 | web3.utils.toWei('2', 'ether'), 17 | NFTLotteryPoolArtifact.address); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/S3KS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | 8 | contract S3KS is ERC20 { 9 | 10 | using SafeERC20 for IERC20; 11 | 12 | IERC20 public socks; 13 | IERC20 public sacks; 14 | IERC20 public sake; 15 | 16 | constructor() ERC20("S3KS", "S3KS") public { 17 | socks = IERC20(0x23B608675a2B2fB1890d3ABBd85c5775c51691d5); 18 | sacks = IERC20(0xa6610Ed604047e7B76C1DA288172D15BcdA57596); 19 | sake = IERC20(0xe9F84dE264E91529aF07Fa2C746e934397810334); 20 | } 21 | 22 | function getS3ks(uint256 amount) public { 23 | socks.safeTransferFrom(msg.sender, address(this), amount); 24 | sacks.safeTransferFrom(msg.sender, address(this), amount); 25 | sake.safeTransferFrom(msg.sender, address(this), amount); 26 | _mint(msg.sender, amount); 27 | } 28 | } -------------------------------------------------------------------------------- /contracts/UsesMon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | interface UsesMon { 6 | struct Mon { 7 | // the original address this monster went to 8 | address summoner; 9 | 10 | // the unique ID associated with parent 1 of this monster 11 | uint256 parent1Id; 12 | 13 | // the unique ID associated with parent 2 of this monster 14 | uint256 parent2Id; 15 | 16 | // the address of the contract that minted this monster 17 | address minterContract; 18 | 19 | // the id of this monster within its specific contract 20 | uint256 contractOrder; 21 | 22 | // the generation of this monster 23 | uint256 gen; 24 | 25 | // used to calculate statistics and other things 26 | uint256 bits; 27 | 28 | // tracks the experience of this monster 29 | uint256 exp; 30 | 31 | // the monster's rarity 32 | uint256 rarity; 33 | } 34 | } -------------------------------------------------------------------------------- /contracts/ERC721Batcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | contract ERC721Batcher { 8 | 9 | function getURIs(address erc721Address, address user) public view returns(string[] memory) { 10 | ERC721 nft = ERC721(erc721Address); 11 | uint256 numTokens = nft.balanceOf(user); 12 | string[] memory uriList = new string[](numTokens); 13 | for (uint256 i; i < numTokens; i++) { 14 | uriList[i] = nft.tokenURI(nft.tokenOfOwnerByIndex(user, i)); 15 | } 16 | return(uriList); 17 | } 18 | 19 | function getIds(address erc721Address, address user) public view returns(uint256[] memory) { 20 | ERC721 nft = ERC721(erc721Address); 21 | uint256 numTokens = nft.balanceOf(user); 22 | uint256[] memory uriList = new uint256[](numTokens); 23 | for (uint256 i; i < numTokens; i++) { 24 | uriList[i] = nft.tokenOfOwnerByIndex(user, i); 25 | } 26 | return(uriList); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /contracts/MonCreatorInstance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "./IMonMinter.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 8 | 9 | abstract contract MonCreatorInstance is AccessControl { 10 | 11 | using SafeERC20 for IERC20; 12 | using SafeMath for uint256; 13 | 14 | IERC20 public xmon; 15 | IMonMinter public monMinter; 16 | 17 | uint256 public maxMons; 18 | uint256 public numMons; 19 | 20 | // to be appended before the URI for the NFT 21 | string public prefixURI; 22 | 23 | modifier onlyAdmin { 24 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin"); 25 | _; 26 | } 27 | 28 | function updateNumMons() internal { 29 | require(numMons < maxMons, "All mons are out"); 30 | numMons = numMons.add(1); 31 | } 32 | 33 | function setMaxMons(uint256 m) public onlyAdmin { 34 | maxMons = m; 35 | } 36 | 37 | function setPrefixURI(string memory prefix) public onlyAdmin { 38 | prefixURI = prefix; 39 | } 40 | } -------------------------------------------------------------------------------- /helpers/timeTravel.js: -------------------------------------------------------------------------------- 1 | advanceTimeAndBlock = async (time) => { 2 | await advanceTime(time); 3 | await advanceBlock(); 4 | 5 | return Promise.resolve(web3.eth.getBlock('latest')); 6 | } 7 | 8 | advanceTime = (time) => { 9 | return new Promise((resolve, reject) => { 10 | web3.currentProvider.send({ 11 | jsonrpc: "2.0", 12 | method: "evm_increaseTime", 13 | params: [time], 14 | id: new Date().getTime() 15 | }, (err, result) => { 16 | if (err) { return reject(err); } 17 | return resolve(result); 18 | }); 19 | }); 20 | } 21 | 22 | advanceBlock = () => { 23 | return new Promise((resolve, reject) => { 24 | web3.currentProvider.send({ 25 | jsonrpc: "2.0", 26 | method: "evm_mine", 27 | id: new Date().getTime() 28 | }, (err, result) => { 29 | if (err) { return reject(err); } 30 | const newBlockHash = web3.eth.getBlock('latest').hash; 31 | 32 | return resolve(newBlockHash) 33 | }); 34 | }); 35 | } 36 | 37 | module.exports = { 38 | advanceTime, 39 | advanceBlock, 40 | advanceTimeAndBlock 41 | } -------------------------------------------------------------------------------- /contracts/LinkTokenInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | interface LinkTokenInterface { 5 | function allowance(address owner, address spender) external view returns (uint256 remaining); 6 | function approve(address spender, uint256 value) external returns (bool success); 7 | function balanceOf(address owner) external view returns (uint256 balance); 8 | function decimals() external view returns (uint8 decimalPlaces); 9 | function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); 10 | function increaseApproval(address spender, uint256 subtractedValue) external; 11 | function name() external view returns (string memory tokenName); 12 | function symbol() external view returns (string memory tokenSymbol); 13 | function totalSupply() external view returns (uint256 totalTokensIssued); 14 | function transfer(address to, uint256 value) external returns (bool success); 15 | function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); 16 | function transferFrom(address from, address to, uint256 value) external returns (bool success); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/IMonMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | import "./UsesMon.sol"; 8 | 9 | interface IMonMinter is IERC721, UsesMon { 10 | 11 | function mintMonster(address to, 12 | uint256 parent1Id, 13 | uint256 parent2Id, 14 | address minterContract, 15 | uint256 contractOrder, 16 | uint256 gen, 17 | uint256 bits, 18 | uint256 exp, 19 | uint256 rarity 20 | ) external returns (uint256); 21 | 22 | function modifyMon(uint256 id, 23 | bool ignoreZeros, 24 | uint256 parent1Id, 25 | uint256 parent2Id, 26 | address minterContract, 27 | uint256 contractOrder, 28 | uint256 gen, 29 | uint256 bits, 30 | uint256 exp, 31 | uint256 rarity 32 | ) external; 33 | 34 | function monRecords(uint256 id) external returns (Mon memory); 35 | 36 | function setTokenURI(uint256 id, string calldata uri) external; 37 | } -------------------------------------------------------------------------------- /old-test/Regenz.test.js: -------------------------------------------------------------------------------- 1 | const truffleAssert = require('truffle-assertions'); 2 | const RegenzArtifact = artifacts.require('./Regenz.sol'); 3 | 4 | contract("Simple tests", async accounts => { 5 | 6 | let Regenz; 7 | 8 | beforeEach(async() => { 9 | Regenz = await RegenzArtifact.deployed(); 10 | }); 11 | 12 | it ("allows owners to reserve", async() => { 13 | await Regenz.reserveRegenz(10, {from: accounts[0]}); 14 | expect(await Regenz.balanceOf(accounts[0])).to.eql(web3.utils.toBN(10)); 15 | }); 16 | 17 | it ("fails if someone else tries to reserve", async() => { 18 | await truffleAssert.reverts( 19 | Regenz.reserveRegenz(10, {from: accounts[1]}) 20 | ); 21 | }); 22 | 23 | it ("fails if done during the sale being active", async() => { 24 | await Regenz.flipSaleState({from: accounts[0]}); 25 | await truffleAssert.reverts( 26 | Regenz.reserveRegenz(10, {from: accounts[0]}), 27 | "Cannot reserve during sale" 28 | ); 29 | }); 30 | 31 | it ("only allows owner to toggle sale active", async() => { 32 | await truffleAssert.reverts( 33 | Regenz.flipSaleState({from: accounts[1]}) 34 | ) 35 | }); 36 | 37 | // fails if eth sent isn't enough 38 | // fails if sale is done 39 | // fails if buying too many 40 | // fails if buying exceeds supply 41 | // allows buying up to total supply 42 | // allows claiming eth 43 | 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 0xmons Smart Contracts 2 | 3 | ## NOW: 4 | 5 | This repository is used as a monorepo for all smart contracts developed by me (0xmons). It previously only housed contracts used for the initial 0xmons NFT ecosystem, which is detailed below. 6 | 7 | ## PREVIOUSLY: 8 | 9 | ### Architecture 10 | The main contract is `MonMinter.sol`, which is the actual ERC-721 implementation. Using AccessControl, we can assign different contracts to be monster minters. 11 | 12 | `IMonMinter.sol` is the minter interface, which is exposed to contracts with minting privileges, allowing them to mint and modify the on-chain data of monsters. 13 | 14 | The default `Mon` struct is in `UsesMon.sol`. Contracts which want to access on-chain information about a monster inherit from this contract to use the struct. 15 | 16 | `MonCreatorInstance` is a parent class that abstracts out some similarities among contracts which want to mint monsters. 17 | 18 | `MonStaker.sol` is the main staking contract. Users can add and remove their staked XMON to generate Doom, which is then used to redeem monsters. 19 | 20 | `MonSpawner.sol` is the spawning contract, which takes in two monsters and spawns a new monster. 21 | 22 | The underlying token is `XMON.sol`, a token with a fee on transfer, settable from 1 to 10%. 23 | 24 | ### Tests 25 | There is a comprehensive set of tests in `test/tests.js`. Test coverage is close to 100%, and the comments explain the cases being tested for. See `coverage/`. 26 | -------------------------------------------------------------------------------- /contracts/VRFRequestIDBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | contract VRFRequestIDBase { 5 | 6 | /** 7 | * @notice returns the seed which is actually input to the VRF coordinator 8 | * 9 | * @dev To prevent repetition of VRF output due to repetition of the 10 | * @dev user-supplied seed, that seed is combined in a hash with the 11 | * @dev user-specific nonce, and the address of the consuming contract. The 12 | * @dev risk of repetition is mostly mitigated by inclusion of a blockhash in 13 | * @dev the final seed, but the nonce does protect against repetition in 14 | * @dev requests which are included in a single block. 15 | * 16 | * @param _userSeed VRF seed input provided by user 17 | * @param _requester Address of the requesting contract 18 | * @param _nonce User-specific nonce at the time of the request 19 | */ 20 | function makeVRFInputSeed(bytes32 _keyHash, uint256 _userSeed, 21 | address _requester, uint256 _nonce) 22 | internal pure returns (uint256) 23 | { 24 | return uint256(keccak256(abi.encode(_keyHash, _userSeed, _requester, _nonce))); 25 | } 26 | 27 | /** 28 | * @notice Returns the id for this request 29 | * @param _keyHash The serviceAgreement ID to be used for this request 30 | * @param _vRFInputSeed The seed to be passed directly to the VRF 31 | * @return The id for this request 32 | * 33 | * @dev Note that _vRFInputSeed is not the seed passed by the consuming 34 | * @dev contract, but the one generated by makeVRFInputSeed 35 | */ 36 | function makeRequestId( 37 | bytes32 _keyHash, uint256 _vRFInputSeed) internal pure returns (bytes32) { 38 | return keccak256(abi.encodePacked(_keyHash, _vRFInputSeed)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/ProtoCards0.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts/access/Ownable.sol"; 9 | 10 | contract ProtoCards0 is ERC721, Ownable { 11 | 12 | IERC721Enumerable public xmonNFT; 13 | 14 | // min and max indices, inclusive 15 | uint256 public min; 16 | uint256 public max; 17 | 18 | // xmon fee 19 | IERC20 public xmon; 20 | uint256 public xmonFee; 21 | 22 | constructor() ERC721("ProtoCards0", "PROTO") public { 23 | min = 5; 24 | max = 132; 25 | xmon = IERC20(0x3aaDA3e213aBf8529606924d8D1c55CbDc70Bf74); 26 | xmonNFT = IERC721Enumerable(0x0427743DF720801825a5c82e0582B1E915E0F750); 27 | xmonFee = 0.1 ether; 28 | } 29 | 30 | function batchMint(uint256[] calldata ids) external { 31 | for (uint256 i = 0; i < ids.length; i++) { 32 | mintCard(ids[i]); 33 | } 34 | } 35 | 36 | // mints a card NFT if the owner has the corresponding xmon NFT 37 | function mintCard(uint256 id) public { 38 | require(xmonNFT.ownerOf(id) == msg.sender, "Not owner"); 39 | require(id <= max, "Too high"); 40 | require(id >= min, "Too low"); 41 | xmon.transferFrom(msg.sender, address(this), xmonFee); 42 | _mint(msg.sender, id); 43 | } 44 | 45 | // Modifies the tokenURI of a monster 46 | function setTokenURI(uint256 id, string memory uri) public onlyOwner { 47 | _setTokenURI(id, uri); 48 | } 49 | 50 | // Sets the base URI 51 | function setBaseURI(string memory uri) public onlyOwner { 52 | _setBaseURI(uri); 53 | } 54 | 55 | function setXmon(address a) public onlyOwner { 56 | xmon = IERC20(a); 57 | } 58 | 59 | function setXmonFee(uint256 f) public onlyOwner { 60 | xmonFee = f; 61 | } 62 | 63 | function setXmonNFT(address a) public onlyOwner { 64 | xmonNFT = IERC721Enumerable(a); 65 | } 66 | 67 | // Rescues tokens locked in the contract 68 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyOwner { 69 | IERC20 _token = IERC20(tokenAddress); 70 | _token.transfer(to, numTokens); 71 | } 72 | } -------------------------------------------------------------------------------- /contracts/XMON.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.1; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 9 | 10 | contract XMON is ERC20, Ownable { 11 | 12 | using SafeERC20 for IERC20; 13 | 14 | uint256 public transferFee; 15 | mapping (address => bool) public whitelist; 16 | 17 | constructor() ERC20("XMON", "XMON") public { 18 | _mint(msg.sender, 10000 ether); 19 | transferFee = 0; 20 | } 21 | 22 | function setWhitelist(address a, bool b) public onlyOwner { 23 | whitelist[a] = b; 24 | } 25 | 26 | // Transfer fee in integer percents 27 | function setTransferFee(uint256 fee) public onlyOwner { 28 | require(fee <= 10, "Fee cannot be greater than 10%"); 29 | transferFee = fee; 30 | } 31 | 32 | // Transfer recipient recives amount - fee 33 | function transfer(address recipient, uint256 amount) public override returns (bool) { 34 | if (whitelist[_msgSender()] == false) { 35 | uint256 fee = transferFee.mul(amount).div(100); 36 | uint amountLessFee = amount.sub(fee); 37 | _transfer(_msgSender(), recipient, amountLessFee); 38 | _transfer(_msgSender(), address(this), fee); 39 | } else { 40 | _transfer(_msgSender(), recipient, amount); 41 | } 42 | return true; 43 | } 44 | 45 | // TransferFrom recipient receives amount - fee, sender's account is debited amount 46 | function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { 47 | if (whitelist[recipient] == false) { 48 | uint256 fee = transferFee.mul(amount).div(100); 49 | uint amountLessFee = amount.sub(fee); 50 | amount = amountLessFee; 51 | _transfer(sender, address(this), fee); 52 | } 53 | _transfer(sender, recipient, amount); 54 | _approve(sender, _msgSender(), allowance(sender,_msgSender()).sub(amount, "ERC20: transfer amount exceeds allowance")); 55 | return true; 56 | } 57 | 58 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyOwner { 59 | IERC20 _token = IERC20(tokenAddress); 60 | _token.safeTransfer(to, numTokens); 61 | } 62 | } -------------------------------------------------------------------------------- /contracts/NFTLocker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721Holder.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | import "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol"; 8 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 9 | 10 | contract NFTLocker is ERC721Holder, ERC1155Holder { 11 | 12 | struct ERC721Lockup { 13 | address owner; 14 | uint64 endTime; 15 | address nftAddress; 16 | uint256 nftId; 17 | } 18 | 19 | struct ERC1155Lockup { 20 | address owner; 21 | uint64 endTime; 22 | address nftAddress; 23 | uint256 nftId; 24 | uint256 amount; 25 | } 26 | 27 | uint256 public counterFor721; 28 | uint256 public counterFor1155; 29 | 30 | mapping(uint256 => ERC721Lockup) public erc721LockupMap; 31 | mapping(uint256 => ERC1155Lockup) public erc1155LockupMap; 32 | 33 | function create721Lockup(address nftAddress, uint64 endTime, uint256 nftId) public { 34 | counterFor721 += 1; 35 | erc721LockupMap[counterFor721] = ERC721Lockup( 36 | msg.sender, 37 | endTime, 38 | nftAddress, 39 | nftId 40 | ); 41 | IERC721(nftAddress).safeTransferFrom(msg.sender, address(this), nftId); 42 | } 43 | 44 | function create1155Lockup(address nftAddress, uint64 endTime, uint256 nftId, uint256 amount) public { 45 | counterFor1155 += 1; 46 | erc1155LockupMap[counterFor1155] = ERC1155Lockup( 47 | msg.sender, 48 | endTime, 49 | nftAddress, 50 | nftId, 51 | amount 52 | ); 53 | IERC1155(nftAddress).safeTransferFrom(msg.sender, address(this), nftId, amount, ""); 54 | } 55 | 56 | function unlock721(uint256 lockId) public { 57 | ERC721Lockup memory lockup = erc721LockupMap[lockId]; 58 | require(block.timestamp >= lockup.endTime, "Not time yet"); 59 | IERC721(lockup.nftAddress).safeTransferFrom(address(this), lockup.owner, lockup.nftId); 60 | delete erc721LockupMap[lockId]; 61 | } 62 | 63 | function unlock1155(uint256 lockId) public { 64 | ERC1155Lockup memory lockup = erc1155LockupMap[lockId]; 65 | require(block.timestamp >= lockup.endTime, "Not time yet"); 66 | IERC1155(lockup.nftAddress).safeTransferFrom(address(this), lockup.owner, lockup.nftId, lockup.amount, ""); 67 | delete erc1155LockupMap[lockId]; 68 | } 69 | } -------------------------------------------------------------------------------- /contracts/DoomRewarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/math/SafeMath.sol"; 6 | import "./IDoomAdmin.sol"; 7 | 8 | contract DoomRewarder is Ownable { 9 | 10 | using SafeMath for uint256; 11 | 12 | // reference to IDoomAdmin instance 13 | IDoomAdmin public doomAdmin; 14 | 15 | struct Bounty { 16 | bytes32 passwordHash; 17 | uint256 maxClaimers; 18 | uint256 doomAmount; 19 | } 20 | mapping(uint256 => Bounty) public bountyInfo; 21 | 22 | // current bounty id 23 | uint256 public currentBountyId; 24 | 25 | mapping(uint256 => mapping(address => bool)) public hasClaimed; 26 | mapping(uint256 => uint256) public numClaimers; 27 | mapping(uint256 => bool) public isInvalid; 28 | 29 | constructor(address a) public { 30 | doomAdmin = IDoomAdmin(a); 31 | } 32 | 33 | function addBounty(bytes32 passwordHash, uint256 doomAmount, uint256 maxClaimers) public onlyOwner { 34 | // Update bounty ID 35 | currentBountyId = currentBountyId.add(1); 36 | // Add new bounty to mapping 37 | bountyInfo[currentBountyId] = Bounty(passwordHash, maxClaimers, doomAmount); 38 | } 39 | 40 | function claimBounty(uint256 bountyID, string memory password) public { 41 | require(! isInvalid[bountyID], "now invalid"); 42 | require(!hasClaimed[bountyID][msg.sender], "already claimed"); 43 | Bounty memory currBounty = bountyInfo[bountyID]; 44 | require(numClaimers[bountyID] < currBounty.maxClaimers, "already out"); 45 | bytes32 attemptedHash = keccak256(abi.encodePacked(password)); 46 | if (attemptedHash == currBounty.passwordHash) { 47 | 48 | // Set account to be true 49 | hasClaimed[bountyID][msg.sender] = true; 50 | 51 | // Update number of bounty claimers 52 | numClaimers[bountyID] = numClaimers[bountyID].add(1); 53 | 54 | // Add DOOM to account 55 | _addDoom(currBounty.doomAmount); 56 | } 57 | } 58 | 59 | function _addDoom(uint256 amount) private { 60 | uint256 newBalance = doomAdmin.doomBalances(msg.sender).add(amount); 61 | doomAdmin.setDoomBalances(msg.sender, newBalance); 62 | } 63 | 64 | function setBountyInvalidity(uint256 id, bool b) public onlyOwner { 65 | isInvalid[id] = b; 66 | } 67 | 68 | function setIDoomAdmin(address a) public onlyOwner { 69 | doomAdmin = IDoomAdmin(a); 70 | } 71 | 72 | function yeet() public onlyOwner { 73 | selfdestruct(0x75d4bdBf6593ed463e9625694272a0FF9a6D346F); 74 | } 75 | } -------------------------------------------------------------------------------- /contracts/NFGas.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/ERC721Burnable.sol"; 7 | import "@openzeppelin/contracts/access/Ownable.sol"; 8 | import "./ISVGGenerator.sol"; 9 | 10 | contract NFGas is Ownable, ERC721Burnable { 11 | 12 | address payable public feeRecipient = 0x75d4bdBf6593ed463e9625694272a0FF9a6D346F; 13 | uint256 public feeAmount = 0.1 ether; 14 | uint256 public averageGasPrice; 15 | ISVGGenerator public generator; 16 | uint256[] public purchasedIds; 17 | event Purchased(uint256 id); 18 | 19 | // TODO: 20 | // metadata address (creates the SVG) [x] 21 | // metadata function that doesn't use URI [ ] 22 | // SVG creator contract [x] 23 | // set token URIs [ ] 24 | 25 | constructor(address a) ERC721("Non-Fungible Gas", "NFG") public { 26 | _mint(msg.sender, 0); 27 | generator = ISVGGenerator(a); 28 | averageGasPrice = 1; 29 | } 30 | 31 | modifier exactGas(uint256 id) { 32 | require(tx.gasprice == (id*(10**9)), "Exact gas"); 33 | _; 34 | } 35 | 36 | modifier updateAverageGasPrice() { 37 | averageGasPrice = (tx.gasprice + averageGasPrice)/2; 38 | _; 39 | } 40 | 41 | function mint(uint256 id) public payable exactGas(id) updateAverageGasPrice() { 42 | require(id <= 1024, "Too high"); 43 | require(msg.value == feeAmount, "Pay fee"); 44 | purchasedIds.push(id); 45 | feeRecipient.transfer(msg.value); 46 | _mint(msg.sender, id); 47 | emit Purchased(id); 48 | } 49 | 50 | function safeTransferFrom( 51 | address from, 52 | address to, 53 | uint256 id 54 | ) public override exactGas(id) updateAverageGasPrice() { 55 | super.safeTransferFrom(from, to, id); 56 | } 57 | 58 | function transferFrom( 59 | address from, 60 | address to, 61 | uint256 id 62 | ) public override exactGas(id) updateAverageGasPrice() { 63 | super.transferFrom(from, to, id); 64 | } 65 | 66 | function svg(uint256 id) public returns (string memory) { 67 | return generator.createSVG(id, averageGasPrice); 68 | } 69 | 70 | function setFeeRecipient(address payable a) public onlyOwner { 71 | feeRecipient = a; 72 | } 73 | 74 | function setGenerator(address a) public onlyOwner { 75 | generator = ISVGGenerator(a); 76 | } 77 | 78 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyOwner { 79 | IERC20 _token = IERC20(tokenAddress); 80 | _token.transfer(to, numTokens); 81 | } 82 | 83 | // Modifies the tokenURI of a monster 84 | function setTokenURI(uint256 id, string memory uri) public onlyOwner { 85 | _setTokenURI(id, uri); 86 | } 87 | 88 | // Sets the base URI 89 | function setBaseURI(string memory uri) public onlyOwner { 90 | _setBaseURI(uri); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/MonImageRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | 8 | contract MonImageRegistry is Ownable { 9 | 10 | event hashChanged(uint256 indexed id, bytes oldHash, bytes newHash); 11 | event dataChanged(uint256 indexed id); 12 | 13 | mapping(uint256 => bytes) public monDataWithAnimation; 14 | mapping (uint256 => bytes) public monDataWithAnimationHash; 15 | mapping(uint256 => bytes) public monDataWithStatic; 16 | mapping (uint256 => bytes) public monDataWithStaticHash; 17 | mapping(uint256 => bool) public isHashLocked; 18 | mapping(uint256 => bool) public isDataLocked; 19 | 20 | uint256 public fee; 21 | IERC721 public mon; 22 | IERC20 public xmonToken; 23 | 24 | constructor(address erc721Add, address tokenAdd) public { 25 | mon = IERC721(erc721Add); 26 | xmonToken = IERC20(tokenAdd); 27 | fee = 10**18; 28 | } 29 | 30 | function uploadMon(bytes calldata s) external {} 31 | 32 | function registerMon(uint256 id, bytes calldata txHash, bool isStatic) external { 33 | require((msg.sender == mon.ownerOf(id) || msg.sender == owner()), "Not owner/admin"); 34 | require(!isHashLocked[id], "locked"); 35 | if (isStatic) { 36 | emit hashChanged(id, monDataWithStaticHash[id], txHash); 37 | monDataWithStaticHash[id] = txHash; 38 | } 39 | else { 40 | emit hashChanged(id, monDataWithAnimationHash[id], txHash); 41 | monDataWithAnimationHash[id] = txHash; 42 | } 43 | xmonToken.transferFrom(msg.sender, address(this), fee); 44 | } 45 | 46 | function registerEntireMonData(uint256 id, bytes calldata data, bool isStatic) external { 47 | require((msg.sender == mon.ownerOf(id) || msg.sender == owner()), "Not owner/admin"); 48 | require(!isDataLocked[id], "locked"); 49 | if (isStatic) { 50 | monDataWithStatic[id] = data; 51 | } 52 | else { 53 | monDataWithAnimation[id] = data; 54 | } 55 | emit dataChanged(id); 56 | xmonToken.transferFrom(msg.sender, address(this), fee); 57 | } 58 | 59 | function setFee(uint256 f) external onlyOwner { 60 | fee = f; 61 | } 62 | 63 | function setHashLock(uint256 id, bool b) external onlyOwner { 64 | isHashLocked[id] = b; 65 | } 66 | 67 | function setDataLock(uint256 id, bool b) external onlyOwner { 68 | isDataLocked[id] = b; 69 | } 70 | 71 | function moveTokens(address tokenAddress, address to, uint256 numTokens) external onlyOwner { 72 | IERC20 _token = IERC20(tokenAddress); 73 | _token.transfer(to, numTokens); 74 | } 75 | 76 | function setXmonToken(address a) external onlyOwner { 77 | xmonToken = IERC20(a); 78 | } 79 | 80 | function setMon(address a) external onlyOwner { 81 | mon = IERC721(a); 82 | } 83 | } -------------------------------------------------------------------------------- /contracts/NFTLotteryPoolFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | import "./IDistributor.sol"; 8 | import {ClonesUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol"; 9 | import "@openzeppelin/contracts/access/Ownable.sol"; 10 | import "./INFTLotteryPool.sol"; 11 | 12 | contract NFTLotteryPoolFactory is Ownable { 13 | 14 | using SafeERC20 for IERC20; 15 | 16 | address immutable public LINK_ADDRESS; 17 | address immutable public RNG_DISTRIBUTOR_ADDRESS; 18 | uint256 immutable public LINK_FEE; 19 | 20 | uint256 public poolFee = 0.1 ether; 21 | address public template; 22 | address public masterTokenURI; 23 | 24 | event LotteryDeployed(address a, address deployer); 25 | 26 | constructor( 27 | address _RNG_DISTRIBUTOR_ADDRESS, 28 | address _LINK_ADDRESS, 29 | uint256 _LINK_FEE, 30 | address _template, 31 | address _masterTokenURI 32 | ) public { 33 | LINK_ADDRESS = _LINK_ADDRESS; 34 | RNG_DISTRIBUTOR_ADDRESS = _RNG_DISTRIBUTOR_ADDRESS; 35 | LINK_FEE = _LINK_FEE; 36 | template = _template; 37 | masterTokenURI = _masterTokenURI; 38 | } 39 | 40 | function createNFTLotteryPool( 41 | bytes32 salt, 42 | address _prizeAddress, 43 | uint256 _prizeId, 44 | uint64 _startDate, 45 | uint64 _endDate, 46 | uint32 _minTicketsToSell, 47 | uint32 _maxTickets, 48 | uint32 _maxTicketsPerAddress, 49 | uint256 _ticketPrice 50 | ) external payable returns (address) { 51 | require(msg.value >= poolFee, "Pay fee"); 52 | INFTLotteryPool pool = INFTLotteryPool(ClonesUpgradeable.cloneDeterministic(template, salt)); 53 | pool.initialize( 54 | _prizeAddress, 55 | _prizeId, 56 | _startDate, 57 | _endDate, 58 | _minTicketsToSell, 59 | _maxTickets, 60 | _maxTicketsPerAddress, 61 | _ticketPrice 62 | ); 63 | 64 | // Transfers ownership of pool to caller 65 | pool.transferOwnership(msg.sender); 66 | 67 | // Escrows the LINK and NFT prize 68 | IERC721(_prizeAddress).safeTransferFrom(msg.sender, address(pool), _prizeId); 69 | IERC20(LINK_ADDRESS).safeTransferFrom(msg.sender, address(pool), LINK_FEE); 70 | 71 | emit LotteryDeployed(address(pool), msg.sender); 72 | return address(pool); 73 | } 74 | 75 | function getLotteryAddress(bytes32 salt) public view returns (address) { 76 | return ClonesUpgradeable.predictDeterministicAddress(template, salt); 77 | } 78 | 79 | function updatePoolFee(uint256 f) public onlyOwner { 80 | poolFee = f; 81 | } 82 | 83 | function claimETH() public onlyOwner { 84 | owner().call{value: address(this).balance}(""); 85 | } 86 | 87 | function setMasterTokenURI(address a) public onlyOwner { 88 | masterTokenURI = a; 89 | } 90 | } -------------------------------------------------------------------------------- /contracts/ERC721Sender.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 7 | 8 | contract ERC721Sender { 9 | 10 | using SafeMath for uint256; 11 | using SafeERC20 for IERC20; 12 | 13 | // Mapping of ERC721 address to tokenID to ERC20 address to amounts 14 | mapping(address => mapping (uint256 => mapping (address => uint256))) public rewards; 15 | 16 | // Used to make future lookups faster 17 | mapping(address => bool) public isCached; 18 | mapping(address => IERC721Enumerable) public erc721Cache; 19 | mapping(address => IERC20) public erc20Cache; 20 | 21 | 22 | 23 | function setRewards(address[] memory erc721Addresses, 24 | uint256[] memory tokenIds, 25 | address erc20Address, 26 | uint256[] memory amounts) public { 27 | uint256 totalToTransfer = 0; 28 | if (! isCached[erc20Address]) { 29 | erc20Cache[erc20Address] = IERC20(erc20Address); 30 | isCached[erc20Address] = true; 31 | } 32 | for (uint256 i = 0; i < erc721Addresses.length; i += 1) { 33 | if (! isCached[erc721Addresses[i]]) { 34 | erc721Cache[erc721Addresses[i]] = IERC721Enumerable(erc721Addresses[i]); 35 | isCached[erc721Addresses[i]] = true; 36 | } 37 | uint256 previousRewardAmount = rewards[erc721Addresses[i]][tokenIds[i]][erc20Address]; 38 | rewards[erc721Addresses[i]][tokenIds[i]][erc20Address] = previousRewardAmount.add(amounts[i]); 39 | totalToTransfer = totalToTransfer.add(amounts[i]); 40 | } 41 | erc20Cache[erc20Address].safeTransferFrom(msg.sender, address(this), totalToTransfer); 42 | } 43 | 44 | 45 | 46 | function takeRewards(address erc721Address, uint256 tokenId, address erc20Address) public { 47 | if (! isCached[erc721Address]) { 48 | erc721Cache[erc721Address] = IERC721Enumerable(erc721Address); 49 | isCached[erc721Address] = true; 50 | } 51 | if (! isCached[erc20Address]) { 52 | erc20Cache[erc20Address] = IERC20(erc20Address); 53 | isCached[erc20Address] = true; 54 | } 55 | 56 | require(erc721Cache[erc721Address].ownerOf(tokenId) == msg.sender, "Not owner"); 57 | 58 | uint256 rewardAmount = rewards[erc721Address][tokenId][erc20Address]; 59 | delete rewards[erc721Address][tokenId][erc20Address]; 60 | 61 | erc20Cache[erc20Address].safeTransfer(msg.sender, rewardAmount); 62 | } 63 | 64 | 65 | 66 | function sendRewards(address[] memory erc721Addresses, 67 | uint256[] memory tokenIds, 68 | address erc20Address, 69 | uint256[] memory amounts) public { 70 | if (! isCached[erc20Address]) { 71 | erc20Cache[erc20Address] = IERC20(erc20Address); 72 | isCached[erc20Address] = true; 73 | } 74 | for (uint256 i = 0; i < erc721Addresses.length; i += 1) { 75 | if (! isCached[erc721Addresses[i]]) { 76 | erc721Cache[erc721Addresses[i]] = IERC721Enumerable(erc721Addresses[i]); 77 | isCached[erc721Addresses[i]] = true; 78 | } 79 | address erc721Holder = erc721Cache[erc721Addresses[i]].ownerOf(tokenIds[i]); 80 | erc20Cache[erc20Address].safeTransferFrom(msg.sender, erc721Holder, amounts[i]); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /contracts/SVGGenerator2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | contract SVGGenerator2 { 8 | 9 | using Strings for uint256; 10 | 11 | function createSVG(uint256 id, uint256 gasPrice) external pure returns (string memory) { 12 | gasPrice = gasPrice/(10**9); 13 | string memory color = getColor(gasPrice); 14 | string memory animationDuration = getSpeed(gasPrice); 15 | string memory svg = string(abi.encodePacked(' NFGas #', 32 | id.toString(), 33 | '', 34 | gasPrice.toString(), 35 | '')); 36 | return svg; 37 | } 38 | 39 | function getSpeed(uint256 gasPrice) internal pure returns(string memory) { 40 | if (gasPrice > 400) { 41 | return "0.2"; 42 | } 43 | if (gasPrice > 300) { 44 | return "0.3"; 45 | } 46 | if (gasPrice > 200) { 47 | return "0.4"; 48 | } 49 | if (gasPrice > 100) { 50 | return "0.8"; 51 | } 52 | if (gasPrice > 50) { 53 | return "1"; 54 | } 55 | else { 56 | return (51/(gasPrice+1)).toString(); 57 | } 58 | } 59 | 60 | function getColor(uint256 gasPrice) internal pure returns(string memory) { 61 | if (gasPrice <= 2) { 62 | return "rgb(220, 20, 60)"; 63 | } 64 | if (gasPrice <= 4) { 65 | return "rgb(230, 215, 140)"; 66 | } 67 | if (gasPrice <= 8) { 68 | return "rgb(120, 240, 20)"; 69 | } 70 | if (gasPrice <= 16) { 71 | return "rgb(20, 200, 209)"; 72 | } 73 | if (gasPrice <= 32) { 74 | return "rgb(30, 150, 220)"; 75 | } 76 | if (gasPrice <= 64) { 77 | return "rgb(120, 100, 220)"; 78 | } 79 | if (gasPrice <= 128) { 80 | return "rgb(220, 160, 220)"; 81 | } 82 | if (gasPrice <= 256) { 83 | return "rgb(250, 200, 200)"; 84 | } 85 | if (gasPrice <= 512) { 86 | return "rgb(120, 120, 120)"; 87 | } 88 | if (gasPrice <= 1000) { 89 | return "rgb(20, 40, 20)"; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /contracts/SVGGenerator1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | contract SVGGenerator1 { 8 | 9 | using Strings for uint256; 10 | uint256 private decimals = 1; 11 | uint256 private imageSize = 1024*(10**decimals); 12 | 13 | function createSVG(uint256 id, uint256 gasPrice) external view returns (string memory) { 14 | 15 | // number of columns/rows for the pattern, typically from 1-10 16 | uint256 numSides = _closestCube(id); 17 | 18 | // initial SVG string 19 | string memory svg = string(abi.encodePacked( 20 | '')); 22 | 23 | // the amount of space each unit takes up (padding included) 24 | uint256 unitSize = (1024*(10**decimals))/numSides; 25 | 26 | // get initial color based on gas price 27 | (uint256 r, uint256 g, uint256 b) = getColor(gasPrice); 28 | 29 | for (uint256 i = 0; i < numSides; i++) { 30 | for (uint256 j = 0; j < numSides; j++) { 31 | uint256 duration = _getSeconds(i, j, id); 32 | svg = string(abi.encodePacked(svg, '')); 35 | svg = string(abi.encodePacked(svg, '')); 37 | svg = string(abi.encodePacked(svg, '')); 38 | } 39 | } 40 | // Add text 41 | svg = string(abi.encodePacked(svg, '', id.toString(), '')); 42 | return svg; 43 | } 44 | 45 | function _getSeconds(uint256 i, uint256 j, uint256 id) internal pure returns (uint256) { 46 | if (i < j) { 47 | if ((i*j*id) % 3 == 0) { 48 | return 4; 49 | } 50 | else if ((i*j*id) % 2 == 0) { 51 | return 6; 52 | } 53 | } 54 | else { 55 | if ((i*j*id) % 3 == 0) { 56 | return 5; 57 | } 58 | else if ((i*j*id) % 2 == 0) { 59 | return 3; 60 | } 61 | } 62 | } 63 | 64 | function _closestCube(uint256 id) internal pure returns (uint256) { 65 | for (uint256 i = 1; i <= 11; i++) { 66 | if (id <= i**3) { 67 | return i; 68 | } 69 | } 70 | } 71 | 72 | function getColor(uint256 gasPrice) internal pure returns(uint256, uint256, uint256) { 73 | if (gasPrice <= 2) { 74 | return (220, 20, 60); 75 | } 76 | if (gasPrice <= 4) { 77 | return (230, 215, 140); 78 | } 79 | if (gasPrice <= 8) { 80 | return (120, 240, 20); 81 | } 82 | if (gasPrice <= 16) { 83 | return (20, 200, 209); 84 | } 85 | if (gasPrice <= 32) { 86 | return (30, 150, 220); 87 | } 88 | if (gasPrice <= 64) { 89 | return (120, 100, 220); 90 | } 91 | if (gasPrice <= 128) { 92 | return (220, 160, 220); 93 | } 94 | if (gasPrice <= 256) { 95 | return (250, 200, 200); 96 | } 97 | if (gasPrice <= 512) { 98 | return (120, 120, 120); 99 | } 100 | if (gasPrice <= 1000) { 101 | return (20, 40, 20); 102 | } 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /contracts/NFTLottery.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "./VRFConsumerBase.sol"; 6 | import "./LinkTokenInterface.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/ERC721Holder.sol"; 9 | 10 | contract NFTLottery is VRFConsumerBase, ERC721Holder { 11 | 12 | bytes32 internal immutable keyHash; 13 | LinkTokenInterface public immutable link; 14 | 15 | mapping(bytes32 => address) public rewardAddress; 16 | mapping(bytes32 => uint256) public rewardId; 17 | 18 | // We have 2 ways of fulfilling randomness: 19 | 20 | // 1) sending an NFT to one address from list of addresses 21 | mapping(bytes32 => address[]) public addressRecipients; 22 | 23 | // 2) sending an NFT to one holder from a list of NFT holders 24 | mapping(bytes32 => address) public nftRecipientAddress; 25 | // Both start and end are inclusive indices 26 | mapping(bytes32 => uint256) public nftRecipientStart; 27 | mapping(bytes32 => uint256) public nftRecipientEnd; 28 | 29 | constructor(address _vrf, address _link, bytes32 _keyHash) 30 | VRFConsumerBase( 31 | _vrf, _link 32 | ) public { 33 | link = LinkTokenInterface(_link); 34 | keyHash = _keyHash; 35 | } 36 | 37 | function distributeToAddresses(uint256 fee, address[] calldata recipients, address _rewardAddress, uint256 _rewardId) external { 38 | link.transferFrom(msg.sender, address(this), fee); 39 | IERC721(_rewardAddress).safeTransferFrom(msg.sender, address(this), _rewardId); 40 | bytes32 requestId = requestRandomness(keyHash, fee); 41 | addressRecipients[requestId] = recipients; 42 | rewardAddress[requestId] = _rewardAddress; 43 | rewardId[requestId] = _rewardId; 44 | } 45 | 46 | function distributeToNftHolders(uint256 fee, address _nftRecipientAddress, uint256 startIndex, uint256 endIndex, address _rewardAddress, uint256 _rewardId) external { 47 | link.transferFrom(msg.sender, address(this), fee); 48 | IERC721(_rewardAddress).safeTransferFrom(msg.sender, address(this), _rewardId); 49 | bytes32 requestId = requestRandomness(keyHash, fee); 50 | nftRecipientAddress[requestId] = _nftRecipientAddress; 51 | nftRecipientStart[requestId] = startIndex; 52 | nftRecipientEnd[requestId] = endIndex; 53 | rewardAddress[requestId] = _rewardAddress; 54 | rewardId[requestId] = _rewardId; 55 | } 56 | 57 | function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override { 58 | // Check what to fulfill: 59 | 60 | // If length of addressRecipients is 0, then it's an NFT distribution 61 | if (addressRecipients[requestId].length == 0) { 62 | uint256 startIndex = nftRecipientStart[requestId]; 63 | uint256 endIndex = nftRecipientEnd[requestId]; 64 | IERC721(rewardAddress[requestId]).transferFrom( 65 | address(this), 66 | IERC721(nftRecipientAddress[requestId]).ownerOf(randomness % (endIndex+1-startIndex) + startIndex), 67 | rewardId[requestId] 68 | ); 69 | delete nftRecipientAddress[requestId]; 70 | delete nftRecipientStart[requestId]; 71 | delete nftRecipientEnd[requestId]; 72 | } 73 | 74 | // Otherwise we pick a random address from the list 75 | else { 76 | address[] memory recipients = addressRecipients[requestId]; 77 | IERC721(rewardAddress[requestId]).transferFrom( 78 | address(this), 79 | recipients[randomness % recipients.length], 80 | rewardId[requestId] 81 | ); 82 | delete addressRecipients[requestId]; 83 | } 84 | // Clean up state 85 | delete rewardAddress[requestId]; 86 | delete rewardId[requestId]; 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /old-test/DoomRewarderTests.js: -------------------------------------------------------------------------------- 1 | const truffleAssert = require('truffle-assertions'); 2 | const monStakerArtifact = artifacts.require('./MonStaker2.sol'); 3 | const doomRewarderArtifact = artifacts.require('./DoomRewarder.sol'); 4 | 5 | // It checks permissions properly 6 | contract("doomRewarder tests", async accounts => { 7 | it ("handles permissions correctly / sets variables", async() => { 8 | let doomRewarder = await doomRewarderArtifact.deployed(); 9 | let monStaker = await monStakerArtifact.deployed(); 10 | 11 | // Ensure only owner can addBounty 12 | await doomRewarder.addBounty(web3.utils.soliditySha3("hello"), 1, 1, {from: accounts[0]}); 13 | await truffleAssert.reverts( 14 | doomRewarder.addBounty(web3.utils.soliditySha3("hello"), 1, 1, {from: accounts[1]}) 15 | ); 16 | 17 | // Ensure only owner can set validity of bounty 18 | await doomRewarder.setBountyInvalidity(1, true, {from: accounts[0]}); 19 | await truffleAssert.reverts( 20 | doomRewarder.setBountyInvalidity(1, true, {from: accounts[1]}) 21 | ); 22 | 23 | // Ensure only owner can setIDoomAdmin 24 | await doomRewarder.setIDoomAdmin(monStaker.address, {from: accounts[0]}); 25 | await truffleAssert.reverts( 26 | doomRewarder.setIDoomAdmin(monStaker.address, {from: accounts[1]}) 27 | ); 28 | 29 | // Ensure only owner can yeet 30 | await truffleAssert.reverts( 31 | doomRewarder.yeet({from: accounts[1]}) 32 | ); 33 | await doomRewarder.yeet({from: accounts[0]}); 34 | }); 35 | }); 36 | 37 | // It allows the setting / taking of bounties 38 | contract("doomRewarder tests", async accounts => { 39 | it ("handles permissions correctly / sets variables", async() => { 40 | let doomRewarder = await doomRewarderArtifact.deployed(); 41 | let monStaker = await monStakerArtifact.deployed(); 42 | let result; 43 | let expected; 44 | 45 | // Set doomRewarder to be admin 46 | await monStaker.setStakerAdminRole(doomRewarder.address, {from: accounts[0]}); 47 | 48 | // Set bounty 49 | await doomRewarder.addBounty(web3.utils.soliditySha3("hello"), 10, 1, {from: accounts[0]}); 50 | 51 | // Claim bounty 52 | await doomRewarder.claimBounty(1, "hello", {from: accounts[0]}); 53 | 54 | result = await monStaker.doomBalances(accounts[0]); 55 | expected = web3.utils.toBN(10); 56 | expect(result).to.eql(expected); 57 | 58 | // Expect to revert if already claimed 59 | await truffleAssert.reverts( 60 | doomRewarder.claimBounty(1, "hello", {from: accounts[0]}), 61 | "already claimed" 62 | ); 63 | 64 | // Expect to revert if numClaimers already met 65 | await truffleAssert.reverts( 66 | doomRewarder.claimBounty(1, "hello", {from: accounts[1]}), 67 | "already out" 68 | ); 69 | 70 | // Set new bounty (ID = 2) 71 | await doomRewarder.addBounty(web3.utils.soliditySha3("trap"), 2, 2, {from: accounts[0]}); 72 | 73 | // expect that a claim with the wrong password leads to no DOOM boost 74 | await doomRewarder.claimBounty(2, "hello", {from: accounts[1]}); 75 | result = await monStaker.doomBalances(accounts[1]); 76 | expected = web3.utils.toBN(0); 77 | expect(result).to.eql(expected); 78 | 79 | // allow someone else to claim the bounty 80 | await doomRewarder.claimBounty(2, "trap", {from: accounts[1]}); 81 | result = await monStaker.doomBalances(accounts[1]); 82 | expected = web3.utils.toBN(2); 83 | expect(result).to.eql(expected); 84 | 85 | // Set the bounty to be invalid 86 | await doomRewarder.setBountyInvalidity(2, true, {from: accounts[0]}); 87 | // Expect it to fail with "now invalid" 88 | await truffleAssert.reverts( 89 | doomRewarder.claimBounty(2, "hello", {from: accounts[1]}), 90 | "now invalid" 91 | ); 92 | }); 93 | }); -------------------------------------------------------------------------------- /coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 97.47% 24 | Statements 25 | 154/158 26 |
27 |
28 | 92.42% 29 | Branches 30 | 61/66 31 |
32 |
33 | 97.96% 34 | Functions 35 | 48/49 36 |
37 |
38 | 97.58% 39 | Lines 40 | 161/165 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
contracts/
97.47%154/15892.42%61/6697.96%48/4997.58%161/165
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for All files 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | / 20 |

21 |
22 |
23 | 97.47% 24 | Statements 25 | 154/158 26 |
27 |
28 | 92.42% 29 | Branches 30 | 61/66 31 |
32 |
33 | 97.96% 34 | Functions 35 | 48/49 36 |
37 |
38 | 97.58% 39 | Lines 40 | 161/165 41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
FileStatementsBranchesFunctionsLines
contracts/
97.47%154/15892.42%61/6697.96%48/4997.58%161/165
76 |
77 |
78 | 82 | 83 | 84 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /contracts/SafeMathChainlink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.6.0; 3 | 4 | /** 5 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 6 | * checks. 7 | * 8 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 9 | * in bugs, because programmers usually assume that an overflow raises an 10 | * error, which is the standard behavior in high level programming languages. 11 | * `SafeMath` restores this intuition by reverting the transaction when an 12 | * operation overflows. 13 | * 14 | * Using this library instead of the unchecked operations eliminates an entire 15 | * class of bugs, so it's recommended to use it always. 16 | */ 17 | library SafeMathChainlink { 18 | /** 19 | * @dev Returns the addition of two unsigned integers, reverting on 20 | * overflow. 21 | * 22 | * Counterpart to Solidity's `+` operator. 23 | * 24 | * Requirements: 25 | * - Addition cannot overflow. 26 | */ 27 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 28 | uint256 c = a + b; 29 | require(c >= a, "SafeMath: addition overflow"); 30 | 31 | return c; 32 | } 33 | 34 | /** 35 | * @dev Returns the subtraction of two unsigned integers, reverting on 36 | * overflow (when the result is negative). 37 | * 38 | * Counterpart to Solidity's `-` operator. 39 | * 40 | * Requirements: 41 | * - Subtraction cannot overflow. 42 | */ 43 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 44 | require(b <= a, "SafeMath: subtraction overflow"); 45 | uint256 c = a - b; 46 | 47 | return c; 48 | } 49 | 50 | /** 51 | * @dev Returns the multiplication of two unsigned integers, reverting on 52 | * overflow. 53 | * 54 | * Counterpart to Solidity's `*` operator. 55 | * 56 | * Requirements: 57 | * - Multiplication cannot overflow. 58 | */ 59 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 60 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 61 | // benefit is lost if 'b' is also tested. 62 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 63 | if (a == 0) { 64 | return 0; 65 | } 66 | 67 | uint256 c = a * b; 68 | require(c / a == b, "SafeMath: multiplication overflow"); 69 | 70 | return c; 71 | } 72 | 73 | /** 74 | * @dev Returns the integer division of two unsigned integers. Reverts on 75 | * division by zero. The result is rounded towards zero. 76 | * 77 | * Counterpart to Solidity's `/` operator. Note: this function uses a 78 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 79 | * uses an invalid opcode to revert (consuming all remaining gas). 80 | * 81 | * Requirements: 82 | * - The divisor cannot be zero. 83 | */ 84 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 85 | // Solidity only automatically asserts when dividing by 0 86 | require(b > 0, "SafeMath: division by zero"); 87 | uint256 c = a / b; 88 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 89 | 90 | return c; 91 | } 92 | 93 | /** 94 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 95 | * Reverts when dividing by zero. 96 | * 97 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 98 | * opcode (which leaves remaining gas untouched) while Solidity uses an 99 | * invalid opcode to revert (consuming all remaining gas). 100 | * 101 | * Requirements: 102 | * - The divisor cannot be zero. 103 | */ 104 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 105 | require(b != 0, "SafeMath: modulo by zero"); 106 | return a % b; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /contracts/MonSpawner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./MonCreatorInstance.sol"; 7 | import "./UsesMon.sol"; 8 | import "@openzeppelin/contracts/utils/Strings.sol"; 9 | 10 | contract MonSpawner is MonCreatorInstance, UsesMon { 11 | 12 | using SafeMath for uint256; 13 | using Strings for uint256; 14 | using SafeMath for uint8; 15 | using SafeERC20 for IERC20; 16 | 17 | modifier onlySpawnerAdmin { 18 | require(hasRole(SPAWNER_ADMIN_ROLE, msg.sender), "Not spawner admin"); 19 | _; 20 | } 21 | 22 | bytes32 public constant SPAWNER_ADMIN_ROLE = keccak256("SPAWNER_ADMIN_ROLE"); 23 | 24 | // cost in XMON to spawn a monster 25 | uint256 public spawnFee; 26 | 27 | // initial delay between spawns 28 | uint256 public initialDelay; 29 | 30 | // extra delay incurred by later generations 31 | uint256 public extraDelay; 32 | 33 | // every pair of monsters can only spawn once 34 | mapping(uint256 => mapping(uint256 => bool)) public hasSpawned; 35 | 36 | // block where a monster becomes usable for spawning again 37 | mapping(uint256 => uint256) public monUnlock; 38 | 39 | constructor(address xmonAddress, address monMinterAddress) public { 40 | 41 | // Give caller admin permissions 42 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 43 | 44 | // set xmon instance 45 | xmon = IERC20(xmonAddress); 46 | 47 | // set monMinter instance 48 | monMinter = IMonMinter(monMinterAddress); 49 | 50 | // spawnFee is 1 XMON to start 51 | spawnFee = 1 * (10**18); 52 | } 53 | 54 | function spawnNewMon(uint256 mon1Id, uint256 mon2Id) public returns (uint256) { 55 | require(monMinter.ownerOf(mon1Id) == msg.sender, "Need to own mon1"); 56 | require(monMinter.ownerOf(mon2Id) == msg.sender, "Need to own mon2"); 57 | require(!hasSpawned[mon1Id][mon2Id], "Already spawned with mon1 mon2"); 58 | require(block.number >= monUnlock[mon1Id], "mon1 isn't unlocked yet"); 59 | require(block.number >= monUnlock[mon2Id], "mon2 isn't unlocked yet"); 60 | require(mon1Id != mon2Id, "Can't spawn monster with itself"); 61 | super.updateNumMons(); 62 | 63 | // set spawn to be true for both (A,B) (B,A) pairings 64 | hasSpawned[mon1Id][mon2Id] = true; 65 | hasSpawned[mon2Id][mon1Id] = true; 66 | 67 | // transfer fee to token address 68 | xmon.transferFrom(msg.sender, address(xmon), spawnFee); 69 | 70 | // get references for both monsters 71 | Mon memory mon1 = monMinter.monRecords(mon1Id); 72 | Mon memory mon2 = monMinter.monRecords(mon2Id); 73 | 74 | // update the unlock time of each mon to be current block + (initalDelay + ((gen-1)*extraDelay)) 75 | monUnlock[mon1Id] = block.number.add(initialDelay.add(extraDelay.mul(mon1.gen.sub(1)))); 76 | monUnlock[mon2Id] = block.number.add(initialDelay.add(extraDelay.mul(mon2.gen.sub(1)))); 77 | 78 | // Set generation to be the lower of the two parents 79 | uint256 gen = mon1.gen.add(1); 80 | if (mon2.gen < mon1.gen) { 81 | gen = mon2.gen.add(1); 82 | } 83 | 84 | // mint the new monster 85 | uint256 id = monMinter.mintMonster( 86 | // to 87 | msg.sender, 88 | // parent1Id 89 | mon1Id, 90 | // parent2Id 91 | mon2Id, 92 | // minterContract 93 | address(this), 94 | // contractOrder 95 | numMons, 96 | // generation 97 | gen, 98 | // bits 99 | uint256(blockhash(block.number.sub(1))), 100 | // exp 101 | 0, 102 | // assign random rarity from 1 to 64 103 | uint8(uint256(blockhash(block.number.sub(1)))).div(4).add(1) 104 | ); 105 | 106 | // update the URI of the new mon to be the prefix plus the id 107 | string memory uri = string(abi.encodePacked(prefixURI, numMons.toString())); 108 | monMinter.setTokenURI(id, uri); 109 | 110 | return(id); 111 | } 112 | 113 | function setMonUnlock(uint256 id, uint256 blockNum) public onlySpawnerAdmin { 114 | monUnlock[id] = blockNum; 115 | } 116 | 117 | function setInitialDelay(uint256 d) public onlyAdmin { 118 | initialDelay = d; 119 | } 120 | 121 | function setExtraDelay(uint256 d) public onlyAdmin { 122 | extraDelay = d; 123 | } 124 | 125 | function setSpawnFee(uint256 f) public onlyAdmin { 126 | spawnFee = f; 127 | } 128 | 129 | function setSpawnerAdminRole(address a) public onlyAdmin { 130 | grantRole(SPAWNER_ADMIN_ROLE, a); 131 | } 132 | 133 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyAdmin { 134 | IERC20 _token = IERC20(tokenAddress); 135 | _token.safeTransfer(to, numTokens); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /old-test/MonImageRegistryTests.js: -------------------------------------------------------------------------------- 1 | const truffleAssert = require('truffle-assertions'); 2 | const monImageRegistryFlatArtifact = artifacts.require('./MonImageRegistryFlat.sol'); 3 | const xmonArtifact = artifacts.require('./XMON.sol'); 4 | const monMinterArtifact = artifacts.require('./MonMinter.sol'); 5 | 6 | contract("MonImageRegistry tests", async accounts => { 7 | it ("does the things", async() => { 8 | 9 | let monImageRegistry = await monImageRegistryFlatArtifact.deployed(); 10 | let XMON = await xmonArtifact.deployed(); 11 | let monMinter = await monMinterArtifact.deployed(); 12 | 13 | let expected, results; 14 | 15 | // It handles image encoding and decoding (TODO) 16 | let dataToEncode = web3.utils.fromAscii('hello'); 17 | let dataToEncode2 = web3.utils.fromAscii('hello2'); 18 | await monImageRegistry.uploadMon(dataToEncode); 19 | 20 | // Only owner can set fee (and fee is actually set) 21 | await monImageRegistry.setFee(1, {from: accounts[0]}); 22 | results = await monImageRegistry.fee(); 23 | expected = web3.utils.toBN(1); 24 | expect(results).to.eql(expected); 25 | await truffleAssert.reverts( 26 | monImageRegistry.setFee(1, {from: accounts[1]}) 27 | ); 28 | 29 | // Only owner can set lock (and lock is actually set) 30 | await monImageRegistry.setHashLock(0, true, {from: accounts[0]}); 31 | results = await monImageRegistry.isHashLocked(0); 32 | expect(results).to.eql(true); 33 | await truffleAssert.reverts( 34 | monImageRegistry.setHashLock(1, true, {from: accounts[1]}) 35 | ); 36 | 37 | await monImageRegistry.setDataLock(0, true, {from: accounts[0]}); 38 | results = await monImageRegistry.isDataLocked(0); 39 | expect(results).to.eql(true); 40 | await truffleAssert.reverts( 41 | monImageRegistry.setDataLock(1, true, {from: accounts[1]}) 42 | ); 43 | 44 | // Move the 0th token to accounts[1] (not owner) 45 | await monMinter.safeTransferFrom(accounts[0], accounts[1], 0, {from: accounts[0]}); 46 | // ERC-721 holder can't register if their id is locked 47 | await truffleAssert.reverts( 48 | monImageRegistry.registerMon(0, dataToEncode, true, {from: accounts[1]}), 49 | "locked" 50 | ); 51 | 52 | // Set lock back to false 53 | await monImageRegistry.setHashLock(0, false, {from: accounts[0]}); 54 | 55 | // Move 1 token to accounts[1] (fee has been reduced) 56 | await XMON.transfer(accounts[1], web3.utils.toBN('1'), {from: accounts[0]}); 57 | 58 | // Approve monImageRegistry to spend 100 tokens 59 | await XMON.approve(monImageRegistry.address, web3.utils.toBN('100'), {from: accounts[1]}); 60 | 61 | // Only owner (or ERC-721 holder) can register their tx hash 62 | // Calling register actually stores the hash 63 | await monImageRegistry.registerMon(0, dataToEncode, true, {from: accounts[1]}); 64 | await truffleAssert.reverts( 65 | monImageRegistry.registerMon(0, dataToEncode, true, {from: accounts[0]}) 66 | ); 67 | results = await monImageRegistry.monDataWithStaticHash(0); 68 | expect(web3.utils.toAscii(results)).to.eql('hello'); 69 | 70 | // Calling register actually deducts tokens 71 | results = await XMON.balanceOf(accounts[1]); 72 | expect(results).to.eql(web3.utils.toBN('0')); 73 | 74 | // Only owner can move tokens 75 | await monImageRegistry.moveTokens(XMON.address, accounts[0], 1, {from: accounts[0]}); 76 | await truffleAssert.reverts( 77 | monImageRegistry.moveTokens(XMON.address, accounts[0], 1, {from: accounts[1]}) 78 | ) 79 | 80 | // Do it again, but for entireMonData 81 | // Set lock back to false for data 82 | await monImageRegistry.setDataLock(0, false, {from: accounts[0]}); 83 | 84 | // Move 1 token to accounts[1] (fee has been reduced) 85 | await XMON.transfer(accounts[1], web3.utils.toBN('1'), {from: accounts[0]}); 86 | 87 | // Only owner (or ERC-721 holder) can register their data 88 | // Calling register actually stores the hash 89 | await monImageRegistry.registerEntireMonData(0, dataToEncode2, true, {from: accounts[1]}); 90 | await truffleAssert.reverts( 91 | monImageRegistry.registerEntireMonData(0, dataToEncode2, true, {from: accounts[0]}) 92 | ); 93 | results = await monImageRegistry.monDataWithStatic(0); 94 | expect(web3.utils.toAscii(results)).to.eql('hello2'); 95 | 96 | // Calling register actually deducts tokens 97 | results = await XMON.balanceOf(accounts[1]); 98 | expect(results).to.eql(web3.utils.toBN('0')); 99 | 100 | // Only owner can set XMON token address 101 | await monImageRegistry.setXmonToken(XMON.address, {from: accounts[0]}); 102 | await truffleAssert.reverts( 103 | monImageRegistry.setXmonToken(XMON.address, {from: accounts[1]}) 104 | ); 105 | 106 | // Only owner can set mon address 107 | await monImageRegistry.setMon(monMinter.address, {from: accounts[0]}); 108 | await truffleAssert.reverts( 109 | monImageRegistry.setMon(monMinter.address, {from: accounts[1]}) 110 | ); 111 | }); 112 | }); -------------------------------------------------------------------------------- /contracts/MonMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./UsesMon.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/ERC721Burnable.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 9 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 10 | import "@openzeppelin/contracts/utils/Counters.sol"; 11 | import "@openzeppelin/contracts/access/AccessControl.sol"; 12 | 13 | contract MonMinter is ERC721Burnable, AccessControl, UsesMon { 14 | 15 | modifier onlyAdmin { 16 | require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin"); 17 | _; 18 | } 19 | 20 | modifier onlyMinter { 21 | require(hasRole(MINTER_ROLE, msg.sender), "Not minter"); 22 | _; 23 | } 24 | 25 | using SafeERC20 for IERC20; 26 | using Counters for Counters.Counter; 27 | 28 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 29 | Counters.Counter private monIds; 30 | 31 | mapping(uint256 => Mon) public monRecords; 32 | 33 | mapping(uint256 => string) public rarityTable; 34 | 35 | constructor() public ERC721("0xmons.xyz", "0XMON") { 36 | 37 | // Give caller admin permissions 38 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 39 | 40 | // Make the caller admin a minter 41 | grantRole(MINTER_ROLE, msg.sender); 42 | 43 | // Send monster #0 to the caller 44 | mintMonster( 45 | // to 46 | msg.sender, 47 | // parent1Id 48 | 0, 49 | // parent2Id 50 | 0, 51 | // minterContract 52 | address(this), 53 | // contractOrder 54 | 0, 55 | // gen 56 | 0, 57 | // bits 58 | 0, 59 | // exp 60 | 0, 61 | // rarity 62 | 0 63 | ); 64 | } 65 | 66 | // Mints a monster to an address and sets its data 67 | function mintMonster(address to, 68 | uint256 parent1Id, 69 | uint256 parent2Id, 70 | address minterContract, 71 | uint256 contractOrder, 72 | uint256 gen, 73 | uint256 bits, 74 | uint256 exp, 75 | uint256 rarity 76 | ) public onlyMinter returns (uint256) { 77 | uint256 currId = monIds.current(); 78 | monIds.increment(); 79 | monRecords[currId] = Mon( 80 | to, 81 | parent1Id, 82 | parent2Id, 83 | minterContract, 84 | contractOrder, 85 | gen, 86 | bits, 87 | exp, 88 | rarity 89 | ); 90 | _safeMint(to, currId); 91 | return(currId); 92 | } 93 | 94 | // Modifies the data of a monster 95 | function modifyMon(uint256 id, 96 | bool ignoreZeros, 97 | uint256 parent1Id, 98 | uint256 parent2Id, 99 | address minterContract, 100 | uint256 contractOrder, 101 | uint256 gen, 102 | uint256 bits, 103 | uint256 exp, 104 | uint256 rarity 105 | ) public onlyMinter { 106 | Mon storage currMon = monRecords[id]; 107 | if (ignoreZeros) { 108 | if (parent1Id != 0) { 109 | currMon.parent1Id = parent1Id; 110 | } 111 | if (parent2Id != 0) { 112 | currMon.parent2Id = parent2Id; 113 | } 114 | if (minterContract != address(0)) { 115 | currMon.minterContract = minterContract; 116 | } 117 | if (contractOrder != 0) { 118 | currMon.contractOrder = contractOrder; 119 | } 120 | if (gen != 0) { 121 | currMon.gen = gen; 122 | } 123 | if (bits != 0) { 124 | currMon.bits = bits; 125 | } 126 | if (exp != 0) { 127 | currMon.exp = exp; 128 | } 129 | if (rarity != 0) { 130 | currMon.rarity = rarity; 131 | } 132 | } 133 | else { 134 | currMon.parent1Id = parent1Id; 135 | currMon.parent2Id = parent2Id; 136 | currMon.minterContract = minterContract; 137 | currMon.contractOrder = contractOrder; 138 | currMon.gen = gen; 139 | currMon.bits = bits; 140 | currMon.exp = exp; 141 | currMon.rarity = rarity; 142 | } 143 | } 144 | 145 | // Modifies the tokenURI of a monster 146 | function setTokenURI(uint256 id, string memory uri) public onlyMinter { 147 | _setTokenURI(id, uri); 148 | } 149 | 150 | // Sets the base URI 151 | function setBaseURI(string memory uri) public onlyAdmin { 152 | _setBaseURI(uri); 153 | } 154 | 155 | // Rescues tokens locked in the contract 156 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyAdmin { 157 | IERC20 _token = IERC20(tokenAddress); 158 | _token.safeTransfer(to, numTokens); 159 | } 160 | 161 | // Updates the mapping of rarity codes to strings 162 | function setRarityTitle(uint256 code, string memory s) public onlyAdmin { 163 | rarityTable[code] = s; 164 | } 165 | 166 | // Allows admin to add new minters 167 | function setMinterRole(address a) public onlyAdmin { 168 | grantRole(MINTER_ROLE, a); 169 | } 170 | } -------------------------------------------------------------------------------- /contracts/NFTLotteryPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | 5 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/ERC721Holder.sol"; 9 | import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; 10 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 11 | import "./IDistributor.sol"; 12 | import "./ILotteryReference.sol"; 13 | import "./ITokenURI.sol"; 14 | 15 | contract NFTLotteryPool is ERC721Upgradeable, OwnableUpgradeable, ERC721Holder, ReentrancyGuard { 16 | 17 | using SafeERC20 for IERC20; 18 | 19 | ILotteryReference private REFERENCE; 20 | IDistributor private RNG_DISTRIBUTOR; 21 | IERC20 private LINK; 22 | 23 | // Lottery vars 24 | address public prizeAddress; 25 | uint256 public prizeId; 26 | uint64 public startDate; 27 | uint64 public endDate; 28 | uint32 public minTicketsToSell; 29 | uint32 public maxTickets; 30 | uint32 public maxTicketsPerAddress; 31 | uint256 public ticketPrice; 32 | 33 | // Mutex for calling VRF 34 | bool public hasCalledVRF; 35 | 36 | // Check if refunds are allowed 37 | bool public isRefundOpen; 38 | 39 | function initialize( 40 | address _prizeAddress, 41 | uint256 _prizeId, 42 | uint64 _startDate, 43 | uint64 _endDate, 44 | uint32 _minTicketsToSell, 45 | uint32 _maxTickets, 46 | uint32 _maxTicketsPerAddress, 47 | uint256 _ticketPrice 48 | ) external initializer { 49 | require(_endDate > _startDate, "End is before start"); 50 | require(_minTicketsToSell > 0, "Min sell at least 1"); 51 | require(_minTicketsToSell <= _maxTickets, "Min greater than max"); 52 | require(_maxTicketsPerAddress >= 1, "Must be able to buy at least 1"); 53 | __Ownable_init(); 54 | __ERC721_init("NFT-LOTTERY", "LOTTO"); 55 | REFERENCE = ILotteryReference(msg.sender); 56 | LINK = IERC20(REFERENCE.LINK_ADDRESS()); 57 | RNG_DISTRIBUTOR = IDistributor(REFERENCE.RNG_DISTRIBUTOR_ADDRESS()); 58 | prizeAddress = _prizeAddress; 59 | prizeId = _prizeId; 60 | startDate = _startDate; 61 | endDate = _endDate; 62 | minTicketsToSell = _minTicketsToSell; 63 | maxTickets = _maxTickets; 64 | maxTicketsPerAddress = _maxTicketsPerAddress; 65 | ticketPrice = _ticketPrice; 66 | // NOTE: Prize and LINK are moved into the contract via the pool factory 67 | } 68 | 69 | function buyTickets(uint256 numTickets) public payable { 70 | require(block.timestamp >= startDate, "Too early"); 71 | require(balanceOf(msg.sender).add(numTickets) <= maxTicketsPerAddress, "Holding too many"); 72 | require(totalSupply().add(numTickets) <= maxTickets, "Exceeds max supply"); 73 | require(msg.value == ticketPrice.mul(numTickets), "Price incorrect"); 74 | require(block.timestamp < endDate, "Lottery over"); 75 | for (uint256 i = 0; i < numTickets; i++) { 76 | _mint(msg.sender, totalSupply()+1); 77 | } 78 | } 79 | 80 | function unlockRefund() public { 81 | require(block.timestamp > endDate + 7 days, "Too early"); 82 | require(!hasCalledVRF, "Already VRFed"); 83 | isRefundOpen = true; 84 | } 85 | 86 | function getRefund(uint256[] calldata ids) external nonReentrant { 87 | require(block.timestamp > endDate, "Lottery not over"); 88 | require(totalSupply() < minTicketsToSell || isRefundOpen, "Enough tickets sold"); 89 | uint256 refundAmount = 0; 90 | for (uint256 i = 0; i < ids.length; i++) { 91 | require(ownerOf(ids[i]) == msg.sender, "Not owner"); 92 | _burn(ids[i]); 93 | refundAmount = refundAmount.add(ticketPrice); 94 | } 95 | (bool sent, bytes memory data) = msg.sender.call{value: refundAmount}(""); 96 | require(sent, "Transfer failed"); 97 | } 98 | 99 | function refundOwnerAssets() public onlyOwner { 100 | require(block.timestamp > endDate, "Lottery not over"); 101 | require(totalSupply() < minTicketsToSell || isRefundOpen, "Enough tickets sold"); 102 | LINK.safeTransfer(owner(), REFERENCE.LINK_FEE()); 103 | IERC721Enumerable(prizeAddress).safeTransferFrom(address(this), owner(), prizeId); 104 | } 105 | 106 | function distributePrize() public onlyOwner { 107 | require(totalSupply() >= minTicketsToSell, "Not enough tickets sold"); 108 | require(block.timestamp > endDate, "Lottery not over"); 109 | require(! hasCalledVRF, "Already called VRF"); 110 | LINK.approve(address(RNG_DISTRIBUTOR), REFERENCE.LINK_FEE()); 111 | IERC721Enumerable(prizeAddress).setApprovalForAll(address(RNG_DISTRIBUTOR), true); 112 | RNG_DISTRIBUTOR.distributeToNftHolders(REFERENCE.LINK_FEE(), address(this), 1, totalSupply(), prizeAddress, prizeId); 113 | hasCalledVRF = true; 114 | } 115 | 116 | function claimETH() public onlyOwner { 117 | require(hasCalledVRF, "Must call VRF first"); 118 | require(IERC721Enumerable(prizeAddress).ownerOf(prizeId) != address(RNG_DISTRIBUTOR), "No VRF yet"); 119 | owner().call{value: address(this).balance}(""); 120 | } 121 | 122 | function tokenURI(uint256 id) public view override returns (string memory) { 123 | return ITokenURI(REFERENCE.masterTokenURI()).tokenURI(id); 124 | } 125 | } -------------------------------------------------------------------------------- /contracts/MultiAssetSwapper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.6.8; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 8 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 9 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 10 | import "@openzeppelin/contracts/token/ERC1155/ERC1155Receiver.sol"; 11 | 12 | contract MultiAssetSwapper is ReentrancyGuard, ERC1155Receiver, IERC721Receiver { 13 | 14 | using SafeERC20 for IERC20; 15 | using SafeMath for uint256; 16 | 17 | struct Swap { 18 | address proposer; 19 | address[] tokenAddresses; 20 | uint256[] amounts; 21 | uint256[] ids; 22 | bool[] isERC20; 23 | // 0 to (numToSwapFor-1) are the proposer's assets 24 | // numToSwapFor to (tokenAddresses.length-1) are the desired assets 25 | uint256 numToSwapFor; 26 | } 27 | 28 | uint256 public currSwapIndex; 29 | mapping(uint256 => Swap) swapRecords; 30 | 31 | constructor() public {} 32 | 33 | // Accepts safe ERC-721 transfers 34 | function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external override returns (bytes4) { 35 | return this.onERC721Received.selector; 36 | } 37 | 38 | // From: https://docs.gnosis.io/conditionaltokens/docs/ctftutorial13/ 39 | function onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes calldata data) external override returns(bytes4) { 40 | return this.onERC1155BatchReceived.selector; 41 | } 42 | 43 | function onERC1155BatchReceived( address operator, address from, uint256[] calldata ids, uint256[] calldata values, bytes calldata data ) 44 | external override returns(bytes4) { 45 | return this.onERC1155BatchReceived.selector; 46 | } 47 | 48 | function _transferAsset(address a, uint256 amount, uint256 id, bool isERC20, address receiver) internal { 49 | // Normal ERC-20 transfer 50 | if (isERC20) { 51 | IERC20(a).safeTransferFrom(msg.sender, receiver, amount); 52 | } 53 | else { 54 | // If amount is greater than 0, we do ERC-1155 transfer 55 | if (amount > 0) { 56 | IERC1155(a).safeTransferFrom(msg.sender, receiver, id, amount, ""); 57 | } 58 | else { 59 | IERC721(a).safeTransferFrom(msg.sender, receiver, id); 60 | } 61 | } 62 | } 63 | 64 | function _transferAssetBack(address a, uint256 amount, uint256 id, bool isERC20, address receiver) internal { 65 | // Normal ERC-20 transfer 66 | if (isERC20) { 67 | IERC20(a).safeTransfer(receiver, amount); 68 | } 69 | else { 70 | // If amount is greater than 0, we do ERC-1155 transfer 71 | if (amount > 0) { 72 | IERC1155(a).safeTransferFrom(address(this), receiver, id, amount, ""); 73 | } 74 | else { 75 | IERC721(a).safeTransferFrom(address(this), receiver, id); 76 | } 77 | } 78 | } 79 | 80 | function proposeSwap(address[] calldata tokenAddresses, 81 | uint256[] calldata amounts, 82 | uint256[] calldata ids, 83 | bool[] calldata isERC20, 84 | uint256 numToSwapFor) external nonReentrant { 85 | require((tokenAddresses.length == amounts.length), "diff lengths"); 86 | require((tokenAddresses.length == ids.length), "diff lengths"); 87 | require((tokenAddresses.length == isERC20.length), "diff lengths"); 88 | require((numToSwapFor < tokenAddresses.length), "nothing desired"); 89 | for (uint256 index = 0; index < numToSwapFor; index.add(1)) { 90 | _transferAsset(tokenAddresses[index], amounts[index], ids[index], isERC20[index], address(this)); 91 | } 92 | currSwapIndex = currSwapIndex.add(1); 93 | swapRecords[currSwapIndex] = Swap( 94 | msg.sender, 95 | tokenAddresses, 96 | amounts, 97 | ids, 98 | isERC20, 99 | numToSwapFor 100 | ); 101 | } 102 | 103 | function removeSwap(uint256 swapId) external nonReentrant { 104 | Swap storage swapItem = swapRecords[swapId]; 105 | require((swapItem.proposer == msg.sender), "not proposer"); 106 | 107 | // Move assets back 108 | for (uint256 index = 0; index < swapItem.numToSwapFor; index.add(1)) { 109 | _transferAssetBack(swapItem.tokenAddresses[index], swapItem.amounts[index], swapItem.ids[index], swapItem.isERC20[index], msg.sender); 110 | } 111 | 112 | // Remove storage slot 113 | delete swapRecords[swapId]; 114 | } 115 | 116 | function takeSwap(uint256 swapId) external nonReentrant { 117 | Swap storage swapItem = swapRecords[swapId]; 118 | // Send all of the desired stuff to the proposer of the swap 119 | for (uint256 index = swapItem.numToSwapFor; index < swapItem.tokenAddresses.length; index.add(1)) { 120 | _transferAsset(swapItem.tokenAddresses[index], swapItem.amounts[index], swapItem.ids[index], swapItem.isERC20[index], swapItem.proposer); 121 | } 122 | // Send all of the escrowed stuff to the taker 123 | for (uint256 index = 9; index < swapItem.numToSwapFor; index.add(1)) { 124 | _transferAsset(swapItem.tokenAddresses[index], swapItem.amounts[index], swapItem.ids[index], swapItem.isERC20[index], msg.sender); 125 | } 126 | 127 | // Remove storage slot 128 | delete swapRecords[swapId]; 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /coverage/contracts/UsesMon.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/UsesMon.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ UsesMon.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 0/0 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 0/0 41 |
42 |
43 |
44 |
45 |

 46 | 
146 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34  80 |   81 |   82 |   83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
113 |  
114 | pragma solidity ^0.6.8;
115 |  
116 | interface UsesMon {
117 |   struct Mon {
118 |       // the original address this monster went to
119 |       address summoner;
120 |  
121 |       // the unique ID associated with parent 1 of this monster
122 |       uint256 parent1Id;
123 |  
124 |       // the unique ID associated with parent 2 of this monster
125 |       uint256 parent2Id;
126 |  
127 |       // the address of the contract that minted this monster
128 |       address minterContract;
129 |  
130 |       // the id of this monster within its specific contract
131 |       uint256 contractOrder;
132 |  
133 |       // the generation of this monster
134 |       uint256 gen;
135 |  
136 |       // used to calculate statistics and other things
137 |       uint256 bits;
138 |  
139 |       // tracks the experience of this monster
140 |       uint256 exp;
141 |  
142 |       // the monster's rarity
143 |       uint256 rarity;
144 |   }
145 | }
147 |
148 |
149 | 153 | 154 | 155 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /coverage/lcov-report/contracts/UsesMon.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/UsesMon.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ UsesMon.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 0/0 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 0/0 41 |
42 |
43 |
44 |
45 |

 46 | 
146 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34  80 |   81 |   82 |   83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
113 |  
114 | pragma solidity ^0.6.8;
115 |  
116 | interface UsesMon {
117 |   struct Mon {
118 |       // the original address this monster went to
119 |       address summoner;
120 |  
121 |       // the unique ID associated with parent 1 of this monster
122 |       uint256 parent1Id;
123 |  
124 |       // the unique ID associated with parent 2 of this monster
125 |       uint256 parent2Id;
126 |  
127 |       // the address of the contract that minted this monster
128 |       address minterContract;
129 |  
130 |       // the id of this monster within its specific contract
131 |       uint256 contractOrder;
132 |  
133 |       // the generation of this monster
134 |       uint256 gen;
135 |  
136 |       // used to calculate statistics and other things
137 |       uint256 bits;
138 |  
139 |       // tracks the experience of this monster
140 |       uint256 exp;
141 |  
142 |       // the monster's rarity
143 |       uint256 rarity;
144 |   }
145 | }
147 |
148 |
149 | 153 | 154 | 155 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /old-test/MonMinterTests.js: -------------------------------------------------------------------------------- 1 | const truffleAssert = require('truffle-assertions'); 2 | const xmonArtifact = artifacts.require('./XMON.sol'); 3 | const monMinterArtifact = artifacts.require('./MonMinter.sol'); 4 | 5 | contract("monMinter tests", async accounts => { 6 | it ("allows setting all of the variables", async() => { 7 | 8 | let xmon = await xmonArtifact.deployed(); 9 | let monMinter = await monMinterArtifact.deployed(); 10 | 11 | // ensure admin role can call grantRole to itself 12 | // ensure other account can't call grantRole 13 | await monMinter.grantRole(web3.utils.asciiToHex("0"), accounts[1], {from: accounts[0]}); 14 | await truffleAssert.reverts( 15 | monMinter.grantRole(web3.utils.asciiToHex("0"), accounts[1], {from: accounts[1]}) 16 | ) 17 | 18 | // ensure that admin role can set minters and other accounts can't 19 | await monMinter.setMinterRole(accounts[0], {from: accounts[0]}); 20 | await truffleAssert.reverts( 21 | monMinter.setMinterRole(accounts[0], {from: accounts[1]}) 22 | ); 23 | 24 | // ensure mintMonster fails when the caller doesn't have minter role 25 | // ensure mintMonster succeeds when the caller has the minter role 26 | await monMinter.mintMonster(accounts[0], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 27 | await truffleAssert.reverts( 28 | monMinter.mintMonster( 29 | accounts[0], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[1]}) 30 | ); 31 | 32 | // ensure the caller of the constructor gets the 0 monster 33 | let mon0Owner = await monMinter.ownerOf(0); 34 | expect(mon0Owner).to.eql(accounts[0]); 35 | 36 | // ensure that modifyMonster fails when the caller doesn't have the minter role 37 | // ensure modifyMonster succeeds when the caller has the minter role 38 | // ensure modifyMonster only sets some values when the ignoreZeros flag is true 39 | await monMinter.modifyMon(0, false, 1, 11, accounts[1], 1111, 2, 3, 4, 5, {from: accounts[0]}); 40 | let mon0 = await monMinter.monRecords(0); 41 | expect(mon0["summoner"]).to.eql(accounts[0]); 42 | expect(mon0["parent1Id"]).to.eql(web3.utils.toBN(1)); 43 | expect(mon0["parent2Id"]).to.eql(web3.utils.toBN(11)); 44 | expect(mon0["minterContract"].toString()).to.eql(accounts[1].toString()); 45 | expect(mon0["contractOrder"]).to.eql(web3.utils.toBN(1111)); 46 | expect(mon0["gen"]).to.eql(web3.utils.toBN(2)); 47 | expect(mon0["bits"]).to.eql(web3.utils.toBN(3)); 48 | expect(mon0["exp"]).to.eql(web3.utils.toBN(4)); 49 | expect(mon0["rarity"]).to.eql(web3.utils.toBN(5)); 50 | 51 | await monMinter.modifyMon(0, true, 0, 0, "0x0000000000000000000000000000000000000000", 0, 0, 0, 0, 10, {from: accounts[0]}); 52 | mon0 = await monMinter.monRecords(0); 53 | expect(mon0["parent1Id"]).to.eql(web3.utils.toBN(1)); 54 | expect(mon0["parent2Id"]).to.eql(web3.utils.toBN(11)); 55 | expect(mon0["minterContract"].toString()).to.eql(accounts[1].toString()); 56 | expect(mon0["contractOrder"]).to.eql(web3.utils.toBN(1111)); 57 | expect(mon0["gen"]).to.eql(web3.utils.toBN(2)); 58 | expect(mon0["bits"]).to.eql(web3.utils.toBN(3)); 59 | expect(mon0["exp"]).to.eql(web3.utils.toBN(4)); 60 | expect(mon0["rarity"]).to.eql(web3.utils.toBN(10)); 61 | 62 | await monMinter.modifyMon(0, true, 10, 110, accounts[2], 11110, 20, 30, 40, 50, {from: accounts[0]}); 63 | mon0 = await monMinter.monRecords(0); 64 | expect(mon0["parent1Id"]).to.eql(web3.utils.toBN(10)); 65 | expect(mon0["parent2Id"]).to.eql(web3.utils.toBN(110)); 66 | expect(mon0["minterContract"].toString()).to.eql(accounts[2].toString()); 67 | expect(mon0["contractOrder"]).to.eql(web3.utils.toBN(11110)); 68 | expect(mon0["gen"]).to.eql(web3.utils.toBN(20)); 69 | expect(mon0["bits"]).to.eql(web3.utils.toBN(30)); 70 | expect(mon0["exp"]).to.eql(web3.utils.toBN(40)); 71 | expect(mon0["rarity"]).to.eql(web3.utils.toBN(50)); 72 | 73 | await truffleAssert.reverts( 74 | monMinter.modifyMon(0, false, 0, 0, accounts[3], 0, 2, 3, 4, 5, {from: accounts[1]}) 75 | ); 76 | 77 | // ensure setBaseURI fails when caller doesn't have admin role 78 | // ensure setBaseURI succeeds when caller has admin role 79 | await monMinter.setBaseURI("test.com/", {from: accounts[0]}); 80 | let baseURI = await monMinter.baseURI(); 81 | expect(baseURI).to.eql("test.com/"); 82 | await truffleAssert.reverts( 83 | monMinter.setBaseURI("test.com/", {from: accounts[1]}) 84 | ); 85 | 86 | // ensure setTokenURI fails when caller doesn't have minter role 87 | // ensure setTokenURI succeeds when caller has minter role, and that concatenation works 88 | await monMinter.setTokenURI(0, "test", {from: accounts[0]}); 89 | let mon0URI = await monMinter.tokenURI(0); 90 | expect(mon0URI).to.eql("test.com/test"); 91 | await truffleAssert.reverts( 92 | monMinter.setTokenURI(0, "test", {from: accounts[1]}) 93 | ); 94 | 95 | 96 | // ensure moveTokens fails when caller doesn't have admin role 97 | // ensure moveToken succeeds when caller has admin role 98 | await truffleAssert.reverts( 99 | monMinter.moveTokens(xmon.address, accounts[0], 0, {from: accounts[1]}) 100 | ) 101 | await monMinter.moveTokens(xmon.address, accounts[0], 0, {from: accounts[0]}); 102 | 103 | // ensure setRarityTitle fails when caller doesn't have admin role 104 | // ensure setRarityTitle succeeds when caller has admin role 105 | await monMinter.setRarityTitle(0, "test", {from: accounts[0]}); 106 | let rarity0 = await monMinter.rarityTable(0); 107 | expect(rarity0).to.eql("test"); 108 | await truffleAssert.reverts( 109 | monMinter.setRarityTitle(0, "test", {from: accounts[1]}) 110 | ); 111 | }); 112 | }); -------------------------------------------------------------------------------- /coverage/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, 4 | currentSort = { 5 | index: 0, 6 | desc: false 7 | }; 8 | 9 | // returns the summary table element 10 | function getTable() { return document.querySelector('.coverage-summary'); } 11 | // returns the thead element of the summary table 12 | function getTableHeader() { return getTable().querySelector('thead tr'); } 13 | // returns the tbody element of the summary table 14 | function getTableBody() { return getTable().querySelector('tbody'); } 15 | // returns the th element for nth column 16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 17 | 18 | // loads all columns 19 | function loadColumns() { 20 | var colNodes = getTableHeader().querySelectorAll('th'), 21 | colNode, 22 | cols = [], 23 | col, 24 | i; 25 | 26 | for (i = 0; i < colNodes.length; i += 1) { 27 | colNode = colNodes[i]; 28 | col = { 29 | key: colNode.getAttribute('data-col'), 30 | sortable: !colNode.getAttribute('data-nosort'), 31 | type: colNode.getAttribute('data-type') || 'string' 32 | }; 33 | cols.push(col); 34 | if (col.sortable) { 35 | col.defaultDescSort = col.type === 'number'; 36 | colNode.innerHTML = colNode.innerHTML + ''; 37 | } 38 | } 39 | return cols; 40 | } 41 | // attaches a data attribute to every tr element with an object 42 | // of data values keyed by column name 43 | function loadRowData(tableRow) { 44 | var tableCols = tableRow.querySelectorAll('td'), 45 | colNode, 46 | col, 47 | data = {}, 48 | i, 49 | val; 50 | for (i = 0; i < tableCols.length; i += 1) { 51 | colNode = tableCols[i]; 52 | col = cols[i]; 53 | val = colNode.getAttribute('data-value'); 54 | if (col.type === 'number') { 55 | val = Number(val); 56 | } 57 | data[col.key] = val; 58 | } 59 | return data; 60 | } 61 | // loads all row data 62 | function loadData() { 63 | var rows = getTableBody().querySelectorAll('tr'), 64 | i; 65 | 66 | for (i = 0; i < rows.length; i += 1) { 67 | rows[i].data = loadRowData(rows[i]); 68 | } 69 | } 70 | // sorts the table using the data for the ith column 71 | function sortByIndex(index, desc) { 72 | var key = cols[index].key, 73 | sorter = function (a, b) { 74 | a = a.data[key]; 75 | b = b.data[key]; 76 | return a < b ? -1 : a > b ? 1 : 0; 77 | }, 78 | finalSorter = sorter, 79 | tableBody = document.querySelector('.coverage-summary tbody'), 80 | rowNodes = tableBody.querySelectorAll('tr'), 81 | rows = [], 82 | i; 83 | 84 | if (desc) { 85 | finalSorter = function (a, b) { 86 | return -1 * sorter(a, b); 87 | }; 88 | } 89 | 90 | for (i = 0; i < rowNodes.length; i += 1) { 91 | rows.push(rowNodes[i]); 92 | tableBody.removeChild(rowNodes[i]); 93 | } 94 | 95 | rows.sort(finalSorter); 96 | 97 | for (i = 0; i < rows.length; i += 1) { 98 | tableBody.appendChild(rows[i]); 99 | } 100 | } 101 | // removes sort indicators for current column being sorted 102 | function removeSortIndicators() { 103 | var col = getNthColumn(currentSort.index), 104 | cls = col.className; 105 | 106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 107 | col.className = cls; 108 | } 109 | // adds sort indicators for current column being sorted 110 | function addSortIndicators() { 111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 112 | } 113 | // adds event listeners for all sorter widgets 114 | function enableUI() { 115 | var i, 116 | el, 117 | ithSorter = function ithSorter(i) { 118 | var col = cols[i]; 119 | 120 | return function () { 121 | var desc = col.defaultDescSort; 122 | 123 | if (currentSort.index === i) { 124 | desc = !currentSort.desc; 125 | } 126 | sortByIndex(i, desc); 127 | removeSortIndicators(); 128 | currentSort.index = i; 129 | currentSort.desc = desc; 130 | addSortIndicators(); 131 | }; 132 | }; 133 | for (i =0 ; i < cols.length; i += 1) { 134 | if (cols[i].sortable) { 135 | // add the click event handler on the th so users 136 | // dont have to click on those tiny arrows 137 | el = getNthColumn(i).querySelector('.sorter').parentElement; 138 | if (el.addEventListener) { 139 | el.addEventListener('click', ithSorter(i)); 140 | } else { 141 | el.attachEvent('onclick', ithSorter(i)); 142 | } 143 | } 144 | } 145 | } 146 | // adds sorting functionality to the UI 147 | return function () { 148 | if (!getTable()) { 149 | return; 150 | } 151 | cols = loadColumns(); 152 | loadData(cols); 153 | addSortIndicators(); 154 | enableUI(); 155 | }; 156 | })(); 157 | 158 | window.addEventListener('load', addSorting); 159 | -------------------------------------------------------------------------------- /coverage/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* dark red */ 156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 157 | .low .chart { border:1px solid #C21F39 } 158 | /* medium red */ 159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 160 | /* light red */ 161 | .low, .cline-no { background:#FCE1E5 } 162 | /* light green */ 163 | .high, .cline-yes { background:rgb(230,245,208) } 164 | /* medium green */ 165 | .cstat-yes { background:rgb(161,215,106) } 166 | /* dark green */ 167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 168 | .high .chart { border:1px solid rgb(77,146,33) } 169 | /* dark yellow (gold) */ 170 | .medium .chart { border:1px solid #f9cd0b; } 171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 172 | /* light yellow */ 173 | .medium { background: #fff4c2; } 174 | /* light gray */ 175 | span.cline-neutral { background: #eaeaea; } 176 | 177 | .cbranch-no { background: yellow !important; color: #111; } 178 | 179 | .cstat-skip { background: #ddd; color: #111; } 180 | .fstat-skip { background: #ddd; color: #111 !important; } 181 | .cbranch-skip { background: #ddd !important; color: #111; } 182 | 183 | 184 | .cover-fill, .cover-empty { 185 | display:inline-block; 186 | height: 12px; 187 | } 188 | .chart { 189 | line-height: 0; 190 | } 191 | .cover-empty { 192 | background: white; 193 | } 194 | .cover-full { 195 | border-right: none !important; 196 | } 197 | pre.prettyprint { 198 | border: none !important; 199 | padding: 0 !important; 200 | margin: 0 !important; 201 | } 202 | .com { color: #999 !important; } 203 | .ignore-none { color: #999; font-weight: normal; } 204 | 205 | .wrapper { 206 | min-height: 100%; 207 | height: auto !important; 208 | height: 100%; 209 | margin: 0 auto -48px; 210 | } 211 | .footer, .push { 212 | height: 48px; 213 | } 214 | -------------------------------------------------------------------------------- /old-test/NFGasTest.js: -------------------------------------------------------------------------------- 1 | // Allows minting with the correct gas price 2 | // mints fail if the gas price is wrong 3 | // transfers failf if the gas price is wrong 4 | // subsequent mints/transfers in a row (with correct prices) will work as intended 5 | 6 | const truffleAssert = require('truffle-assertions'); 7 | const nfgasArtifact = artifacts.require('./NFGas.sol'); 8 | const gwei = 1000000000; 9 | 10 | contract("NFGas generation", async accounts => { 11 | it ("makes images", async() =>{ 12 | let nfgas = await nfgasArtifact.deployed(); 13 | let img = await nfgas.svg(10); 14 | console.log(img) 15 | }) 16 | }); 17 | 18 | contract("NFGas tests", async accounts => { 19 | it ("allows minting at the right gas price", async() => { 20 | let nfgas = await nfgasArtifact.deployed(); 21 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 22 | }); 23 | }); 24 | 25 | contract("NFGas tests", async accounts => { 26 | it ("reverts if minting two at the same ID", async() => { 27 | let nfgas = await nfgasArtifact.deployed(); 28 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 29 | await truffleAssert.reverts( 30 | nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}) 31 | ) 32 | }); 33 | }); 34 | 35 | contract("NFGas tests", async accounts => { 36 | it ("reverts if gas price is wrong", async() => { 37 | let nfgas = await nfgasArtifact.deployed(); 38 | await truffleAssert.reverts( 39 | nfgas.mint(1, {from: accounts[0], gasPrice: 10*gwei, value: web3.utils.toWei('0.1', 'ether')}), 40 | "Exact gas" 41 | ); 42 | }); 43 | }); 44 | 45 | contract("NFGas tests", async accounts => { 46 | it ("reverts if there is no payment", async() => { 47 | let nfgas = await nfgasArtifact.deployed(); 48 | await truffleAssert.reverts( 49 | nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei}), 50 | "Pay fee" 51 | ); 52 | }); 53 | }); 54 | 55 | contract("NFGas tests", async accounts => { 56 | it ("reverts if id is too high", async() => { 57 | let nfgas = await nfgasArtifact.deployed(); 58 | await truffleAssert.reverts( 59 | nfgas.mint(1025, {from: accounts[0], gasPrice: 1025*gwei, value: web3.utils.toWei('0.1', 'ether')}), 60 | "Too high" 61 | ); 62 | }); 63 | }); 64 | 65 | contract("NFGas tests", async accounts => { 66 | it ("allows minting many at the right price", async() => { 67 | let nfgas = await nfgasArtifact.deployed(); 68 | for (let i = 1; i < 10; i++) { 69 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 70 | } 71 | for (let i = 100; i < 110; i++) { 72 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 73 | } 74 | let svg = await nfgas.svg(1); 75 | console.log(svg); 76 | }); 77 | }); 78 | 79 | 80 | contract("NFGas tests", async accounts => { 81 | it ("allows transfers at the right gas price", async() => { 82 | let nfgas = await nfgasArtifact.deployed(); 83 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 84 | await nfgas.transferFrom(accounts[0], accounts[1], 1, {from: accounts[0], gasPrice: 1*gwei}); 85 | }); 86 | }); 87 | 88 | contract("NFGas tests", async accounts => { 89 | it ("allows safe transfers at the right gas price", async() => { 90 | let nfgas = await nfgasArtifact.deployed(); 91 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 92 | await nfgas.safeTransferFrom(accounts[0], accounts[1], 1, {from: accounts[0], gasPrice: 1*gwei}); 93 | }); 94 | }); 95 | 96 | 97 | contract("NFGas tests", async accounts => { 98 | it ("reverts transfers at the wrong gas price", async() => { 99 | let nfgas = await nfgasArtifact.deployed(); 100 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 101 | await truffleAssert.reverts( 102 | nfgas.transferFrom(accounts[0], accounts[1], 1, {from: accounts[0], gasPrice: 10*gwei}) 103 | ); 104 | }); 105 | }); 106 | 107 | contract("NFGas tests", async accounts => { 108 | it ("reverts safe transfers at the wrong gas price", async() => { 109 | let nfgas = await nfgasArtifact.deployed(); 110 | await nfgas.mint(1, {from: accounts[0], gasPrice: 1*gwei, value: web3.utils.toWei('0.1', 'ether')}); 111 | await truffleAssert.reverts( 112 | nfgas.safeTransferFrom(accounts[0], accounts[1], 1, {from: accounts[0], gasPrice: 10*gwei}) 113 | ); 114 | }); 115 | }); 116 | 117 | contract("NFGas tests", async accounts => { 118 | it ("allows minting/transfers for many at the right price", async() => { 119 | let nfgas = await nfgasArtifact.deployed(); 120 | for (let i = 1; i < 10; i++) { 121 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 122 | await nfgas.transferFrom(accounts[0], accounts[1], i, {from: accounts[0], gasPrice: i*gwei}); 123 | } 124 | for (let i = 100; i < 110; i++) { 125 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 126 | await nfgas.transferFrom(accounts[0], accounts[1], i, {from: accounts[0], gasPrice: i*gwei}); 127 | } 128 | }); 129 | }); 130 | 131 | contract("NFGas tests", async accounts => { 132 | it ("allows minting/safe transfers for many at the right price", async() => { 133 | let nfgas = await nfgasArtifact.deployed(); 134 | for (let i = 1; i < 10; i++) { 135 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 136 | await nfgas.safeTransferFrom(accounts[0], accounts[1], i, {from: accounts[0], gasPrice: i*gwei}); 137 | } 138 | for (let i = 100; i < 110; i++) { 139 | await nfgas.mint(i, {from: accounts[0], gasPrice: i*gwei, value: web3.utils.toWei('0.1', 'ether')}); 140 | await nfgas.safeTransferFrom(accounts[0], accounts[1], i, {from: accounts[0], gasPrice: i*gwei}); 141 | } 142 | }); 143 | }); -------------------------------------------------------------------------------- /coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* dark red */ 156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 157 | .low .chart { border:1px solid #C21F39 } 158 | /* medium red */ 159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 160 | /* light red */ 161 | .low, .cline-no { background:#FCE1E5 } 162 | /* light green */ 163 | .high, .cline-yes { background:rgb(230,245,208) } 164 | /* medium green */ 165 | .cstat-yes { background:rgb(161,215,106) } 166 | /* dark green */ 167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 168 | .high .chart { border:1px solid rgb(77,146,33) } 169 | /* dark yellow (gold) */ 170 | .medium .chart { border:1px solid #f9cd0b; } 171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 172 | /* light yellow */ 173 | .medium { background: #fff4c2; } 174 | /* light gray */ 175 | span.cline-neutral { background: #eaeaea; } 176 | 177 | .cbranch-no { background: yellow !important; color: #111; } 178 | 179 | .cstat-skip { background: #ddd; color: #111; } 180 | .fstat-skip { background: #ddd; color: #111 !important; } 181 | .cbranch-skip { background: #ddd !important; color: #111; } 182 | 183 | 184 | .cover-fill, .cover-empty { 185 | display:inline-block; 186 | height: 12px; 187 | } 188 | .chart { 189 | line-height: 0; 190 | } 191 | .cover-empty { 192 | background: white; 193 | } 194 | .cover-full { 195 | border-right: none !important; 196 | } 197 | pre.prettyprint { 198 | border: none !important; 199 | padding: 0 !important; 200 | margin: 0 !important; 201 | } 202 | .com { color: #999 !important; } 203 | .ignore-none { color: #999; font-weight: normal; } 204 | 205 | .wrapper { 206 | min-height: 100%; 207 | height: auto !important; 208 | height: 100%; 209 | margin: 0 auto -48px; 210 | } 211 | .footer, .push { 212 | height: 48px; 213 | } 214 | -------------------------------------------------------------------------------- /coverage/contracts/MonCreatorInstance.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/MonCreatorInstance.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ MonCreatorInstance.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 4/4 31 |
32 |
33 | 100% 34 | Functions 35 | 4/4 36 |
37 |
38 | 100% 39 | Lines 40 | 6/6 41 |
42 |
43 |
44 |
45 |

 46 | 
164 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40  86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 | 74× 109 | 42× 110 |   111 |   112 |   113 | 41× 114 | 40× 115 |   116 |   117 |   118 | 119 |   120 |   121 |   122 | 123 |   124 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
125 |  
126 | pragma solidity ^0.6.8;
127 |  
128 | import "./IMonMinter.sol";
129 | import "@openzeppelin/contracts/access/AccessControl.sol";
130 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
131 |  
132 | abstract contract MonCreatorInstance is AccessControl {
133 |  
134 |   using SafeERC20 for IERC20;
135 |   using SafeMath for uint256;
136 |  
137 |   IERC20 public xmon;
138 |   IMonMinter public monMinter;
139 |  
140 |   uint256 public maxMons;
141 |   uint256 public numMons;
142 |  
143 |   // to be appended before the URI for the NFT
144 |   string public prefixURI;
145 |  
146 |   modifier onlyAdmin {
147 |     require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");
148 |     _;
149 |   }
150 |  
151 |   function updateNumMons() internal {
152 |     require(numMons < maxMons, "All mons are out");
153 |     numMons = numMons.add(1);
154 |   }
155 |  
156 |   function setMaxMons(uint256 m) public onlyAdmin {
157 |     maxMons = m;
158 |   }
159 |  
160 |   function setPrefixURI(string memory prefix) public onlyAdmin {
161 |     prefixURI = prefix;
162 |   }
163 | }
165 |
166 |
167 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /coverage/lcov-report/contracts/MonCreatorInstance.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/MonCreatorInstance.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ MonCreatorInstance.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 5/5 26 |
27 |
28 | 100% 29 | Branches 30 | 4/4 31 |
32 |
33 | 100% 34 | Functions 35 | 4/4 36 |
37 |
38 | 100% 39 | Lines 40 | 6/6 41 |
42 |
43 |
44 |
45 |

 46 | 
164 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40  86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 | 74× 109 | 42× 110 |   111 |   112 |   113 | 41× 114 | 40× 115 |   116 |   117 |   118 | 119 |   120 |   121 |   122 | 123 |   124 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
125 |  
126 | pragma solidity ^0.6.8;
127 |  
128 | import "./IMonMinter.sol";
129 | import "@openzeppelin/contracts/access/AccessControl.sol";
130 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
131 |  
132 | abstract contract MonCreatorInstance is AccessControl {
133 |  
134 |   using SafeERC20 for IERC20;
135 |   using SafeMath for uint256;
136 |  
137 |   IERC20 public xmon;
138 |   IMonMinter public monMinter;
139 |  
140 |   uint256 public maxMons;
141 |   uint256 public numMons;
142 |  
143 |   // to be appended before the URI for the NFT
144 |   string public prefixURI;
145 |  
146 |   modifier onlyAdmin {
147 |     require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");
148 |     _;
149 |   }
150 |  
151 |   function updateNumMons() internal {
152 |     require(numMons < maxMons, "All mons are out");
153 |     numMons = numMons.add(1);
154 |   }
155 |  
156 |   function setMaxMons(uint256 m) public onlyAdmin {
157 |     maxMons = m;
158 |   }
159 |  
160 |   function setPrefixURI(string memory prefix) public onlyAdmin {
161 |     prefixURI = prefix;
162 |   }
163 | }
165 |
166 |
167 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /coverage/contracts/IMonMinter.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/IMonMinter.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ IMonMinter.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 0/0 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 0/0 41 |
42 |
43 |
44 |
45 |

 46 | 
155 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37  83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |   113 |   114 |   115 |   116 |   117 |   118 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
119 |  
120 | pragma solidity ^0.6.8;
121 | pragma experimental ABIEncoderV2;
122 |  
123 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
124 | import "./UsesMon.sol";
125 |  
126 | interface IMonMinter is IERC721, UsesMon {
127 |  
128 |   function mintMonster(address to,
129 |                        uint256 parent1Id,
130 |                        uint256 parent2Id,
131 |                        address minterContract,
132 |                        uint256 contractOrder,
133 |                        uint256 gen,
134 |                        uint256 bits,
135 |                        uint256 exp,
136 |                        uint256 rarity
137 |                       ) external returns (uint256);
138 |  
139 |   function modifyMon(uint256 id,
140 |                      bool ignoreZeros,
141 |                      uint256 parent1Id,
142 |                      uint256 parent2Id,
143 |                      address minterContract,
144 |                      uint256 contractOrder,
145 |                      uint256 gen,
146 |                      uint256 bits,
147 |                      uint256 exp,
148 |                      uint256 rarity
149 |   ) external;
150 |  
151 |   function monRecords(uint256 id) external returns (Mon memory);
152 |  
153 |   function setTokenURI(uint256 id, string calldata uri) external;
154 | }
156 |
157 |
158 | 162 | 163 | 164 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /coverage/lcov-report/contracts/IMonMinter.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/IMonMinter.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ IMonMinter.sol 20 |

21 |
22 |
23 | 100% 24 | Statements 25 | 0/0 26 |
27 |
28 | 100% 29 | Branches 30 | 0/0 31 |
32 |
33 | 100% 34 | Functions 35 | 0/0 36 |
37 |
38 | 100% 39 | Lines 40 | 0/0 41 |
42 |
43 |
44 |
45 |

 46 | 
155 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37  83 |   84 |   85 |   86 |   87 |   88 |   89 |   90 |   91 |   92 |   93 |   94 |   95 |   96 |   97 |   98 |   99 |   100 |   101 |   102 |   103 |   104 |   105 |   106 |   107 |   108 |   109 |   110 |   111 |   112 |   113 |   114 |   115 |   116 |   117 |   118 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
119 |  
120 | pragma solidity ^0.6.8;
121 | pragma experimental ABIEncoderV2;
122 |  
123 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
124 | import "./UsesMon.sol";
125 |  
126 | interface IMonMinter is IERC721, UsesMon {
127 |  
128 |   function mintMonster(address to,
129 |                        uint256 parent1Id,
130 |                        uint256 parent2Id,
131 |                        address minterContract,
132 |                        uint256 contractOrder,
133 |                        uint256 gen,
134 |                        uint256 bits,
135 |                        uint256 exp,
136 |                        uint256 rarity
137 |                       ) external returns (uint256);
138 |  
139 |   function modifyMon(uint256 id,
140 |                      bool ignoreZeros,
141 |                      uint256 parent1Id,
142 |                      uint256 parent2Id,
143 |                      address minterContract,
144 |                      uint256 contractOrder,
145 |                      uint256 gen,
146 |                      uint256 bits,
147 |                      uint256 exp,
148 |                      uint256 rarity
149 |   ) external;
150 |  
151 |   function monRecords(uint256 id) external returns (Mon memory);
152 |  
153 |   function setTokenURI(uint256 id, string calldata uri) external;
154 | }
156 |
157 |
158 | 162 | 163 | 164 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /contracts/MonStaker2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./MonCreatorInstance.sol"; 7 | import "./IWhitelist.sol"; 8 | import "@openzeppelin/contracts/utils/Strings.sol"; 9 | 10 | contract MonStaker2 is MonCreatorInstance { 11 | 12 | using SafeMath for uint256; 13 | using Strings for uint256; 14 | using SafeERC20 for IERC20; 15 | 16 | bytes32 public constant STAKER_ADMIN_ROLE = keccak256("STAKER_ADMIN_ROLE"); 17 | 18 | uint256 public maxStake; 19 | 20 | struct Stake { 21 | uint256 amount; 22 | uint256 startBlock; 23 | } 24 | mapping(address => Stake) public stakeRecords; 25 | 26 | // amount of time an account has summoned 27 | mapping(address => uint256) public numSummons; 28 | 29 | // the amount that gets added as a multiplier to numSummons 30 | // summon fee in DOOM = numSummons * extraDoom + baseDoomFee 31 | uint256 public doomMultiplier; 32 | 33 | // amount of base doom needed to summon monster 34 | uint256 public baseDoomFee; 35 | 36 | // the amount of doom accrued by each account 37 | mapping(address => uint256) public doomBalances; 38 | 39 | // initial rarity 40 | uint256 public rarity; 41 | 42 | // the reference for checking if this contract is whitelisted 43 | IWhitelist private whitelistChecker; 44 | 45 | modifier onlyStakerAdmin { 46 | require(hasRole(STAKER_ADMIN_ROLE, msg.sender), "Not staker admin"); 47 | _; 48 | } 49 | 50 | constructor(address xmonAddress, address monMinterAddress) public { 51 | // Give caller admin permissions 52 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 53 | 54 | // Make the caller admin a staker admin 55 | grantRole(STAKER_ADMIN_ROLE, msg.sender); 56 | 57 | // starting rarity is 1 58 | rarity = 1; 59 | 60 | // set xmon instance 61 | xmon = IERC20(xmonAddress); 62 | 63 | // set whitelistChecker to also be the xmonAddress 64 | whitelistChecker = IWhitelist(xmonAddress); 65 | 66 | // set monMinter instance 67 | monMinter = IMonMinter(monMinterAddress); 68 | } 69 | 70 | function addStake(uint256 amount) public { 71 | 72 | require(whitelistChecker.whitelist(address(this)), "Staker not whitelisted!"); 73 | require(amount > 0, "Need to stake nonzero"); 74 | 75 | // award existing doom 76 | awardDoom(msg.sender); 77 | 78 | // update to total amount 79 | uint256 newAmount = stakeRecords[msg.sender].amount.add(amount); 80 | 81 | // ensure the total is less than max stake 82 | require(newAmount <= maxStake, "Exceeds max stake"); 83 | 84 | // update stake records 85 | stakeRecords[msg.sender] = Stake( 86 | newAmount, 87 | block.number 88 | ); 89 | 90 | // transfer tokens to contract 91 | xmon.safeTransferFrom(msg.sender, address(this), amount); 92 | } 93 | 94 | function removeStake() public { 95 | // award doom 96 | awardDoom(msg.sender); 97 | emergencyRemoveStake(); 98 | } 99 | 100 | function emergencyRemoveStake() public { 101 | // calculate how much to award 102 | uint256 amountToTransfer = stakeRecords[msg.sender].amount; 103 | 104 | // remove stake records 105 | delete stakeRecords[msg.sender]; 106 | 107 | // transfer tokens back 108 | xmon.safeTransfer(msg.sender, amountToTransfer); 109 | } 110 | 111 | // Awards accumulated doom and resets startBlock 112 | function awardDoom(address a) public { 113 | // If there is an existing amount staked, add the current accumulated amount and reset the block number 114 | if (stakeRecords[a].amount != 0) { 115 | uint256 doomAmount = stakeRecords[a].amount.mul(block.number.sub(stakeRecords[a].startBlock)); 116 | doomBalances[a] = doomBalances[a].add(doomAmount); 117 | 118 | // reset the start block 119 | stakeRecords[a].startBlock = block.number; 120 | } 121 | } 122 | 123 | // Claim a monster 124 | function claimMon() public returns (uint256) { 125 | 126 | // award doom first 127 | awardDoom(msg.sender); 128 | 129 | // check conditions 130 | require(doomBalances[msg.sender] >= doomFee(msg.sender), "Not enough DOOM"); 131 | super.updateNumMons(); 132 | 133 | // remove doom fee from caller's doom balance 134 | doomBalances[msg.sender] = doomBalances[msg.sender].sub(doomFee(msg.sender)); 135 | 136 | // mint the monster 137 | uint256 id = monMinter.mintMonster( 138 | // to 139 | msg.sender, 140 | // parent1Id 141 | 0, 142 | // parent2Id 143 | 0, 144 | // minterContract 145 | address(this), 146 | // contractOrder 147 | numMons, 148 | // gen 149 | 1, 150 | // bits 151 | uint256(blockhash(block.number.sub(1))), 152 | // exp 153 | 0, 154 | // rarity 155 | rarity 156 | ); 157 | 158 | // update the URI of the new mon to be the prefix plus the numMons 159 | string memory uri = string(abi.encodePacked(prefixURI, numMons.toString())); 160 | monMinter.setTokenURI(id, uri); 161 | 162 | // update the number of summons 163 | numSummons[msg.sender] = numSummons[msg.sender].add(1); 164 | 165 | // return new monster id 166 | return(id); 167 | } 168 | 169 | function setRarity(uint256 r) public onlyAdmin { 170 | rarity = r; 171 | } 172 | 173 | function setMaxStake(uint256 m) public onlyAdmin { 174 | maxStake = m; 175 | } 176 | 177 | function setBaseDoomFee(uint256 f) public onlyAdmin { 178 | baseDoomFee = f; 179 | } 180 | 181 | function setDoomMultiplier(uint256 m) public onlyAdmin { 182 | doomMultiplier = m; 183 | } 184 | 185 | // Allows admin to add new staker admins 186 | function setStakerAdminRole(address a) public onlyAdmin { 187 | grantRole(STAKER_ADMIN_ROLE, a); 188 | } 189 | 190 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyAdmin { 191 | require(tokenAddress != address(xmon), "Can't move XMON"); 192 | IERC20 _token = IERC20(tokenAddress); 193 | _token.safeTransfer(to, numTokens); 194 | } 195 | 196 | function setDoomBalances(address a, uint256 d) public onlyStakerAdmin { 197 | doomBalances[a] = d; 198 | } 199 | 200 | function pendingDoom(address a) public view returns(uint256) { 201 | uint256 doomAmount = stakeRecords[a].amount.mul(block.number.sub(stakeRecords[a].startBlock)); 202 | return(doomAmount); 203 | } 204 | 205 | function doomFee(address a) public view returns(uint256) { 206 | return (numSummons[a].mul(doomMultiplier)).add(baseDoomFee); 207 | } 208 | 209 | } -------------------------------------------------------------------------------- /old-test/ERC721SenderTests.js: -------------------------------------------------------------------------------- 1 | const truffleAssert = require('truffle-assertions'); 2 | const xmonArtifact = artifacts.require('./XMON.sol'); 3 | const monMinterArtifact = artifacts.require('./MonMinter.sol'); 4 | const erc721SenderArtifact = artifacts.require('./ERC721Sender.sol'); 5 | 6 | // Allows setting rewards to multiple people 7 | // Allows taking rewards to multiple people 8 | // Expect taking reward to revert if you are not a holder 9 | // Allows sending rewards to multiple people 10 | 11 | contract("erc721Sender tests", async accounts => { 12 | it ("allows setting/taking rewards to multiple people", async() => { 13 | 14 | let xmon = await xmonArtifact.deployed(); 15 | let monMinter = await monMinterArtifact.deployed(); 16 | let erc721Sender = await erc721SenderArtifact.deployed(); 17 | 18 | // Set A to be monMinter 19 | await monMinter.setMinterRole(accounts[0], {from: accounts[0]}); 20 | 21 | // Approve erc721Sender to spend A's XMON tokens 22 | await xmon.approve(erc721Sender.address, web3.utils.toBN('9999000000000000000000'), {from: accounts[0]}); 23 | 24 | // Mint NFTs to A,B,C,D,E 25 | await monMinter.mintMonster(accounts[0], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 26 | await monMinter.mintMonster(accounts[1], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 27 | await monMinter.mintMonster(accounts[2], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 28 | await monMinter.mintMonster(accounts[3], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 29 | await monMinter.mintMonster(accounts[4], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 30 | 31 | let startBalance = web3.utils.toBN(await xmon.balanceOf(accounts[0])); 32 | 33 | // Send tokens to A,B,C,D,E 34 | await erc721Sender.setRewards( 35 | [monMinter.address, monMinter.address, monMinter.address, monMinter.address, monMinter.address], 36 | [1,2,3,4,5], 37 | xmon.address, 38 | [1,2,3,4,5] 39 | ); 40 | 41 | let endBalance = web3.utils.toBN(await xmon.balanceOf(accounts[0])); 42 | let difference = startBalance.sub(endBalance); 43 | let expected = web3.utils.toBN(15); 44 | expect(difference.toString()).to.eql(expected.toString()); 45 | 46 | // Expect revert if someone else tries to claim the bounty 47 | for (let i = 1; i < 6; i++) { 48 | await truffleAssert.reverts( 49 | erc721Sender.takeRewards(monMinter.address, i, xmon.address, {from: accounts[5]}) 50 | ); 51 | } 52 | for (let i = 1; i < 6; i++) { 53 | await truffleAssert.reverts( 54 | erc721Sender.takeRewards(monMinter.address, i, xmon.address, {from: accounts[6]}) 55 | ); 56 | } 57 | 58 | // Allow taking rewards from A,B.C.D.E 59 | for (let i = 0; i < 5; i++) { 60 | await erc721Sender.takeRewards(monMinter.address, i+1, xmon.address, {from: accounts[i]}); 61 | } 62 | 63 | for (let i = 1; i < 5; i++) { 64 | let balance = await xmon.balanceOf(accounts[i]); 65 | expect(balance).to.eql(web3.utils.toBN(i+1)); 66 | } 67 | 68 | // Ensure second rewards don't increase balance 69 | for (let i = 0; i < 5; i++) { 70 | await erc721Sender.takeRewards(monMinter.address, i+1, xmon.address, {from: accounts[i]}); 71 | } 72 | for (let i = 1; i < 5; i++) { 73 | let balance = await xmon.balanceOf(accounts[i]); 74 | expect(balance).to.eql(web3.utils.toBN(i+1)); 75 | } 76 | }); 77 | 78 | }); 79 | 80 | contract("erc721Sender tests", async accounts => { 81 | it ("allows setting rewards to accumulate if set multiple times", async() => { 82 | 83 | let xmon = await xmonArtifact.deployed(); 84 | let monMinter = await monMinterArtifact.deployed(); 85 | let erc721Sender = await erc721SenderArtifact.deployed(); 86 | 87 | // Set A to be monMinter 88 | await monMinter.setMinterRole(accounts[0], {from: accounts[0]}); 89 | 90 | // Approve erc721Sender to spend A's XMON tokens 91 | await xmon.approve(erc721Sender.address, web3.utils.toBN('9999000000000000000000'), {from: accounts[0]}); 92 | 93 | // Mint NFTs to A,B (B has ID = 2) 94 | await monMinter.mintMonster(accounts[0], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 95 | await monMinter.mintMonster(accounts[1], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 96 | 97 | for (let i = 1; i < 11; i++) { 98 | await erc721Sender.setRewards( 99 | [monMinter.address], 100 | [2], 101 | xmon.address, 102 | [i] 103 | ); 104 | } 105 | 106 | // Take reward accumulated 107 | await erc721Sender.takeRewards(monMinter.address, 2, xmon.address, {from: accounts[1]}); 108 | balance = await xmon.balanceOf(accounts[1]); 109 | expect(balance).to.eql(web3.utils.toBN(55)); 110 | }); 111 | }); 112 | 113 | contract("erc721Sender tests", async accounts => { 114 | it ("allows sending rewards to multiple people", async() => { 115 | 116 | let xmon = await xmonArtifact.deployed(); 117 | let monMinter = await monMinterArtifact.deployed(); 118 | let erc721Sender = await erc721SenderArtifact.deployed(); 119 | 120 | // Set A to be monMinter 121 | await monMinter.setMinterRole(accounts[0], {from: accounts[0]}); 122 | 123 | // Approve erc721Sender to spend A's XMON tokens 124 | await xmon.approve(erc721Sender.address, web3.utils.toBN('9999000000000000000000'), {from: accounts[0]}); 125 | 126 | // Mint NFTs to A,B,C,D,E 127 | await monMinter.mintMonster(accounts[0], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 128 | await monMinter.mintMonster(accounts[1], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 129 | await monMinter.mintMonster(accounts[2], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 130 | await monMinter.mintMonster(accounts[3], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 131 | await monMinter.mintMonster(accounts[4], 0, 0, accounts[0], 0, 0, 0, 0, 0, {from: accounts[0]}); 132 | 133 | let startBalance = web3.utils.toBN(await xmon.balanceOf(accounts[0])); 134 | 135 | // Send tokens to A,B,C,D,E 136 | await erc721Sender.sendRewards( 137 | [monMinter.address, monMinter.address, monMinter.address, monMinter.address, monMinter.address], 138 | [1,2,3,4,5], 139 | xmon.address, 140 | [1,2,3,4,5] 141 | ); 142 | 143 | let endBalance = web3.utils.toBN(await xmon.balanceOf(accounts[0])); 144 | let difference = startBalance.sub(endBalance); 145 | let expected = web3.utils.toBN(14); 146 | expect(difference.toString()).to.eql(expected.toString()); 147 | 148 | // Expect revert if someone else tries to claim the bounty 149 | for (let i = 1; i < 6; i++) { 150 | await truffleAssert.reverts( 151 | erc721Sender.takeRewards(monMinter.address, i, xmon.address, {from: accounts[5]}) 152 | ); 153 | } 154 | for (let i = 1; i < 6; i++) { 155 | await truffleAssert.reverts( 156 | erc721Sender.takeRewards(monMinter.address, i, xmon.address, {from: accounts[6]}) 157 | ); 158 | } 159 | 160 | // Check balances for B.C.D.E 161 | for (let i = 1; i < 5; i++) { 162 | let balance = await xmon.balanceOf(accounts[i]); 163 | expect(balance).to.eql(web3.utils.toBN(i+1)); 164 | } 165 | }); 166 | 167 | }); -------------------------------------------------------------------------------- /coverage/lcov.info: -------------------------------------------------------------------------------- 1 | TN: 2 | SF:/home/owen/GitHub/0xmons-contracts/contracts/IMonMinter.sol 3 | FNF:0 4 | FNH:0 5 | LF:0 6 | LH:0 7 | BRF:0 8 | BRH:0 9 | end_of_record 10 | TN: 11 | SF:/home/owen/GitHub/0xmons-contracts/contracts/IXMON.sol 12 | FNF:0 13 | FNH:0 14 | LF:0 15 | LH:0 16 | BRF:0 17 | BRH:0 18 | end_of_record 19 | TN: 20 | SF:/home/owen/GitHub/0xmons-contracts/contracts/MonCreatorInstance.sol 21 | FN:23,onlyAdmin 22 | FN:28,updateNumMons 23 | FN:33,setMaxMons 24 | FN:37,setPrefixURI 25 | FNF:4 26 | FNH:4 27 | FNDA:74,onlyAdmin 28 | FNDA:41,updateNumMons 29 | FNDA:6,setMaxMons 30 | FNDA:4,setPrefixURI 31 | DA:24,74 32 | DA:25,42 33 | DA:29,41 34 | DA:30,40 35 | DA:34,6 36 | DA:38,4 37 | LF:6 38 | LH:6 39 | BRDA:24,1,0,42 40 | BRDA:24,1,1,32 41 | BRDA:29,2,0,40 42 | BRDA:29,2,1,1 43 | BRF:4 44 | BRH:4 45 | end_of_record 46 | TN: 47 | SF:/home/owen/GitHub/0xmons-contracts/contracts/MonMinter.sol 48 | FN:15,onlyAdmin 49 | FN:20,onlyMinter 50 | FN:35,constructor 51 | FN:76,mintMonster 52 | FN:105,modifyMon 53 | FN:146,setTokenURI 54 | FN:151,setBaseURI 55 | FN:156,moveTokens 56 | FN:162,setRarityTitle 57 | FN:167,setMinterRole 58 | FNF:10 59 | FNH:10 60 | FNDA:18,onlyAdmin 61 | FNDA:94,onlyMinter 62 | FNDA:3,constructor 63 | FNDA:44,mintMonster 64 | FNDA:3,modifyMon 65 | FNDA:41,setTokenURI 66 | FNDA:3,setBaseURI 67 | FNDA:1,moveTokens 68 | FNDA:1,setRarityTitle 69 | FNDA:5,setMinterRole 70 | DA:16,18 71 | DA:17,10 72 | DA:21,94 73 | DA:22,88 74 | DA:38,3 75 | DA:41,3 76 | DA:44,3 77 | DA:77,44 78 | DA:78,44 79 | DA:79,44 80 | DA:90,44 81 | DA:91,44 82 | DA:106,3 83 | DA:107,3 84 | DA:108,2 85 | DA:109,1 86 | DA:111,2 87 | DA:112,1 88 | DA:114,2 89 | DA:115,1 90 | DA:117,2 91 | DA:118,1 92 | DA:120,2 93 | DA:121,1 94 | DA:123,2 95 | DA:124,1 96 | DA:126,2 97 | DA:127,1 98 | DA:129,2 99 | DA:130,2 100 | DA:134,1 101 | DA:135,1 102 | DA:136,1 103 | DA:137,1 104 | DA:138,1 105 | DA:139,1 106 | DA:140,1 107 | DA:141,1 108 | DA:147,41 109 | DA:152,3 110 | DA:157,1 111 | DA:158,1 112 | DA:163,1 113 | DA:168,5 114 | LF:44 115 | LH:44 116 | BRDA:16,1,0,10 117 | BRDA:16,1,1,8 118 | BRDA:21,2,0,88 119 | BRDA:21,2,1,6 120 | BRDA:107,3,0,2 121 | BRDA:107,3,1,1 122 | BRDA:108,4,0,1 123 | BRDA:108,4,1,1 124 | BRDA:111,5,0,1 125 | BRDA:111,5,1,1 126 | BRDA:114,6,0,1 127 | BRDA:114,6,1,1 128 | BRDA:117,7,0,1 129 | BRDA:117,7,1,1 130 | BRDA:120,8,0,1 131 | BRDA:120,8,1,1 132 | BRDA:123,9,0,1 133 | BRDA:123,9,1,1 134 | BRDA:126,10,0,1 135 | BRDA:126,10,1,1 136 | BRDA:129,11,0,2 137 | BRDA:129,11,1,0 138 | BRF:22 139 | BRH:21 140 | end_of_record 141 | TN: 142 | SF:/home/owen/GitHub/0xmons-contracts/contracts/MonSpawner.sol 143 | FN:17,onlySpawnerAdmin 144 | FN:39,constructor 145 | FN:54,spawnNewMon 146 | FN:113,setMonUnlock 147 | FN:117,setInitialDelay 148 | FN:121,setExtraDelay 149 | FN:125,setSpawnFee 150 | FN:129,setSpawnerAdminRole 151 | FN:133,moveTokens 152 | FNF:9 153 | FNH:9 154 | FNDA:5,onlySpawnerAdmin 155 | FNDA:3,constructor 156 | FNDA:20,spawnNewMon 157 | FNDA:3,setMonUnlock 158 | FNDA:2,setInitialDelay 159 | FNDA:2,setExtraDelay 160 | FNDA:1,setSpawnFee 161 | FNDA:2,setSpawnerAdminRole 162 | FNDA:1,moveTokens 163 | DA:18,5 164 | DA:19,3 165 | DA:42,3 166 | DA:45,3 167 | DA:48,3 168 | DA:51,3 169 | DA:55,20 170 | DA:56,18 171 | DA:57,16 172 | DA:58,12 173 | DA:59,10 174 | DA:60,8 175 | DA:61,6 176 | DA:64,5 177 | DA:65,5 178 | DA:68,5 179 | DA:71,5 180 | DA:72,5 181 | DA:75,5 182 | DA:76,5 183 | DA:79,5 184 | DA:80,5 185 | DA:81,1 186 | DA:85,5 187 | DA:107,5 188 | DA:108,5 189 | DA:110,5 190 | DA:114,3 191 | DA:118,2 192 | DA:122,2 193 | DA:126,1 194 | DA:130,2 195 | DA:134,1 196 | DA:135,1 197 | LF:34 198 | LH:34 199 | BRDA:18,1,0,3 200 | BRDA:18,1,1,2 201 | BRDA:55,2,0,18 202 | BRDA:55,2,1,2 203 | BRDA:56,3,0,16 204 | BRDA:56,3,1,2 205 | BRDA:57,4,0,12 206 | BRDA:57,4,1,4 207 | BRDA:58,5,0,10 208 | BRDA:58,5,1,2 209 | BRDA:59,6,0,8 210 | BRDA:59,6,1,2 211 | BRDA:60,7,0,6 212 | BRDA:60,7,1,2 213 | BRDA:80,8,0,1 214 | BRDA:80,8,1,4 215 | BRF:16 216 | BRH:16 217 | end_of_record 218 | TN: 219 | SF:/home/owen/GitHub/0xmons-contracts/contracts/MonStaker.sol 220 | FN:51,onlyStakerAdmin 221 | FN:56,constructor 222 | FN:83,addStake 223 | FN:109,removeStake 224 | FN:123,emergencyRemoveStake 225 | FN:135,awardDoom 226 | FN:147,claimMon 227 | FN:201,resetDelay 228 | FN:209,setRarity 229 | FN:213,setMaxStake 230 | FN:217,setStartDelay 231 | FN:221,setResetFee 232 | FN:225,setDoomFee 233 | FN:229,setMaxDelay 234 | FN:235,setStakerAdminRole 235 | FN:239,moveTokens 236 | FN:245,setDoomBalances 237 | FN:249,setSummonDelay 238 | FN:253,setNextSummonTime 239 | FN:257,pendingDoom 240 | FNF:20 241 | FNH:20 242 | FNDA:12,onlyStakerAdmin 243 | FNDA:3,constructor 244 | FNDA:43,addStake 245 | FNDA:37,removeStake 246 | FNDA:1,emergencyRemoveStake 247 | FNDA:118,awardDoom 248 | FNDA:37,claimMon 249 | FNDA:31,resetDelay 250 | FNDA:1,setRarity 251 | FNDA:9,setMaxStake 252 | FNDA:4,setStartDelay 253 | FNDA:2,setResetFee 254 | FNDA:2,setDoomFee 255 | FNDA:2,setMaxDelay 256 | FNDA:2,setStakerAdminRole 257 | FNDA:2,moveTokens 258 | FNDA:2,setDoomBalances 259 | FNDA:2,setSummonDelay 260 | FNDA:2,setNextSummonTime 261 | FNDA:1,pendingDoom 262 | DA:52,12 263 | DA:53,6 264 | DA:59,3 265 | DA:62,3 266 | DA:65,3 267 | DA:68,3 268 | DA:71,3 269 | DA:74,3 270 | DA:77,3 271 | DA:80,3 272 | DA:86,43 273 | DA:89,43 274 | DA:92,43 275 | DA:95,41 276 | DA:101,41 277 | DA:102,35 278 | DA:106,41 279 | DA:111,37 280 | DA:114,37 281 | DA:117,37 282 | DA:120,37 283 | DA:125,1 284 | DA:128,1 285 | DA:131,1 286 | DA:137,118 287 | DA:138,75 288 | DA:139,75 289 | DA:142,75 290 | DA:150,37 291 | DA:153,37 292 | DA:154,37 293 | DA:155,35 294 | DA:158,35 295 | DA:161,35 296 | DA:164,35 297 | DA:167,35 298 | DA:168,1 299 | DA:172,35 300 | DA:194,35 301 | DA:195,35 302 | DA:198,35 303 | DA:203,31 304 | DA:206,31 305 | DA:210,1 306 | DA:214,9 307 | DA:218,4 308 | DA:222,2 309 | DA:226,2 310 | DA:230,2 311 | DA:231,2 312 | DA:236,2 313 | DA:240,2 314 | DA:241,0 315 | DA:242,0 316 | DA:246,2 317 | DA:250,2 318 | DA:254,2 319 | DA:258,1 320 | DA:259,1 321 | LF:59 322 | LH:57 323 | BRDA:52,1,0,6 324 | BRDA:52,1,1,6 325 | BRDA:92,2,0,41 326 | BRDA:92,2,1,2 327 | BRDA:101,3,0,35 328 | BRDA:101,3,1,6 329 | BRDA:137,4,0,75 330 | BRDA:137,4,1,43 331 | BRDA:153,5,0,37 332 | BRDA:153,5,1,0 333 | BRDA:154,6,0,35 334 | BRDA:154,6,1,2 335 | BRDA:167,7,0,1 336 | BRDA:167,7,1,34 337 | BRDA:230,8,0,2 338 | BRDA:230,8,1,0 339 | BRDA:240,9,0,0 340 | BRDA:240,9,1,2 341 | BRF:18 342 | BRH:15 343 | end_of_record 344 | TN: 345 | SF:/home/owen/GitHub/0xmons-contracts/contracts/UsesMon.sol 346 | FNF:0 347 | FNH:0 348 | LF:0 349 | LH:0 350 | BRF:0 351 | BRH:0 352 | end_of_record 353 | TN: 354 | SF:/home/owen/GitHub/0xmons-contracts/contracts/XMON.sol 355 | FN:17,constructor 356 | FN:22,setWhitelist 357 | FN:27,setTransferFee 358 | FN:33,transfer 359 | FN:46,transferFrom 360 | FN:58,moveTokens 361 | FNF:6 362 | FNH:5 363 | FNDA:3,constructor 364 | FNDA:8,setWhitelist 365 | FNDA:5,setTransferFee 366 | FNDA:44,transfer 367 | FNDA:77,transferFrom 368 | FNDA:0,moveTokens 369 | DA:18,3 370 | DA:19,3 371 | DA:23,8 372 | DA:28,5 373 | DA:29,5 374 | DA:34,44 375 | DA:35,3 376 | DA:36,3 377 | DA:37,3 378 | DA:38,3 379 | DA:40,41 380 | DA:42,44 381 | DA:47,77 382 | DA:48,36 383 | DA:49,36 384 | DA:50,36 385 | DA:51,36 386 | DA:53,77 387 | DA:54,77 388 | DA:55,77 389 | DA:59,0 390 | DA:60,0 391 | LF:22 392 | LH:20 393 | BRDA:28,1,0,5 394 | BRDA:28,1,1,0 395 | BRDA:34,2,0,3 396 | BRDA:34,2,1,41 397 | BRDA:47,3,0,36 398 | BRDA:47,3,1,41 399 | BRF:6 400 | BRH:5 401 | end_of_record 402 | -------------------------------------------------------------------------------- /contracts/MonStaker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./MonCreatorInstance.sol"; 7 | import "./IWhitelist.sol"; 8 | import "@openzeppelin/contracts/utils/Strings.sol"; 9 | 10 | contract MonStaker is MonCreatorInstance { 11 | 12 | using SafeMath for uint256; 13 | using Strings for uint256; 14 | using SafeERC20 for IERC20; 15 | 16 | bytes32 public constant STAKER_ADMIN_ROLE = keccak256("STAKER_ADMIN_ROLE"); 17 | 18 | uint256 public maxStake; 19 | 20 | struct Stake { 21 | uint256 amount; 22 | uint256 startBlock; 23 | } 24 | mapping(address => Stake) public stakeRecords; 25 | 26 | // amount of doom needed to summon monster 27 | uint256 public doomFee; 28 | 29 | // fee in XMON to pay to reset delay 30 | uint256 public resetFee; 31 | 32 | // starting delay 33 | uint256 public startDelay; 34 | 35 | // initial rarity 36 | uint256 public rarity; 37 | 38 | // maximum delay between summons 39 | uint256 public maxDelay; 40 | 41 | // the amount of doom accrued by each account 42 | mapping(address => uint256) public doomBalances; 43 | 44 | // the additional delay between mintings for each account 45 | mapping(address => uint256) public summonDelay; 46 | 47 | // the block at which each account can summon 48 | mapping(address => uint256) public nextSummonTime; 49 | 50 | // the reference for checking if this contract is whitelisted 51 | IWhitelist private whitelistChecker; 52 | 53 | modifier onlyStakerAdmin { 54 | require(hasRole(STAKER_ADMIN_ROLE, msg.sender), "Not staker admin"); 55 | _; 56 | } 57 | 58 | constructor(address xmonAddress, address monMinterAddress) public { 59 | 60 | // Give caller admin permissions 61 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 62 | 63 | // Make the caller admin a staker admin 64 | grantRole(STAKER_ADMIN_ROLE, msg.sender); 65 | 66 | // starting reset fee is 1 XMON to reset 67 | resetFee = 1 * (10**18); 68 | 69 | // starting delay is 6000 blocks, ~22 hours (assuming 6500 blocks a day) 70 | startDelay = 6000; 71 | 72 | // max delay is 48,000 blocks, ~8 days 73 | maxDelay = 48000; 74 | 75 | // starting rarity is 1 76 | rarity = 1; 77 | 78 | // set xmon instance 79 | xmon = IERC20(xmonAddress); 80 | 81 | // set whitelistChecker to also be the xmonAddress 82 | whitelistChecker = IWhitelist(xmonAddress); 83 | 84 | // set monMinter instance 85 | monMinter = IMonMinter(monMinterAddress); 86 | } 87 | 88 | function addStake(uint256 amount) public { 89 | 90 | require(whitelistChecker.whitelist(address(this)), "Staker not whitelisted!"); 91 | require(amount > 0, "Need to stake nonzero"); 92 | 93 | // award existing doom 94 | awardDoom(msg.sender); 95 | 96 | // update to total amount 97 | uint256 newAmount = stakeRecords[msg.sender].amount.add(amount); 98 | 99 | // ensure the total is less than max stake 100 | require(newAmount <= maxStake, "Exceeds max stake"); 101 | 102 | // update stake records 103 | stakeRecords[msg.sender] = Stake( 104 | newAmount, 105 | block.number 106 | ); 107 | 108 | // initialize the summonDelay if it's 0 109 | if (summonDelay[msg.sender] == 0) { 110 | summonDelay[msg.sender] = startDelay; 111 | } 112 | 113 | // transfer tokens to contract 114 | xmon.safeTransferFrom(msg.sender, address(this), amount); 115 | } 116 | 117 | function removeStake() public { 118 | // award doom 119 | awardDoom(msg.sender); 120 | emergencyRemoveStake(); 121 | } 122 | 123 | function emergencyRemoveStake() public { 124 | // calculate how much to award 125 | uint256 amountToTransfer = stakeRecords[msg.sender].amount; 126 | 127 | // remove stake records 128 | delete stakeRecords[msg.sender]; 129 | 130 | // transfer tokens back 131 | xmon.safeTransfer(msg.sender, amountToTransfer); 132 | } 133 | 134 | // Awards accumulated doom and resets startBlock 135 | function awardDoom(address a) public { 136 | // If there is an existing amount staked, add the current accumulated amount and reset the block number 137 | if (stakeRecords[a].amount != 0) { 138 | uint256 doomAmount = stakeRecords[a].amount.mul(block.number.sub(stakeRecords[a].startBlock)); 139 | doomBalances[a] = doomBalances[a].add(doomAmount); 140 | 141 | // reset the start block 142 | stakeRecords[a].startBlock = block.number; 143 | } 144 | } 145 | 146 | // Claim a monster 147 | function claimMon() public returns (uint256) { 148 | 149 | // award doom first 150 | awardDoom(msg.sender); 151 | 152 | // check conditions 153 | require(doomBalances[msg.sender] >= doomFee, "Not enough DOOM"); 154 | require(block.number >= nextSummonTime[msg.sender], "Time isn't up yet"); 155 | super.updateNumMons(); 156 | 157 | // remove doom fee from caller's doom balance 158 | doomBalances[msg.sender] = doomBalances[msg.sender].sub(doomFee); 159 | 160 | // update the next block where summon can happen 161 | nextSummonTime[msg.sender] = summonDelay[msg.sender] + block.number; 162 | 163 | // double the delay time 164 | summonDelay[msg.sender] = summonDelay[msg.sender].mul(2); 165 | 166 | // set it to be maxDelay if that's lower 167 | if (summonDelay[msg.sender] > maxDelay) { 168 | summonDelay[msg.sender] = maxDelay; 169 | } 170 | 171 | // mint the monster 172 | uint256 id = monMinter.mintMonster( 173 | // to 174 | msg.sender, 175 | // parent1Id 176 | 0, 177 | // parent2Id 178 | 0, 179 | // minterContract 180 | address(this), 181 | // contractOrder 182 | numMons, 183 | // gen 184 | 1, 185 | // bits 186 | uint256(blockhash(block.number.sub(1))), 187 | // exp 188 | 0, 189 | // rarity 190 | rarity 191 | ); 192 | 193 | // update the URI of the new mon to be the prefix plus the numMons 194 | string memory uri = string(abi.encodePacked(prefixURI, numMons.toString())); 195 | monMinter.setTokenURI(id, uri); 196 | 197 | // return new monster id 198 | return(id); 199 | } 200 | 201 | function resetDelay() public { 202 | // set delay to the starting value 203 | summonDelay[msg.sender] = startDelay; 204 | 205 | // move tokens to the XMON contract as fee 206 | xmon.safeTransferFrom(msg.sender, address(xmon), resetFee); 207 | } 208 | 209 | function setRarity(uint256 r) public onlyAdmin { 210 | rarity = r; 211 | } 212 | 213 | function setMaxStake(uint256 m) public onlyAdmin { 214 | maxStake = m; 215 | } 216 | 217 | function setStartDelay(uint256 s) public onlyAdmin { 218 | startDelay = s; 219 | } 220 | 221 | function setResetFee(uint256 f) public onlyAdmin { 222 | resetFee = f; 223 | } 224 | 225 | function setDoomFee(uint256 f) public onlyAdmin { 226 | doomFee = f; 227 | } 228 | 229 | function setMaxDelay(uint256 d) public onlyAdmin { 230 | require(maxDelay >= startDelay, "maxDelay too low"); 231 | maxDelay = d; 232 | } 233 | 234 | // Allows admin to add new staker admins 235 | function setStakerAdminRole(address a) public onlyAdmin { 236 | grantRole(STAKER_ADMIN_ROLE, a); 237 | } 238 | 239 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyAdmin { 240 | require(tokenAddress != address(xmon), "Can't move XMON"); 241 | IERC20 _token = IERC20(tokenAddress); 242 | _token.safeTransfer(to, numTokens); 243 | } 244 | 245 | function setDoomBalances(address a, uint256 d) public onlyStakerAdmin { 246 | doomBalances[a] = d; 247 | } 248 | 249 | function setSummonDelay(address a, uint256 d) public onlyStakerAdmin { 250 | summonDelay[a] = d; 251 | } 252 | 253 | function setNextSummonTime(address a, uint256 t) public onlyStakerAdmin { 254 | nextSummonTime[a] = t; 255 | } 256 | 257 | function pendingDoom(address a) public view returns(uint256) { 258 | uint256 doomAmount = stakeRecords[a].amount.mul(block.number.sub(stakeRecords[a].startBlock)); 259 | return(doomAmount); 260 | } 261 | } -------------------------------------------------------------------------------- /old-test/ImageRegistryTests.js: -------------------------------------------------------------------------------- 1 | const imageRegistryArtifact = artifacts.require('./ImageRegistry.sol'); 2 | 3 | contract("imageRegistry tests", async accounts => { 4 | it ("compares between binary and uint encodings for static images", async() => { 5 | let imageRegistry = await imageRegistryArtifact.deployed(); 6 | let binaryData = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00@\x00\x00\x00@\x04\x03\x00\x00\x00XGl\xed\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x000PLTE\x00\x00\x000\x01\x00o\x03\x00\x8e\x02\x00\xea\n\x00\xe6`\x06X\r\x06\xd8"\x03\x16$\x15\xa8U\x1e\xb1\x02\x01@D2\x9e\xa1v\xce\x9f<\xa5)\x06\xe9\xa1\x0e\x8e\x9f\xb3\xbc\x00\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\x07tIME\x07\xe5\x01\x0b\x15\x0c\x1a+\x06Y\xcd\x00\x00\x01\xf8IDAT\x18\x19\xed\xc1\xefK\x13q\x1c\x07\xf0\xf7mx\x0e\x16c9\x8a\xb2\x07]f\x81\xf6dP\xacz\xdcw\x8c\x0e\xeaA\xdeq\x19\xd5\xad\xa4\x03G\x0f\n7\x06B5\x08\xc2\xd8\x93`d\x8dt\xc2\xb4\x8d\xdd\x83\x18\xf5\xa4\x9a\x7f@?n\xd4\xa0\x1f\x14\xca(\xa9 Hhx\r1\xf9\x96\xf8\xc8\xf59\x1f\xf4\xd8\xd7\x0b\x1b\xfe\x8f\x1f\xeb\xea8\xd0\x87\xf5\x88\x11M\x81\xb3\x11\xb8\x12L\xb9\x04G\xdb\x0fuj\n\x8bK\xa0\xa5\x9eZ\x99\x83S\n\x0b+\x12H\xe9\x8boB\xc7\xb5Hb\xe1t\x1fH\x9b\xe6\n\xddG\x93I\xa6\x1ck\x80v\xde\x88=0\xe3J\xbc\xb8\x08\xda\xfe\xc9\xeeo\xb9\xdb\nc\xbc\x0c\x8a+o|/\x9d\xd2\x126\xe7K\xa0t\x19\xfbF+\xb9LXW\x7fWA\xe8\xc8\xf4\\\x10_\xa6\x1f\xde\xd7\x17\x96\xab\xa0\x94\xc6\xca\x9eg\xe9\xe7In\xdb\xefA\xb9\x81\x14\xa6{\xb3g\xf8\xab\xe6k8\xa9X\xe3WT\xcb\xdc\x06\'^1;3;~=\x08\'\x82\xd03Q\x1f\xbb\xd5\x0bG"\x9f\xa8\xdd,\xc7$P\x02M\t{>\xf5\x8f\xbe\xf0\xe7ARu\xcdx\xb4\xfb\xf0|\xed\x1ch6[\xfe\xd8(\x15\x02\\\x96@j\x86\xd9$v\x0c\x86~\xec\x05\xcd\xd8R,`\xc4\x9aN\x81\xb6\x19\xed\xc3\xd9z\xda\xaa \x87\xbf\xbc)\xb40$\xb7\xce\xdf\t3ww\x86\xb0\xa2\x8c\x16\x9e;.\xfe\x13\x82\xf5d\x11\xab\xd2XK\xd4\x07\xeb\r\xb4\xcdV\xb3X\xe5\xc1Z\xbe{\t~6o\xd7\xdf\xd6\xfc\x00\xbc\x8f\xcbh\x95\xfd\xa2\xaa\x9an\xf3\xfe\x13X\x11\xc5?\xda\xec\x88\xcd8\xd7/\xc3\x81\xcfV\xe2\xecZS\xfb\n\x07\xeeb\x9c\xc9\xaa\xac\xff\x92\x00\x11\x04\x1f\x93e32\xc5\xd4\x93\xa0\xb5\x0f\x0f\x98Gd\x93]]\x02-\xf69`F\x85\x00\x0b\x7f\x00I\xd0\x86:\xb7\x06\xe1\x1a\xca\xcf\xfbAq+\xb9\xae]\x03A\xcc\x85\xe0 \n\xc0\x1d\x84K\xc0\x86\rN\xfe\x00\xcef\xad\xb1qa\xa3\x00\x00\x00\x00%tEXtdate:create\x002021-01-11T21:12:26-08:00.\x033\xd8\x00\x00\x00%tEXtdate:modify\x002021-01-11T21:12:26-08:00_^\x8bd\x00\x00\x00\x00IEND\xaeB`\x82'; 7 | 8 | // console.log( web3.utils.fromAscii(binaryData)); 9 | // let encodedString = web3.utils.toAscii(tx.logs[0].args['0']); 10 | // console.log(encodedString === binaryData); 11 | 12 | console.log(web3.utils.fromAscii(binaryData).length); 13 | 14 | let tx = await imageRegistry.saveBytes( 15 | web3.utils.fromAscii(binaryData) 16 | ); 17 | 18 | console.log(tx.receipt.gasUsed); 19 | console.log("-----"); 20 | 21 | tx = await imageRegistry.saveUints([ 22 | web3.utils.toBN('182521206828400401493034671582069933138'), 23 | web3.utils.toBN('5070602402093798301505311754092'), 24 | web3.utils.toBN('315027035002387829655812384943984344161'), 25 | web3.utils.toBN('6646139988948124863512141713073242240'), 26 | web3.utils.toBN('175458115250649526167287534097931436266'), 27 | web3.utils.toBN('127605892237627071551331187840625147904'), 28 | web3.utils.toBN('64219875686008742515953612656182595074'), 29 | web3.utils.toBN('1215200360290825277823796738279220245'), 30 | web3.utils.toBN('223752271021134684006108486505928277157'), 31 | web3.utils.toBN('54534240170009787166263306171302693703'), 32 | web3.utils.toBN('90390262526283296499791583369570355173'), 33 | web3.utils.toBN('1386770150667049889397923940319970369'), 34 | web3.utils.toBN('111780292667872271061945378744317081614'), 35 | web3.utils.toBN('29758220382194167552753337170579348599'), 36 | web3.utils.toBN('186169362868427930510179309688395020038'), 37 | web3.utils.toBN('88004417151874079875551901136994534168'), 38 | web3.utils.toBN('326515529225043428599090522376091803720'), 39 | web3.utils.toBN('138863054815404521940451519878617565979'), 40 | web3.utils.toBN('338367056825943193026673793446991565240'), 41 | web3.utils.toBN('24324471070304721681341101633867980378'), 42 | web3.utils.toBN('204053758479295324470777695335712376930'), 43 | web3.utils.toBN('299679239994569806403454241562067435638'), 44 | web3.utils.toBN('295796008537293132718543984811228248539'), 45 | web3.utils.toBN('13810131433348984988570384621099240352'), 46 | web3.utils.toBN('154325351376827371743752498584794756188'), 47 | web3.utils.toBN('21764285539152409336019666505356437481'), 48 | web3.utils.toBN('307432953187058974909792475792400755686'), 49 | web3.utils.toBN('142521598942723941350548232737440609083'), 50 | web3.utils.toBN('167799622966929941925310861202805661608'), 51 | web3.utils.toBN('293991887206728499957794738926383064897'), 52 | web3.utils.toBN('109608361844171804535810851242459977768'), 53 | web3.utils.toBN('27926050391044039647691356260004529613'), 54 | web3.utils.toBN('287539915524713453694178604497390721706'), 55 | web3.utils.toBN('43240144789055079214644398695105656710'), 56 | web3.utils.toBN('234788120678562102345433709237385286232'), 57 | web3.utils.toBN('100793027217605371798526726366920866569'), 58 | web3.utils.toBN('167765379950056283945568443524404060842'), 59 | web3.utils.toBN('205277212760789526022951094841123591983'), 60 | web3.utils.toBN('259873470815448865378039134883650783658'), 61 | web3.utils.toBN('229954212211000274311177887757083320501'), 62 | web3.utils.toBN('20019392972237708048062487854429144320'), 63 | web3.utils.toBN('15280935502859016628235908450401075569'), 64 | web3.utils.toBN('58120915246078475903447381831791692736'), 65 | web3.utils.toBN('178185653446491566786010558410952540160'), 66 | web3.utils.toBN('49785148773994494460774253081665827941'), 67 | web3.utils.toBN('260592375215237098690396240089266737'), 68 | web3.utils.toBN('66763571419826891408649102387328843776'), 69 | web3.utils.toBN('194473237398415993987399468999534950'), 70 | web3.utils.toBN('160837605428940505141277413654234542394'), 71 | web3.utils.toBN('65392966994319576566851581721923249152'), 72 | web3.utils.toBN('5279712195050102914') 73 | ]); 74 | console.log(tx.receipt.gasUsed); 75 | }); 76 | }); 77 | 78 | contract("imageRegistry tests", async accounts => { 79 | it ("compares costs for bytes and uint", async() => { 80 | let imageRegistry = await imageRegistryArtifact.deployed(); 81 | 82 | let lore = "A mystic scholar who may be from another world entirely. He is the only Ancient God left alive in the modern world. Many of his writings have mysteriously found their ways into libraries and studies across the world."; 83 | 84 | 85 | // tricky encoding is way too wasteful 86 | // just do it normally lmao 87 | let encodedLore = lore.toLowerCase().split(""); 88 | encodedLore = encodedLore.map(function(c) { 89 | let char = c.charCodeAt(0) - 97; 90 | if (c == " ") { 91 | char = 26; 92 | } 93 | if (c == ".") { 94 | char = 27; 95 | } 96 | if (c == ",") { 97 | char = 28; 98 | } 99 | if (c == "(") { 100 | char = 29; 101 | } 102 | if (c == ")") { 103 | char = 30; 104 | } 105 | if (c == "-") { 106 | char = 31; 107 | } 108 | else if (char < 0 || char > 31) { 109 | char = 0; 110 | } 111 | // convert to fixed 5 bits 112 | char = ('00000'+char.toString(2)).slice(-5); 113 | return char; 114 | }); 115 | encodedLore = encodedLore.reduce((all,one,i) => { 116 | const ch = Math.floor(i/51); 117 | all[ch] = ''.concat((all[ch]||[]),one); 118 | return all 119 | }, []); 120 | encodedLore = encodedLore.map((s) => { 121 | return web3.utils.toBN(BigInt('0b' + s)); 122 | }) 123 | 124 | let tx = await imageRegistry.saveBytes( 125 | web3.utils.fromAscii(lore) 126 | ); 127 | 128 | console.log(tx.receipt.gasUsed); 129 | console.log("-----"); 130 | 131 | tx = await imageRegistry.saveUints(encodedLore); 132 | console.log(tx.receipt.gasUsed); 133 | }); 134 | }); -------------------------------------------------------------------------------- /contracts/MonStaker3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.6.8; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "./MonCreatorInstance.sol"; 7 | import "@openzeppelin/contracts/utils/Strings.sol"; 8 | import "./IDoomAdmin.sol"; 9 | 10 | contract MonStaker3 is MonCreatorInstance { 11 | 12 | using SafeMath for uint256; 13 | using Strings for uint256; 14 | using SafeERC20 for IERC20; 15 | 16 | bytes32 public constant STAKER_ADMIN_ROLE = keccak256("STAKER_ADMIN_ROLE"); 17 | 18 | // Offset for the URI 19 | int128 public uriOffset; 20 | 21 | // Start time for mintMon 22 | uint256 public claimMonStart; 23 | 24 | // Maximum amount of DOOM to migrate 25 | uint256 public maxDoomToMigrate; 26 | 27 | // Last migration date 28 | uint256 public lastMigrationDate; 29 | 30 | // Record of which addresses have migrated 31 | mapping(address => bool) public hasMigrated; 32 | 33 | // Previous staker reference 34 | IDoomAdmin public prevStaker; 35 | 36 | struct Stake { 37 | uint256 amount; 38 | uint256 startBlock; 39 | } 40 | mapping(address => Stake) public stakeRecords; 41 | 42 | // amount of base doom needed to summon monster 43 | uint256 public baseDoomFee; 44 | 45 | // the amount of doom accrued by each account 46 | mapping(address => uint256) public doomBalances; 47 | 48 | // initial rarity 49 | uint256 public rarity; 50 | 51 | modifier onlyStakerAdmin { 52 | require(hasRole(STAKER_ADMIN_ROLE, msg.sender), "Not staker admin"); 53 | _; 54 | } 55 | 56 | constructor(address xmonAddress, address monMinterAddress) public { 57 | // Give caller admin permissions 58 | _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); 59 | 60 | // Make the caller admin a staker admin 61 | grantRole(STAKER_ADMIN_ROLE, msg.sender); 62 | 63 | // starting rarity is 2 64 | rarity = 2; 65 | 66 | // set xmon instance 67 | xmon = IERC20(xmonAddress); 68 | 69 | // set monMinter instance 70 | monMinter = IMonMinter(monMinterAddress); 71 | 72 | // set max DOOM to migrate to be 666 times doom scaling 73 | maxDoomToMigrate = 666 * (10**21); 74 | 75 | // set new doom fee to be sqrt(12 ether)*6600*10 = approx 23.76*(10**13) = 2376*(10**11) 76 | baseDoomFee = 2376 * (10**11); 77 | 78 | // set prefixURI 79 | prefixURI = "mons2/"; 80 | 81 | // Point to previous staker 82 | prevStaker = IDoomAdmin(0xD06337A401B468657dE2f9d3E390cE5b21C3c1C0); 83 | 84 | // Set first time to claim and last migration date to be 2021, April 2, 10 am pacific time 85 | claimMonStart = 1617382800; 86 | lastMigrationDate = 1617382800; 87 | } 88 | 89 | // Taken from Uniswap 90 | function sqrt(uint y) public pure returns (uint z) { 91 | if (y > 3) { 92 | z = y; 93 | uint x = y / 2 + 1; 94 | while (x < z) { 95 | z = x; 96 | x = (y / x + x) / 2; 97 | } 98 | } else if (y != 0) { 99 | z = 1; 100 | } 101 | } 102 | 103 | function addStake(uint256 amount) public { 104 | require(amount > 0, "Need to stake nonzero"); 105 | 106 | // award existing doom 107 | awardDoom(msg.sender); 108 | 109 | // update to total amount 110 | uint256 newAmount = stakeRecords[msg.sender].amount.add(amount); 111 | 112 | // update stake records 113 | stakeRecords[msg.sender] = Stake( 114 | newAmount, 115 | block.number 116 | ); 117 | 118 | // transfer tokens to contract 119 | xmon.safeTransferFrom(msg.sender, address(this), amount); 120 | } 121 | 122 | function removeStake() public { 123 | // award doom 124 | awardDoom(msg.sender); 125 | emergencyRemoveStake(); 126 | } 127 | 128 | function emergencyRemoveStake() public { 129 | // calculate how much to award 130 | uint256 amountToTransfer = stakeRecords[msg.sender].amount; 131 | 132 | // remove stake records 133 | delete stakeRecords[msg.sender]; 134 | 135 | // transfer tokens back 136 | xmon.safeTransfer(msg.sender, amountToTransfer); 137 | } 138 | 139 | // Awards accumulated doom and resets startBlock 140 | function awardDoom(address a) public { 141 | // If there is an existing amount staked, add the current accumulated amount and reset the block number 142 | if (stakeRecords[a].amount != 0) { 143 | // DOOM = sqrt(staked balance) * number of blocks staked 144 | uint256 doomAmount = sqrt(stakeRecords[a].amount).mul(block.number.sub(stakeRecords[a].startBlock)); 145 | doomBalances[a] = doomBalances[a].add(doomAmount); 146 | 147 | // reset the start block 148 | stakeRecords[a].startBlock = block.number; 149 | } 150 | } 151 | 152 | // Claim a monster 153 | function claimMon() public returns (uint256) { 154 | require(block.timestamp >= claimMonStart, "Not time yet"); 155 | 156 | // award doom first 157 | awardDoom(msg.sender); 158 | 159 | // check conditions 160 | require(doomBalances[msg.sender] >= baseDoomFee, "Not enough DOOM"); 161 | super.updateNumMons(); 162 | 163 | // remove doom fee from caller's doom balance 164 | doomBalances[msg.sender] = doomBalances[msg.sender].sub(baseDoomFee); 165 | 166 | // update the offset of the new mon to be the prefix plus the numMons 167 | // if uriOffset is negative, it will subtract from numMons 168 | // otherwise, it adds to numMons 169 | uint256 offsetMons = uriOffset < 0 ? numMons - uint256(uriOffset) : numMons + uint256(uriOffset); 170 | 171 | // mint the monster 172 | uint256 id = monMinter.mintMonster( 173 | // to 174 | msg.sender, 175 | // parent1Id 176 | 0, 177 | // parent2Id 178 | 0, 179 | // minterContract (we don't actually care where it came from for now) 180 | address(0), 181 | // contractOrder (also set to be the offset) 182 | offsetMons, 183 | // gen 184 | 1, 185 | // bits 186 | 0, 187 | // exp 188 | 0, 189 | // rarity 190 | rarity 191 | ); 192 | string memory uri = string(abi.encodePacked(prefixURI, offsetMons.toString())); 193 | monMinter.setTokenURI(id, uri); 194 | 195 | // return new monster id 196 | return(id); 197 | } 198 | 199 | function migrateDoom() public { 200 | require(!hasMigrated[msg.sender], "Already migrated"); 201 | require(block.timestamp <= lastMigrationDate, "Time limit up"); 202 | uint256 totalDoom = prevStaker.pendingDoom(msg.sender) + prevStaker.doomBalances(msg.sender); 203 | if (totalDoom > maxDoomToMigrate) { 204 | totalDoom = maxDoomToMigrate; 205 | } 206 | totalDoom = (totalDoom*baseDoomFee)/maxDoomToMigrate; 207 | doomBalances[msg.sender] = totalDoom; 208 | hasMigrated[msg.sender] = true; 209 | } 210 | 211 | function setUriOffset(int128 o) public onlyAdmin { 212 | uriOffset = o; 213 | } 214 | 215 | function setLastMigrationDate(uint256 d) public onlyAdmin { 216 | lastMigrationDate = d; 217 | } 218 | 219 | function setMaxDoomToMigrate(uint256 m) public onlyAdmin { 220 | maxDoomToMigrate = m; 221 | } 222 | 223 | function setClaimMonStart(uint256 c) public onlyAdmin { 224 | claimMonStart = c; 225 | } 226 | 227 | function setRarity(uint256 r) public onlyAdmin { 228 | rarity = r; 229 | } 230 | 231 | function setBaseDoomFee(uint256 f) public onlyAdmin { 232 | baseDoomFee = f; 233 | } 234 | 235 | function setMonMinter(address a) public onlyAdmin { 236 | monMinter = IMonMinter(a); 237 | } 238 | 239 | function setPrevStaker(address a) public onlyAdmin { 240 | prevStaker = IDoomAdmin(a); 241 | } 242 | 243 | // Allows admin to add new staker admins 244 | function setStakerAdminRole(address a) public onlyAdmin { 245 | grantRole(STAKER_ADMIN_ROLE, a); 246 | } 247 | 248 | function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyAdmin { 249 | require(tokenAddress != address(xmon), "Can't move XMON"); 250 | IERC20 _token = IERC20(tokenAddress); 251 | _token.safeTransfer(to, numTokens); 252 | } 253 | 254 | function setDoomBalances(address a, uint256 d) public onlyStakerAdmin { 255 | doomBalances[a] = d; 256 | } 257 | 258 | function balanceOf(address a) public view returns(uint256) { 259 | return stakeRecords[a].amount; 260 | } 261 | 262 | function pendingDoom(address a) public view returns(uint256) { 263 | return(sqrt(stakeRecords[a].amount).mul(block.number.sub(stakeRecords[a].startBlock))); 264 | } 265 | } -------------------------------------------------------------------------------- /coverage/contracts/XMON.sol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code coverage report for contracts/XMON.sol 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |

19 | all files / contracts/ XMON.sol 20 |

21 |
22 |
23 | 90.91% 24 | Statements 25 | 20/22 26 |
27 |
28 | 83.33% 29 | Branches 30 | 5/6 31 |
32 |
33 | 83.33% 34 | Functions 35 | 5/6 36 |
37 |
38 | 90.91% 39 | Lines 40 | 20/22 41 |
42 |
43 |
44 |
45 |

 46 | 
230 | 
1 47 | 2 48 | 3 49 | 4 50 | 5 51 | 6 52 | 7 53 | 8 54 | 9 55 | 10 56 | 11 57 | 12 58 | 13 59 | 14 60 | 15 61 | 16 62 | 17 63 | 18 64 | 19 65 | 20 66 | 21 67 | 22 68 | 23 69 | 24 70 | 25 71 | 26 72 | 27 73 | 28 74 | 29 75 | 30 76 | 31 77 | 32 78 | 33 79 | 34 80 | 35 81 | 36 82 | 37 83 | 38 84 | 39 85 | 40 86 | 41 87 | 42 88 | 43 89 | 44 90 | 45 91 | 46 92 | 47 93 | 48 94 | 49 95 | 50 96 | 51 97 | 52 98 | 53 99 | 54 100 | 55 101 | 56 102 | 57 103 | 58 104 | 59 105 | 60 106 | 61 107 | 62  108 |   109 |   110 |   111 |   112 |   113 |   114 |   115 |   116 |   117 |   118 |   119 |   120 |   121 |   122 |   123 |   124 | 125 | 126 |   127 |   128 |   129 | 130 |   131 |   132 |   133 |   134 | 135 | 136 |   137 |   138 |   139 |   140 | 44× 141 | 142 | 143 | 144 | 145 |   146 | 41× 147 |   148 | 44× 149 |   150 |   151 |   152 |   153 | 77× 154 | 36× 155 | 36× 156 | 36× 157 | 36× 158 |   159 | 77× 160 | 77× 161 | 77× 162 |   163 |   164 |   165 |   166 |   167 |   168 |  
// SPDX-License-Identifier: AGPL-3.0-or-later
169 |  
170 | pragma solidity ^0.6.1;
171 |  
172 | import "@openzeppelin/contracts/access/Ownable.sol";
173 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
174 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
175 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
176 |  
177 | contract XMON is ERC20, Ownable {
178 |  
179 |   using SafeERC20 for IERC20;
180 |  
181 |   uint256 public transferFee;
182 |   mapping (address => bool) public whitelist;
183 |  
184 |   constructor() ERC20("XMON", "XMON") public {
185 |     _mint(msg.sender, 10000 ether);
186 |     transferFee = 1;
187 |   }
188 |  
189 |   function setWhitelist(address a, bool b) public onlyOwner {
190 |     whitelist[a] = b;
191 |   }
192 |  
193 |   // Transfer fee in integer percents
194 |   function setTransferFee(uint256 fee) public onlyOwner {
195 |     Erequire(fee <= 10, "Fee cannot be greater than 10%");
196 |     transferFee = fee;
197 |   }
198 |  
199 |   // Transfer recipient recives amount - fee
200 |   function transfer(address recipient, uint256 amount) public override returns (bool) {
201 |     if (whitelist[_msgSender()] == false) {
202 |       uint256 fee = transferFee.mul(amount).div(100);
203 |       uint amountLessFee = amount.sub(fee);
204 |       _transfer(_msgSender(), recipient, amountLessFee);
205 |       _transfer(_msgSender(), address(this), fee);
206 |     } else {
207 |       _transfer(_msgSender(), recipient, amount);
208 |     }
209 |     return true;
210 |   }
211 |  
212 |   // TransferFrom recipient receives amount - fee, sender's account is debited amount
213 |   function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
214 |     if (whitelist[recipient] == false) {
215 |       uint256 fee = transferFee.mul(amount).div(100);
216 |       uint amountLessFee = amount.sub(fee);
217 |       amount = amountLessFee;
218 |       _transfer(sender, address(this), fee);
219 |     }
220 |     _transfer(sender, recipient, amount);
221 |     _approve(sender, _msgSender(), allowance(sender,_msgSender()).sub(amount, "ERC20: transfer amount exceeds allowance"));
222 |     return true;
223 |   }
224 |  
225 |   function moveTokens(address tokenAddress, address to, uint256 numTokens) public onlyOwner {
226 |     IERC20 _token = IERC20(tokenAddress);
227 |     _token.safeTransfer(to, numTokens);
228 |   }
229 | }
231 |
232 |
233 | 237 | 238 | 239 | 246 | 247 | 248 | 249 | --------------------------------------------------------------------------------