From 0cf3ffbd159e352eb9689baacecd1100a1773d66 Mon Sep 17 00:00:00 2001 From: animocabrands <55173168+animocabrands@users.noreply.github.com> Date: Sat, 20 Jun 2020 18:08:04 +0800 Subject: [PATCH] Version 2.0.1 (#7) * 2.0.1 Co-authored-by: Jan-Paul Azucena Co-authored-by: Nathan Sala --- CHANGELOG.md | 11 +- README.md | 17 +- migrations/1_NftStaking.js | 107 ++----- package-lock.json | 287 +----------------- package.json | 3 +- src/constants.js | 51 ++++ src/index.js | 7 + src/utils/index.js | 5 + src/utils/rewardsPool.js | 23 ++ test/contracts/staking/constants.js | 16 +- .../staking/scenarios/Claim.scenario.js | 16 +- .../staking/scenarios/GasHeavy.scenario.js | 12 +- .../scenarios/MultiStakers.scenario.js | 8 +- .../scenarios/Preconditions.scenario.js | 18 +- test/utils/tokenHelper.js | 14 +- 15 files changed, 172 insertions(+), 423 deletions(-) create mode 100644 src/constants.js create mode 100644 src/index.js create mode 100644 src/utils/index.js create mode 100644 src/utils/rewardsPool.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cace8d..95e841a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.0.1 (20/06/2020) + +### New features +* The module now exports a `constants` and a `utils` object. +* Added a function to calculate a total rewards based on a rewards schedule. + +### Bugfixes +* Fixed a wrong import in the migration and restructured the script + ## 2.0.0 (17/06/2020) ### Breaking changes @@ -36,4 +45,4 @@ * Better abstraction of core staking features. ## 0.0.1 (15/04/2020) - * Initial commit. \ No newline at end of file + * Initial commit. diff --git a/README.md b/README.md index 5a946c9..369aab7 100644 --- a/README.md +++ b/README.md @@ -58,33 +58,32 @@ Please see the mock contracts used for the tests in `contracts/mocks/staking/` f _Staking_ is the mechanism by-which an NFT is transferred to the `NftStaking` staking contract, to be held for a period of time, in exchange for a claimable ERC20-based token payout (rewards). While staked, the staking contract maintains ownership of the NFT on behalf of the original owner until such time as that owner decides to withdraw, or _unstake_, the NFT from the staking contract. The unstaked NFT is then transferred back to the original owner. -Upon the initial stake of an NFT to the staking contract, the NFT will be "frozen" for a fixed duration (as specified by the `freezeLengthInCycles_` constructor argument) before being allowed to be unstaked from the staking contract. - -Before any staker can stake or unstake their NFTs from the staking contract, all outstanding claimable rewards must first be claimed. +Upon the initial stake of an NFT to the staking contract, the NFT will be "frozen" for a fixed duration (1 cycle) before being allowed to be unstaked from the staking contract. ### Periods and Cycles -Discrete units of time in staking are expressed in terms of either _periods_ or _cycles_. A cycle is defined as some number of seconds (as specified by the `cycleLengthInSeconds_` constructor argument), and a period is defined as some number of cycles (as specified by the `periodLengthInCycles_` constructor argument). While cycles are used to calculate a staker's entitlement to claimable rewards, based on a fixed reward pool allotment schedule, periods are used for claiming rewards based on a payout schedule. +Discrete units of time in staking are expressed in terms of either _periods_ or _cycles_. A cycle is defined as some number of seconds (as specified by the `cycleLengthInSeconds_` constructor argument), and a period is defined as some number of cycles (as specified by the `periodLengthInCycles_` constructor argument). Cycles are used to calculate a staker's entitlement to claimable rewards, based on a fixed reward pool allotment schedule, while periods are used for claiming rewards based on a payout schedule. ### Claiming -As mentioned in the [Staking](#staking) section above, claiming outstanding claimable rewards is a requirement prior to staking and unstaking any additional NFTs from the staking contract. While entitled rewards for staking accumulate every cycle, those rewards can only be claimed according to a payout schedule; that schedule is defined as one (1) period. This means that at least one period must elapse before the accumulated rewards for staking an NFT in any given period can be claimed. Or in other words, a staker can claim rewards once per payout period. +Entitled rewards accumulate for every cycle that passes since staking. Those rewards can only be claimed according to a payout schedule; that schedule is defined as one (1) period. This means that at least one period must elapse before the accumulated rewards for staking an NFT, in any given period, can be claimed. Or in other words, a staker can claim rewards once per payout period. The only period that cannot be claimed for by a staker is the current period, as it has not completed its full elapsed duration to be claimable. ### Snapshots -Snapshots are a historical record of changes in total staked weight over time. For every cycle in which an NFT is staked or unstaked, a new snapshot is created. This provides a means for calculating a staker's entitled proportion of rewards for every cycle of a period that they are claiming. +Snapshots are historical records of changes in total staked weight, over time. For every cycle in which an NFT is staked or unstaked, a new snapshot is created. This provides a means for calculating a staker's entitled proportion of rewards for every cycle of a period that they are claiming. There is a global snapshot history that tracks aggregate stake changes for all stakers, as well as a snapshot history for each staker to track their own stake changes. Snapshots have the following properties: -- Span at least one cycle, and at most one period. -- The span of one snapshot will never overlap with another. +- Spans at least one cycle. +- Can span multiple cycles over multiple periods. +- The span of one snapshot will never overlap with another (for any given staker). - Are arranged consecutively in sequence without skipping over cycles (i.e. there will never be a cycle in between two snapshots). -- Snapshots do not span multiple periods (i.e. a snapshot will not start in one period and end in a different period). +- Are removed from a staker's snapshot history as soon as a reward claim is made for the periods that cover the span of the snapshot. ## Testing diff --git a/migrations/1_NftStaking.js b/migrations/1_NftStaking.js index b8e8ba2..61d3d53 100644 --- a/migrations/1_NftStaking.js +++ b/migrations/1_NftStaking.js @@ -1,109 +1,42 @@ -const program = require('commander'); -const { NFCollectionMaskLength } = require('../src').constants; -const { BN } = require('@openzeppelin/test-helpers'); -const { fromWei, toWei } = require('web3-utils'); +const { fromWei } = require('web3-utils'); +const { DefaultNFMaskLength } = require('@animoca/ethereum-contracts-assets_inventory').constants; +const { DefaultCycleLengthInSeconds, DefaultPeriodLengthInCycles, ExamplePayoutSchedule, ExampleWeightsByRarity } = require('../src/constants'); +const { rewardsPoolFromSchedule } = require('../src/utils'); const NftStaking = artifacts.require("NftStakingMock"); const AssetsInventory = artifacts.require("AssetsInventoryMock"); const ERC20 = artifacts.require("ERC20WithOperatorsMock"); -const DayInSeconds = 86400; -const CycleLengthInSeconds = new BN(DayInSeconds); -const PeriodLengthInCycles = new BN(7); - -const RewardsTokenInitialBalance = toWei('320000000'); -const PayoutSchedule = [ // payouts are expressed in decimal form and need to be converted to wei - { startPeriod: 1, endPeriod: 4, payoutPerCycle: '2700000' }, - { startPeriod: 5, endPeriod: 5, payoutPerCycle: '2200000' }, - { startPeriod: 6, endPeriod: 6, payoutPerCycle: '2150000' }, - { startPeriod: 7, endPeriod: 7, payoutPerCycle: '2100000' }, - { startPeriod: 8, endPeriod: 8, payoutPerCycle: '2050000' }, - { startPeriod: 9, endPeriod: 9, payoutPerCycle: '2000000' }, - { startPeriod: 10, endPeriod: 10, payoutPerCycle: '1950000' }, - { startPeriod: 11, endPeriod: 11, payoutPerCycle: '1900000' }, - { startPeriod: 12, endPeriod: 12, payoutPerCycle: '1850000' }, - { startPeriod: 13, endPeriod: 13, payoutPerCycle: '1800000' }, - { startPeriod: 14, endPeriod: 14, payoutPerCycle: '1750000' }, - { startPeriod: 15, endPeriod: 15, payoutPerCycle: '1700000' }, - { startPeriod: 16, endPeriod: 16, payoutPerCycle: '1650000' }, - { startPeriod: 17, endPeriod: 17, payoutPerCycle: '1600000' }, - { startPeriod: 18, endPeriod: 18, payoutPerCycle: '1550000' }, - { startPeriod: 19, endPeriod: 19, payoutPerCycle: '1500000' }, - { startPeriod: 20, endPeriod: 20, payoutPerCycle: '1475000' }, - { startPeriod: 21, endPeriod: 21, payoutPerCycle: '1450000' }, - { startPeriod: 22, endPeriod: 22, payoutPerCycle: '1425000' }, - { startPeriod: 23, endPeriod: 23, payoutPerCycle: '1400000' }, - { startPeriod: 24, endPeriod: 24, payoutPerCycle: '1375000' }, -]; // total ~ 320,000,000 - -const RarityToWeightsMap = { - 0: 500,// Apex, - 1: 100,// Legendary, - 2: 50,// Epic, - 3: 50,// Epic, - 4: 10,// Rare, - 5: 10,// Rare, - 6: 10,// Rare, - 7: 1,// Common, - 8: 1,// Common, - 9: 1// Common, -}; +const RewardsPool = rewardsPoolFromSchedule(ExamplePayoutSchedule, DefaultPeriodLengthInCycles); module.exports = async (deployer, network, accounts) => { - switch (network) { - case "ganache": - await deployer.deploy(AssetsInventory, NFCollectionMaskLength); - this.nftContract = await AssetsInventory.deployed(); - await deployer.deploy(ERC20, RewardsTokenInitialBalance); - this.rewardsTokenContract = await ERC20.deployed(); - break; - case "rinkeby": - const nftContractAddressRinkeby = program.nftContractAddressRinkeby; - const ERC20BaseAddressRinkeby = program.ERC20BaseAddressRinkeby; - - this.nftContract = - nftContractAddressRinkeby ? - await AssetsInventory.at(nftContractAddressRinkeby) : - await AssetsInventory.new(NFCollectionMaskLength); - - this.rewardsTokenContract = - ERC20BaseAddressRinkeby ? - await ERC20.at(ERC20BaseAddressRinkeby) : - await ERC20.new(RewardsTokenInitialBalance); - - break; - case "mainnet": - - break; - default: - console.log(`Unknown network '${network}', stopping...`); - return; - - } - + this.inventoryContract = await AssetsInventory.new(DefaultNFMaskLength); + this.erc20Contract = await ERC20.new(RewardsPool); await deployer.deploy(NftStaking, - CycleLengthInSeconds, - PeriodLengthInCycles, - this.nftContract.address, - this.rewardsTokenContract.address, - Object.keys(RarityToWeightsMap), - Object.values(RarityToWeightsMap), + DefaultCycleLengthInSeconds, + DefaultPeriodLengthInCycles, + this.inventoryContract.address, + this.erc20Contract.address, + Object.keys(ExampleWeightsByRarity), + Object.values(ExampleWeightsByRarity), ); this.stakingContract = await NftStaking.deployed(); - // Enough to cover the whole payout schedule needs to be approved and will be transferred to the contract at start - await this.rewardsTokenContract.approve(this.stakingContract.address, RewardsTokenInitialBalance); - - for (schedule of PayoutSchedule) { + for (schedule of ExamplePayoutSchedule) { + console.log(`Setting schedule: ${fromWei(schedule.payoutPerCycle)} ERC20s per-cycle for periods ${schedule.startPeriod} to ${schedule.endPeriod}`); await this.stakingContract.setRewardsForPeriods( schedule.startPeriod, schedule.endPeriod, - toWei(schedule.payoutPerCycle) + schedule.payoutPerCycle ); } + console.log(`Approving ${fromWei(RewardsPool)} ERC20s to the staking contract for the reward pool before starting`); + await this.erc20Contract.approve(this.stakingContract.address, RewardsPool); + + console.log('Starting the staking schedule'); await this.stakingContract.start(); } diff --git a/package-lock.json b/package-lock.json index 7ae746f..58b1d7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@animoca/ethereum-contracts-nft_staking", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1701,14 +1701,9 @@ "integrity": "sha512-joF+s3243TY5cL7Z7y4h1JsJpUCf/kmFmj+eJar7Y2yNIGVcW961VyrAms75tjUysSuHaUQ3eQXjBEUJueT52A==", "dev": true, "requires": { - "bn.js": "4.11.8", - "eth-lib": "0.2.7", - "ethereum-bloom-filters": "^1.0.6", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "underscore": "1.9.1", - "utf8": "3.0.0" + "got": "9.6.0", + "swarm-js": "0.1.39", + "underscore": "1.9.1" } } } @@ -1957,73 +1952,6 @@ } } }, - "@truffle/codec": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@truffle/codec/-/codec-0.5.1.tgz", - "integrity": "sha512-zvXM/tRqOKldlm0iiYdkdyMpLMtwcWZnj2NfTWlBeL8yvgpHhNDxALNuHv9tiaRktckl/is80WiT9LL6mDn75Q==", - "requires": { - "big.js": "^5.2.2", - "bn.js": "^4.11.8", - "debug": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.partition": "^4.6.0", - "lodash.sum": "^4.0.2", - "semver": "^6.3.0", - "source-map-support": "^0.5.16", - "utf8": "^3.0.0", - "web3-utils": "1.2.1" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "eth-lib": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.7.tgz", - "integrity": "sha1-L5Pxex4jrsN1nNSj/iDBKGo/wco=", - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" - }, - "web3-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.1.tgz", - "integrity": "sha512-Mrcn3l58L+yCKz3zBryM6JZpNruWuT0OCbag8w+reeNROSGVlXzUQkU+gtAwc9JCZ7tKUyg67+2YUGqUjVcyBA==", - "requires": { - "bn.js": "4.11.8", - "eth-lib": "0.2.7", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randomhex": "0.1.5", - "underscore": "1.9.1", - "utf8": "3.0.0" - } - } - } - }, "@truffle/compile-solidity": { "version": "4.3.9", "resolved": "https://registry.npmjs.org/@truffle/compile-solidity/-/compile-solidity-4.3.9.tgz", @@ -2985,35 +2913,6 @@ } } }, - "@truffle/debug-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@truffle/debug-utils/-/debug-utils-4.1.1.tgz", - "integrity": "sha512-Gnhc2sa36GVU8GkmgDRqz8GjA1ui2eGlOdqdzwWNdn9kIgPr7/8+umYsawp30m65EImQMdreU4TXhztH7Gq71A==", - "requires": { - "@truffle/codec": "^0.5.1", - "@trufflesuite/chromafi": "^2.1.2", - "chalk": "^2.4.2", - "debug": "^4.1.0", - "highlight.js": "^9.15.8", - "highlightjs-solidity": "^1.0.12", - "node-dir": "0.1.17" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "@truffle/error": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@truffle/error/-/error-0.0.6.tgz", @@ -4507,33 +4406,6 @@ } } }, - "ansi-mark": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ansi-mark/-/ansi-mark-1.0.4.tgz", - "integrity": "sha1-HNS6jVfxXxCdaq9uycqXhsik7mw=", - "requires": { - "ansi-regex": "^3.0.0", - "array-uniq": "^1.0.3", - "chalk": "^2.3.2", - "strip-ansi": "^4.0.0", - "super-split": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", @@ -4649,11 +4521,6 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", @@ -4836,11 +4703,6 @@ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, "bignumber.js": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.1.1.tgz", @@ -5398,11 +5260,6 @@ } } }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -6153,11 +6010,6 @@ "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", "dev": true }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" - }, "detect-installed": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-installed/-/detect-installed-2.0.4.tgz", @@ -23773,28 +23625,6 @@ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.0" @@ -23829,16 +23659,6 @@ "integrity": "sha512-uxdj3Qn4cBoY1zNIe8BSiwvw14G9Nq99HWEqPqFSu/rBCFaz84C+N/FChpPcUjd6q+cVsXOdyafCIAx5LHhBEQ==", "dev": true }, - "highlight.js": { - "version": "9.18.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz", - "integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg==" - }, - "highlightjs-solidity": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-1.0.14.tgz", - "integrity": "sha512-NhU/f45QKrdJzX9+rw6AC9oUvBdl9QBzsVHya74yJ8FEl1BpZwkT+0u9u5qg3zta3Ofh4quVmr+aLR1Mrvzvcg==" - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -23959,16 +23779,6 @@ "strip-indent": "^2.0.0" } }, - "husky": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", - "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", - "requires": { - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -24174,14 +23984,6 @@ "ci-info": "^1.5.0" } }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "^1.5.0" - } - }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", @@ -24969,16 +24771,6 @@ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=" - }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -25051,11 +24843,6 @@ "integrity": "sha1-o45GtzRp4EILDaEhLmbUFL42S6Q=", "dev": true }, - "lodash.partition": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.partition/-/lodash.partition-4.6.0.tgz", - "integrity": "sha1-o45GtzRp4EILDaEhLmbUFL42S6Q=" - }, "lodash.rest": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.5.tgz", @@ -25068,11 +24855,6 @@ "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=", "dev": true }, - "lodash.sum": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lodash.sum/-/lodash.sum-4.0.2.tgz", - "integrity": "sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s=" - }, "lodash.template": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.2.4.tgz", @@ -25907,19 +25689,6 @@ "minimatch": "^3.0.2" } }, - "node-addon-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", - "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" - }, - "node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=", - "requires": { - "minimatch": "^3.0.2" - } - }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", @@ -27097,14 +26866,6 @@ "prepend-http": "^1.0.1" } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -28931,11 +28692,6 @@ "integrity": "sha512-I4bA5mgcb6Fw5UJ+EkpzqXfiuvVGS/7MuND+oBxNFmxu3ugLNrdIatzBLfhFRMVMLxgSsRy+TjIktgkF9RFSNQ==", "dev": true }, - "super-split": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/super-split/-/super-split-1.1.0.tgz", - "integrity": "sha512-I4bA5mgcb6Fw5UJ+EkpzqXfiuvVGS/7MuND+oBxNFmxu3ugLNrdIatzBLfhFRMVMLxgSsRy+TjIktgkF9RFSNQ==" - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -30286,19 +30042,6 @@ "graceful-fs": "^4.1.6" } }, - "js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -30389,28 +30132,6 @@ } } }, - "solc": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.5.17.tgz", - "integrity": "sha512-qpX+PGaU0Q3c6lh2vDzMoIbhv6bIrecI4bYsx+xUs01xsGFnY6Nr0L8y/QMyutTnrHN6Lb/Yl672ZVRqxka96w==", - "requires": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "dependencies": { - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" - } - } - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", diff --git a/package.json b/package.json index b77e1cd..726121c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@animoca/ethereum-contracts-nft_staking", - "version": "2.0.0", + "version": "2.0.1", "description": "Contracts for staking of ERC1155 NFTs vs claimable ERC20", "scripts": { "compile": "./node_modules/@animoca/ethereum-contracts-core_library/scripts/oz-compile.sh", @@ -8,6 +8,7 @@ "coverage": "./node_modules/@animoca/ethereum-contracts-core_library/scripts/coverage.sh", "migrate": "./node_modules/@animoca/ethereum-contracts-core_library/scripts/migrate.sh" }, + "main": "src/index.js", "repository": { "type": "git", "url": "git@github.com:animocabrands/ethereum-contracts-nft_staking.git" diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..a06599d --- /dev/null +++ b/src/constants.js @@ -0,0 +1,51 @@ +const { BN } = require('@openzeppelin/test-helpers'); +const { toWei } = require('web3-utils'); + +const DayInSeconds = 86400; + +const DefaultCycleLengthInSeconds = new BN(DayInSeconds); +const DefaultPeriodLengthInCycles = new BN(7); + +const ExamplePayoutSchedule = [ // payouts are expressed in decimal form and need to be converted to wei + { startPeriod: 1, endPeriod: 4, payoutPerCycle: toWei('2700000') }, + { startPeriod: 5, endPeriod: 5, payoutPerCycle: toWei('2200000') }, + { startPeriod: 6, endPeriod: 6, payoutPerCycle: toWei('2150000') }, + { startPeriod: 7, endPeriod: 7, payoutPerCycle: toWei('2100000') }, + { startPeriod: 8, endPeriod: 8, payoutPerCycle: toWei('2050000') }, + { startPeriod: 9, endPeriod: 9, payoutPerCycle: toWei('2000000') }, + { startPeriod: 10, endPeriod: 10, payoutPerCycle: toWei('1950000') }, + { startPeriod: 11, endPeriod: 11, payoutPerCycle: toWei('1900000') }, + { startPeriod: 12, endPeriod: 12, payoutPerCycle: toWei('1850000') }, + { startPeriod: 13, endPeriod: 13, payoutPerCycle: toWei('1800000') }, + { startPeriod: 14, endPeriod: 14, payoutPerCycle: toWei('1750000') }, + { startPeriod: 15, endPeriod: 15, payoutPerCycle: toWei('1700000') }, + { startPeriod: 16, endPeriod: 16, payoutPerCycle: toWei('1650000') }, + { startPeriod: 17, endPeriod: 17, payoutPerCycle: toWei('1600000') }, + { startPeriod: 18, endPeriod: 18, payoutPerCycle: toWei('1550000') }, + { startPeriod: 19, endPeriod: 19, payoutPerCycle: toWei('1500000') }, + { startPeriod: 20, endPeriod: 20, payoutPerCycle: toWei('1475000') }, + { startPeriod: 21, endPeriod: 21, payoutPerCycle: toWei('1450000') }, + { startPeriod: 22, endPeriod: 22, payoutPerCycle: toWei('1425000') }, + { startPeriod: 23, endPeriod: 23, payoutPerCycle: toWei('1400000') }, + { startPeriod: 24, endPeriod: 24, payoutPerCycle: toWei('1375000') }, +]; // total ~ 320,000,000 + +const ExampleWeightsByRarity = { + 0: 500, // Apex, + 1: 100, // Legendary, + 2: 50, // Epic, + 3: 50, // Epic, + 4: 10, // Rare, + 5: 10, // Rare, + 6: 10, // Rare, + 7: 1, // Common, + 8: 1, // Common, + 9: 1 // Common, +}; + +module.exports = { + DefaultCycleLengthInSeconds, + DefaultPeriodLengthInCycles, + ExamplePayoutSchedule, + ExampleWeightsByRarity, +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..928816f --- /dev/null +++ b/src/index.js @@ -0,0 +1,7 @@ +const utils = require('./utils'); +const constants = require('./constants'); + +module.exports = { + utils, + constants +}; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..5542174 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,5 @@ +const rewardsPool = require('./rewardsPool'); + +module.exports = { + ...rewardsPool +}; diff --git a/src/utils/rewardsPool.js b/src/utils/rewardsPool.js new file mode 100644 index 0000000..fc970a3 --- /dev/null +++ b/src/utils/rewardsPool.js @@ -0,0 +1,23 @@ +const { BN } = require('@openzeppelin/test-helpers'); + +/** + * Calculates the total rewards pool based for a staking schedule. + * @param {Array} schedule an array of objects as follow: { startPeriod: number, endPeriod: Number, payoutPerCycle: String } + * @param {BN} periodLengthInCycles the number of cycles in a period + */ +function rewardsPoolFromSchedule(schedule, periodLengthInCycles) { + return schedule.reduce( + ((total, schedule) => { + return total.add( + new BN(schedule.payoutPerCycle) + .mul(new BN(periodLengthInCycles)) + .mul(new BN(schedule.endPeriod - schedule.startPeriod + 1)) + ) + }), + new BN(0) + ); +} + +module.exports = { + rewardsPoolFromSchedule +} \ No newline at end of file diff --git a/test/contracts/staking/constants.js b/test/contracts/staking/constants.js index fe8b25c..11f6599 100644 --- a/test/contracts/staking/constants.js +++ b/test/contracts/staking/constants.js @@ -12,28 +12,28 @@ const PeriodLengthInSeconds = PeriodLengthInCycles.mul(CycleLengthInSeconds); const RarityWeights = [ { - rarity: TokenHelper.Rarity.Common, + rarity: TokenHelper.Rarities.Common, weight: 1 }, { - rarity: TokenHelper.Rarity.Epic, + rarity: TokenHelper.Rarities.Epic, weight: 10 }, { - rarity: TokenHelper.Rarity.Legendary, + rarity: TokenHelper.Rarities.Legendary, weight: 100 }, { - rarity: TokenHelper.Rarity.Apex, + rarity: TokenHelper.Rarities.Apex, weight: 500 } ]; const TokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Legendary, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Legendary, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const DefaultRewardSchedule = [ diff --git a/test/contracts/staking/scenarios/Claim.scenario.js b/test/contracts/staking/scenarios/Claim.scenario.js index cc92b2c..9ce0442 100644 --- a/test/contracts/staking/scenarios/Claim.scenario.js +++ b/test/contracts/staking/scenarios/Claim.scenario.js @@ -7,17 +7,17 @@ const { const { TokenIds } = require('../constants'); const OtherTokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Legendary, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Legendary, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const AnotherTokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Legendary, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Legendary, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const claimScenario = function (creator, staker, otherStaker, anotherStaker) { diff --git a/test/contracts/staking/scenarios/GasHeavy.scenario.js b/test/contracts/staking/scenarios/GasHeavy.scenario.js index 2fb8857..23b4706 100644 --- a/test/contracts/staking/scenarios/GasHeavy.scenario.js +++ b/test/contracts/staking/scenarios/GasHeavy.scenario.js @@ -7,15 +7,15 @@ const { shouldStakeNft, shouldUnstakeNft, shouldClaimRewards, shouldHaveGlobalHi const { TokenIds } = require('../constants'); const OtherTokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const AnotherTokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const gasHeavyScenario = function (creator, staker, otherStaker, anotherStaker) { diff --git a/test/contracts/staking/scenarios/MultiStakers.scenario.js b/test/contracts/staking/scenarios/MultiStakers.scenario.js index 6600c49..2bdc77a 100644 --- a/test/contracts/staking/scenarios/MultiStakers.scenario.js +++ b/test/contracts/staking/scenarios/MultiStakers.scenario.js @@ -5,10 +5,10 @@ const { shouldStakeNft, shouldEstimateRewards, shouldTimeWarpBy, initialiseDebug const { TokenIds } = require('../constants'); const OtherTokenIds = [ - TokenHelper.makeTokenId(TokenHelper.Rarity.Common, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Epic, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Legendary, TokenHelper.Type.Car), - TokenHelper.makeTokenId(TokenHelper.Rarity.Apex, TokenHelper.Type.Car) + TokenHelper.makeTokenId(TokenHelper.Rarities.Common, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Epic, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Legendary, TokenHelper.Types.Car), + TokenHelper.makeTokenId(TokenHelper.Rarities.Apex, TokenHelper.Types.Car) ]; const multiStakersScenario = function (creator, staker, otherStaker) { diff --git a/test/contracts/staking/scenarios/Preconditions.scenario.js b/test/contracts/staking/scenarios/Preconditions.scenario.js index 760e1d7..e4e5be3 100644 --- a/test/contracts/staking/scenarios/Preconditions.scenario.js +++ b/test/contracts/staking/scenarios/Preconditions.scenario.js @@ -25,22 +25,22 @@ const preconditionsScenario = function (staker) { }); it('should have assigned a weight of 1 for Common cars', async function () { - const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarity.Common); + const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarities.Common); weight.should.be.bignumber.equal(new BN(1)); }); it('should have assigned a weight of 10 for Epic cars', async function () { - const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarity.Epic); + const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarities.Epic); weight.should.be.bignumber.equal(new BN(10)); }); it('should have assigned a weight of 100 for Legendary cars', async function () { - const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarity.Legendary); + const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarities.Legendary); weight.should.be.bignumber.equal(new BN(100)); }); it('should have assigned a weight of 500 for Apex cars', async function () { - const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarity.Apex); + const weight = await this.stakingContract.weightByTokenAttribute(TokenHelper.Rarities.Apex); weight.should.be.bignumber.equal(new BN(500)); }); @@ -68,32 +68,32 @@ const preconditionsScenario = function (staker) { balance.should.be.bignumber.equal(new BN(1)); const tokenType = TokenHelper.getType(tokenId) - tokenType.should.be.equal(TokenHelper.Type.Car); + tokenType.should.be.equal(TokenHelper.Types.Car); } }); it('should have minted a Common car token for the staker', async function () { const tokenId = TokenIds[0]; const rarity = TokenHelper.getRarity(tokenId); - rarity.should.be.equal(TokenHelper.Rarity.Common); + rarity.should.be.equal(TokenHelper.Rarities.Common); }); it('should have minted an Epic car token for the staker', async function () { const tokenId = TokenIds[1]; const rarity = TokenHelper.getRarity(tokenId); - rarity.should.be.equal(TokenHelper.Rarity.Epic); + rarity.should.be.equal(TokenHelper.Rarities.Epic); }); it('should have minted an Apex car token for the staker', async function () { const tokenId = TokenIds[2]; const rarity = TokenHelper.getRarity(tokenId); - rarity.should.be.equal(TokenHelper.Rarity.Legendary); + rarity.should.be.equal(TokenHelper.Rarities.Legendary); }); it('should have minted an Apex car token for the staker', async function () { const tokenId = TokenIds[3]; const rarity = TokenHelper.getRarity(tokenId); - rarity.should.be.equal(TokenHelper.Rarity.Apex); + rarity.should.be.equal(TokenHelper.Rarities.Apex); }); }); diff --git a/test/utils/tokenHelper.js b/test/utils/tokenHelper.js index d83cd24..900ebf7 100644 --- a/test/utils/tokenHelper.js +++ b/test/utils/tokenHelper.js @@ -2,7 +2,7 @@ const { BN } = require('@openzeppelin/test-helpers'); const { inventoryIds } = require('@animoca/blockchain-inventory_metadata'); const { DefaultNFMaskLength } = require('@animoca/ethereum-contracts-assets_inventory').constants; -const Type = { +const Types = { None: 0, Car: 1, Driver: 2, @@ -12,7 +12,7 @@ const Type = { Track: 6 }; -const Rarity = { +const Rarities = { Common: 1, Epic: 2, Legendary: 3, @@ -55,9 +55,9 @@ function getRarity(tokenId) { } module.exports = { - Type: Type, - Rarity: Rarity, - makeTokenId: makeTokenId, - getType: getType, - getRarity: getRarity + Types, + Rarities, + makeTokenId, + getType, + getRarity };