From 44fc97d375a08e2e3eaf6c59945fce35545d2a8f Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Thu, 24 Sep 2020 02:59:09 +0000 Subject: [PATCH 01/11] add stake changes and tests --- contracts/mining.js | 278 ++++++++++++++++++ contracts/tokens.js | 61 +++- libs/Block.js | 1 + test/mining.js | 703 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1038 insertions(+), 5 deletions(-) create mode 100644 contracts/mining.js create mode 100644 test/mining.js diff --git a/contracts/mining.js b/contracts/mining.js new file mode 100644 index 00000000..7bab91f6 --- /dev/null +++ b/contracts/mining.js @@ -0,0 +1,278 @@ +/* eslint-disable no-await-in-loop */ +/* global actions, api */ + +actions.createSSC = async () => { + const tableExists = await api.db.tableExists('miningPower'); + if (tableExists === false) { + await api.db.createTable('miningPower', ['id', 'power']); + await api.db.createTable('pools', ['id']); + // Given symbol, output which pools are using it. + await api.db.createTable('tokenPools', ['symbol']); + await api.db.createTable('params'); + + const params = {}; + params.poolCreationFee = '1000'; + await api.db.insert('params', params); + } +}; + +actions.updateParams = async (payload) => { + if (api.sender !== api.owner) return; + + const { poolCreationFee } = payload; + + const params = await api.db.findOne('params', {}); + + params.poolCreationFee = poolCreationFee; + await api.db.update('params', params); +}; + +actions.updatePool = async (payload) => { + const { id } = payload; + + if (api.assert(id && typeof id === 'string', 'invalid params')) { + const pool = await api.db.findOne('pools', { id }); + + if (pool) { + if (api.assert(pool.owner === api.sender, 'must be the owner')) { + // TODO: update pool + } + } + } +}; + +async function validateTokenMiners(tokenMiners) { + if (!api.assert(tokenMiners && typeof tokenMiners === 'object')) return false; + const tokens = Object.keys(tokenMiners); + if (!api.assert(tokens.length >= 1 && tokens.length <= 2, 'only 1 or 2 tokenMiners allowed')) return false; + for (let i = 0; i < tokens.length; i += 1) { + const token = tokens[i]; + const tokenObject = await api.db.findOneInTable('tokens', 'tokens', { symbol: token }); + if (!api.assert(tokenObject && tokenObject.stakingEnabled, 'tokenMiners must have staking enabled')) return false; + if (!api.assert(Number.isInteger(tokenMiners[token]) && tokenMiners[token] >= 1 && tokenMiners[token] <= 100, 'tokenMiner multiplier must be an integer from 1 to 100')) return false; + } + return true; +} + +function generatePoolId(pool) { + const tokenMinerString = Object.keys(pool.tokenMiners).join('_'); + return `${pool.minedToken}-${tokenMinerString}`; +} + +const findAndProcessAll = async (contractName, table, query, callback) => { + let offset = 0; + let results = []; + let done = false; + while (!done) { + results = await api.db.findInTable(contractName, table, query, 1000, offset); + if (results) { + for (let i = 0; i < results.length; i += 1) { + await callback(results[i]); + } + if (results.length < 1000) { + done = true; + } else { + offset += 1000; + } + } + } +}; + +function computeMiningPower(miningPower, tokenMiners) { + let power = api.BigNumber(0); + Object.keys(tokenMiners).forEach((token) => { + if (miningPower.balances[token]) { + power = power.plus(api.BigNumber(miningPower.balances[token]) + .multipliedBy(tokenMiners[token])); + } + }); + return power; +} + +async function updateMiningPower(pool, token, account, stakedQuantity, delegatedQuantity) { + let miningPower = await api.db.findOne('miningPower', { id: pool.id, account }); + let stake = api.BigNumber(stakedQuantity); + let oldMiningPower = api.BigNumber(0); + stake = stake.plus(delegatedQuantity); + if (!miningPower) { + const balances = {}; + balances[token] = stake; + miningPower = { + id: pool.id, + account, + balances, + power: { $numberDecimal: '0' }, + }; + miningPower = await api.db.insert('miningPower', miningPower); + } else { + if (!miningPower.balances[token]) { + miningPower.balances[token] = '0'; + } + miningPower.balances[token] = stake.plus(miningPower.balances[token]); + oldMiningPower = miningPower.power.$numberDecimal; + } + const newMiningPower = computeMiningPower(miningPower, pool.tokenMiners); + miningPower.power = { $numberDecimal: newMiningPower }; + await api.db.update('miningPower', miningPower); + return newMiningPower.minus(oldMiningPower); +} + +async function initMiningPower(pool, token) { + let totalAdjusted = api.BigNumber(0); + await findAndProcessAll('tokens', 'balances', { symbol: token }, async (balance) => { + const adjusted = await updateMiningPower( + pool, token, balance.account, balance.stake, balance.delegationsIn, + ); + totalAdjusted = totalAdjusted.plus(adjusted); + }); + return totalAdjusted; +} + +actions.createPool = async (payload) => { + const { + lotteryWinners, lotteryIntervalHours, lotteryAmount, minedToken, tokenMiners, + isSignedWithActiveKey, + } = payload; + + // get contract params + const params = await api.db.findOne('params', {}); + const { poolCreationFee } = params; + + // get api.sender's UTILITY_TOKEN_SYMBOL balance + // eslint-disable-next-line no-template-curly-in-string + const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); + + const authorizedCreation = api.BigNumber(poolCreationFee).lte(0) + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(poolCreationFee); + + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fees') + && api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(minedToken && typeof minedToken === 'string' + && lotteryAmount && typeof lotteryAmount === 'string' && !api.BigNumber(lotteryAmount).isNaN(), 'invalid params')) { + if (api.assert(minedToken.length > 0 && minedToken.length <= 10, 'invalid symbol: uppercase letters only, max length of 10') + && api.assert(Number.isInteger(lotteryWinners) && lotteryWinners >= 1 && lotteryWinners <= 20, 'invalid lotteryWinners: integer between 1 and 20 only') + && api.assert(Number.isInteger(lotteryIntervalHours) && lotteryIntervalHours >= 1 && lotteryIntervalHours <= 720, 'invalid lotteryIntervalHours: integer between 1 and 720 only') + ) { + const minedTokenObject = await api.db.findOneInTable('tokens', 'tokens', { symbol: minedToken }); + + if (api.assert(minedTokenObject, 'minedToken does not exist') + && api.assert(minedTokenObject.issuer === api.sender, 'must be issuer of minedToken') + && api.assert(api.BigNumber(lotteryAmount).dp() <= minedTokenObject.precision, 'minedToken precision mismatch for lotteryAmount') + && await validateTokenMiners(tokenMiners)) { + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + const newPool = { + owner: api.sender, + minedToken, + lotteryWinners, + lotteryIntervalHours, + lotteryAmount, + tokenMiners, + active: true, + nextLotteryTimestamp: api.BigNumber(blockDate.getTime()) + .plus(lotteryIntervalHours * 3600 * 1000).toNumber(), + totalPower: '0', + }; + newPool.id = generatePoolId(newPool); + + const tokenMinerSymbols = Object.keys(tokenMiners); + for (let i = 0; i < tokenMinerSymbols.length; i += 1) { + const token = tokenMinerSymbols[i]; + await api.db.insert('tokenPools', { symbol: token, id: newPool.id }); + const adjustedPower = await initMiningPower(newPool, token); + newPool.totalPower = api.BigNumber(newPool.totalPower) + .plus(adjustedPower); + } + await api.db.insert('pools', newPool); + + // burn the token creation fees + if (api.BigNumber(poolCreationFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: poolCreationFee, isSignedWithActiveKey, + }); + } + } + } + } +}; + +actions.checkPendingLotteries = async () => { + if (api.assert(api.sender === 'null', 'not authorized')) { + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + const timestamp = blockDate.getTime(); + + await findAndProcessAll('mining', 'pools', + { + nextLotteryTimestamp: { + $lte: timestamp, + }, + }, + async (pool) => { + const winningNumbers = []; + const minedToken = await api.db.findOneInTable('tokens', 'tokens', + { symbol: pool.minedToken }); + const winningAmount = api.BigNumber(pool.lotteryAmount).dividedBy(pool.lotteryWinners) + .toFixed(minedToken.precision); + for (let i = 0; i < pool.lotteryWinners; i += 1) { + winningNumbers[i] = api.BigNumber(pool.totalPower).multipliedBy(api.random()); + api.emit('miningLotteryDebug', winningNumbers[i]); + } + // iterate power desc + let offset = 0; + let miningPowers; + let cumulativePower = api.BigNumber(0); + let nextCumulativePower = api.BigNumber(0); + let computedWinners = 0; + const winners = []; + while (computedWinners < pool.lotteryWinners) { + miningPowers = await api.db.find('miningPower', { id: pool.id, power: { $gt: { $numberDecimal: '0' } } }, 1000, offset, [{ index: 'power', descending: true }]); + for (let i = 0; i < miningPowers.length; i += 1) { + const miningPower = miningPowers[i]; + api.emit('miningLotteryDebug', miningPower); + nextCumulativePower = cumulativePower.plus(miningPower.power.$numberDecimal); + for (let j = 0; j < pool.lotteryWinners; j += 1) { + const currentWinningNumber = winningNumbers[j]; + if (cumulativePower.lte(currentWinningNumber) + && nextCumulativePower.gt(currentWinningNumber)) { + computedWinners += 1; + winners.push({ + winner: miningPower.account, + winningNumber: currentWinningNumber, + winningAmount, + }); + await api.executeSmartContract('tokens', 'issue', + { to: miningPower.account, symbol: minedToken.symbol, quantity: winningAmount }); + } + } + cumulativePower = nextCumulativePower; + } + if (computedWinners === pool.lotteryWinners || miningPowers.length < 1000) { + break; + } + offset += 1000; + } + api.emit('miningLottery', { poolId: pool.id, winners }); + }); + } +}; + +actions.handleStakeChange = async (payload) => { + const { + account, symbol, quantity, delegated, callingContractInfo, + } = payload; + if (api.assert(callingContractInfo.name === 'tokens' + && api.sender === api.owner, 'must be called from tokens contract')) { + await findAndProcessAll('mining', 'tokenPools', { symbol }, async (tokenPool) => { + const pool = await api.db.findOne('pools', { id: tokenPool.id }); + let adjusted; + if (delegated) { + adjusted = await updateMiningPower(pool, symbol, account, 0, quantity); + } else { + adjusted = await updateMiningPower(pool, symbol, account, quantity, 0); + } + pool.totalPower = adjusted.plus(pool.totalPower) + await api.db.update('pools', pool); + }); + } +}; diff --git a/contracts/tokens.js b/contracts/tokens.js index e340338a..efee6a8d 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -1152,12 +1152,16 @@ actions.create = async (payload) => { actions.issue = async (payload) => { const { to, symbol, quantity, isSignedWithActiveKey, + callingContractInfo, } = payload; - if (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') - && api.assert(to && typeof to === 'string' - && symbol && typeof symbol === 'string' - && quantity && typeof quantity === 'string' && !api.BigNumber(quantity).isNaN(), 'invalid params')) { + const fromMiningContract = (api.sender === 'null' + && callingContractInfo.name === 'mining'); + if (fromMiningContract + || (api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(to && typeof to === 'string' + && symbol && typeof symbol === 'string' + && quantity && typeof quantity === 'string' && !api.BigNumber(quantity).isNaN(), 'invalid params'))) { const finalTo = to.trim(); const token = await api.db.findOne('tokens', { symbol }); @@ -1165,7 +1169,7 @@ actions.issue = async (payload) => { // the api.sender must be the issuer // then we need to check that the quantity is correct if (api.assert(token !== null, 'symbol does not exist') - && api.assert(token.issuer === api.sender, 'not allowed to issue tokens') + && api.assert(fromMiningContract || token.issuer === api.sender, 'not allowed to issue tokens') && api.assert(countDecimals(quantity) <= token.precision, 'symbol precision mismatch') && api.assert(api.BigNumber(quantity).gt(0), 'must issue positive quantity') && api.assert(api.BigNumber(token.maxSupply).minus(token.supply).gte(quantity), 'quantity exceeds available supply')) { @@ -1478,6 +1482,8 @@ const processUnstake = async (unstake) => { if (symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'") { // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', { account }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account, symbol, quantity: api.BigNumber(tokensToRelease).negated() }); } } } @@ -1605,6 +1611,8 @@ actions.stake = async (payload) => { // await api.executeSmartContract // ('witnesses', 'updateWitnessesApprovals', { account: api.sender }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account: finalTo, symbol, quantity }); } } } @@ -1648,6 +1656,8 @@ actions.stakeFromContract = async (payload) => { // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', // { account: finalTo }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account: finalTo, symbol, quantity }); } } } @@ -1680,6 +1690,16 @@ const startUnstake = async (account, token, quantity) => { token.totalStaked, nextTokensToRelease, token.precision, false, ); await api.db.update('tokens', token); + // update witnesses rank + // eslint-disable-next-line no-template-curly-in-string + if (token.symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'") { + // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', { account }); + } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', { + account, + symbol: token.symbol, + quantity: api.BigNumber(nextTokensToRelease).negated(), + }); } } else { return false; @@ -1768,6 +1788,16 @@ const processCancelUnstake = async (unstake) => { await api.db.update('tokens', token); api.emit('unstake', { account, symbol, quantity: quantityLeft }); + + // update witnesses rank + // eslint-disable-next-line no-template-curly-in-string + if (symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'") { + // await api.executeSmartContract + // ('witnesses', 'updateWitnessesApprovals', { account: api.sender }); + } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account, symbol, quantity: tokensToRelease }); + return true; } } @@ -1968,6 +1998,12 @@ actions.delegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalTo }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { + account: finalTo, symbol, quantity, delegated: true, + }); + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); } else { // if a delegation already exists, increase it @@ -2007,6 +2043,12 @@ actions.delegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalTo }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { + account: finalTo, symbol, quantity, delegated: true, + }); + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); } } } @@ -2101,6 +2143,13 @@ actions.undelegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalFrom }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { + account: finalFrom, + symbol, + quantity: api.BigNumber(quantity).negated(), + delegated: true, + }); } } } @@ -2146,6 +2195,8 @@ const processUndelegation = async (undelegation) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account }); } + await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + { account, symbol, quantity }); } } }; diff --git a/libs/Block.js b/libs/Block.js index d92f7ad0..c5ac1d97 100644 --- a/libs/Block.js +++ b/libs/Block.js @@ -97,6 +97,7 @@ class Block { virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUnstakes', '')); virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUndelegations', '')); virtualTransactions.push(new Transaction(0, '', 'null', 'nft', 'checkPendingUndelegations', '')); + virtualTransactions.push(new Transaction(0, '', 'null', 'mining', 'checkPendingLotteries', '')); if (this.refHiveBlockNumber >= 45251626) { virtualTransactions.push(new Transaction(0, '', 'null', 'botcontroller', 'tick', '')); diff --git a/test/mining.js b/test/mining.js new file mode 100644 index 00000000..0c993f8e --- /dev/null +++ b/test/mining.js @@ -0,0 +1,703 @@ +/* eslint-disable */ +const { fork } = require('child_process'); +const assert = require('assert'); +const fs = require('fs-extra'); +const { MongoClient } = require('mongodb'); +const { Base64 } = require('js-base64'); + +const { Database } = require('../libs/Database'); +const blockchain = require('../plugins/Blockchain'); +const { Transaction } = require('../libs/Transaction'); + +const { CONSTANTS } = require('../libs/Constants'); + +const conf = { + chainId: "test-chain-id", + genesisSteemBlock: 2000000, + dataDirectory: "./test/data/", + databaseFileName: "database.db", + autosaveInterval: 0, + javascriptVMTimeout: 10000, + databaseURL: "mongodb://localhost:27017", + databaseName: "testssc", + streamNodes: ["https://api.steemit.com"], +}; + +let plugins = {}; +let jobs = new Map(); +let currentJobId = 0; +let database1 = null + +function send(pluginName, from, message) { + const plugin = plugins[pluginName]; + const newMessage = { + ...message, + to: plugin.name, + from, + type: 'request', + }; + currentJobId += 1; + newMessage.jobId = currentJobId; + plugin.cp.send(newMessage); + return new Promise((resolve) => { + jobs.set(currentJobId, { + message: newMessage, + resolve, + }); + }); +} + + +// function to route the IPC requests +const route = (message) => { + const { to, type, jobId } = message; + if (to) { + if (to === 'MASTER') { + if (type && type === 'request') { + // do something + } else if (type && type === 'response' && jobId) { + const job = jobs.get(jobId); + if (job && job.resolve) { + const { resolve } = job; + jobs.delete(jobId); + resolve(message); + } + } + } else if (type && type === 'broadcast') { + plugins.forEach((plugin) => { + plugin.cp.send(message); + }); + } else if (plugins[to]) { + plugins[to].cp.send(message); + } else { + console.error('ROUTING ERROR: ', message); + } + } +}; + +const loadPlugin = (newPlugin) => { + const plugin = {}; + plugin.name = newPlugin.PLUGIN_NAME; + plugin.cp = fork(newPlugin.PLUGIN_PATH, [], { silent: true }); + plugin.cp.on('message', msg => route(msg)); + plugin.cp.stdout.on('data', data => console.log(`[${newPlugin.PLUGIN_NAME}]`, data.toString())); + plugin.cp.stderr.on('data', data => console.error(`[${newPlugin.PLUGIN_NAME}]`, data.toString())); + + plugins[newPlugin.PLUGIN_NAME] = plugin; + + return send(newPlugin.PLUGIN_NAME, 'MASTER', { action: 'init', payload: conf }); +}; + +const unloadPlugin = (plugin) => { + plugins[plugin.PLUGIN_NAME].cp.kill('SIGINT'); + plugins[plugin.PLUGIN_NAME] = null; + jobs = new Map(); + currentJobId = 0; +} + +function setupContractPayload(name, file) { + let contractCode = fs.readFileSync(file); + contractCode = contractCode.toString(); + contractCode = contractCode.replace(/'\$\{CONSTANTS.UTILITY_TOKEN_PRECISION\}\$'/g, CONSTANTS.UTILITY_TOKEN_PRECISION); + contractCode = contractCode.replace(/'\$\{CONSTANTS.UTILITY_TOKEN_SYMBOL\}\$'/g, CONSTANTS.UTILITY_TOKEN_SYMBOL); + contractCode = contractCode.replace(/'\$\{CONSTANTS.HIVE_PEGGED_SYMBOL\}\$'/g, CONSTANTS.HIVE_PEGGED_SYMBOL); + + let base64ContractCode = Base64.encode(contractCode); + + return { + name, + params: '', + code: base64ContractCode, + }; +} + +const tokensContractPayload = setupContractPayload('tokens', './contracts/tokens.js'); +const contractPayload = setupContractPayload('mining', './contracts/mining.js'); + +async function assertUserBalances(account, symbol, balance, stake, delegationsIn) { + let res = await database1.findOne({ + contract: 'tokens', + table: 'balances', + query: { + account, + symbol, + } + }); + + assert.ok(res, `No balance for ${account}, ${symbol}`); + + assert.equal(res.balance, balance, `${account} has ${symbol} balance ${res.balance}, expected ${balance}`); + assert.equal(res.stake, stake, `${account} has ${symbol} stake ${res.stake}, expected ${stake}`); + if (delegationsIn) { + assert.equal(res.delegationsIn, delegationsIn, `${account} has ${symbol} delegationsIn ${res.delegationsIn}, expected ${delegationsIn}`); + } +} + +async function assertMiningPower(account, id, power) { + let res = await database1.findOne({ + contract: 'mining', + table: 'miningPower', + query: { + id, + account, + } + }); + assert.ok(res, `No power for ${account} in pool ${id}`); + + assert.equal(res.power['$numberDecimal'], power, `${account} has ${id} power ${res.power['$numberDecimal']}, expected ${power}`); +} + +async function assertPoolMiningPower(id, power) { + let res = await database1.findOne({ + contract: 'mining', + table: 'pools', + query: { + id, + } + }); + + assert.ok(res, `Pool ${id} not found.`); + + assert.equal(res.totalPower, power, `Pool ${id} has total power ${res.totalPower}, expected ${power}`); +} + +async function assertTokenPool(symbol, poolId) { + let res = await database1.findOne({ + contract: 'mining', + table: 'tokenPools', + query: { + symbol, + id: poolId, + } + }); + + assert.ok(res, `Token pool ${poolId} not found for ${symbol}.`); +} + +async function assertTotalStaked(amount, symbol='TKN') { + let res = await database1.findOne({ + contract: 'tokens', + table: 'tokens', + query: { + symbol, + }, + }); + + assert.equal(res.totalStaked, amount, `${symbol} has ${res.totalStaked} staked, expected ${amount}`); +} + +async function assertParams(key, value) { + let res = await database1.findOne({ + contract: 'tokens', + table: 'params', + query: {}, + }); + assert.equal(res[key], value, `Params for ${key} is ${res[key]}, expected ${value}`); +} + +function assertError(tx, message) { + const logs = JSON.parse(tx.logs); + assert(logs.errors, 'No error in logs. Error expected with message ' + message); + assert.equal(logs.errors[0], message, `Error expected with message ${message}. Instead got ${logs.errors[0]}`); +} + +async function assertNoErrorInLastBlock() { + const transactions = (await database1.getLatestBlockInfo()).transactions; + for (let i = 0; i < transactions.length; i++) { + const logs = JSON.parse(transactions[i].logs); + assert(!logs.errors, `Tx #${i} had unexpected error ${logs.errors}`); + } +} + +let txId = 1; +function getNextTxId() { + txId++; + return `TXID${txId.toString().padStart(8, "0")}`; +} + +// smart tokens +describe('mining', function () { + this.timeout(30000); + + before((done) => { + new Promise(async (resolve) => { + client = await MongoClient.connect(conf.databaseURL, { useNewUrlParser: true }); + db = await client.db(conf.databaseName); + await db.dropDatabase(); + resolve(); + }) + .then(() => { + done() + }) + }); + + after((done) => { + new Promise(async (resolve) => { + await client.close(); + resolve(); + }) + .then(() => { + done() + }) + }); + + beforeEach((done) => { + new Promise(async (resolve) => { + db = await client.db(conf.databaseName); + resolve(); + }) + .then(() => { + done() + }) + }); + + afterEach((done) => { + // runs after each test in this block + new Promise(async (resolve) => { + await db.dropDatabase() + resolve(); + }) + .then(() => { + done() + }) + }); + + it.only('should update mining power on stake update', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + + await database1.init(conf.databaseURL, conf.databaseName); + + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "7000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "MTKN", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "MTKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "30", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN_MTKN'); + await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + + await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); + await assertPoolMiningPower('TKN-TKN_MTKN', '150'); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi2", "symbol": "MTKN", "quantity": "10", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '40.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '30.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); + await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + + transactions = []; + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'delegate', '{ "to": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi2', 'tokens', 'delegate', '{ "to": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678903, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '35.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '5.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '5.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '25.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '75'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '125'); + await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + + transactions = []; + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'tokens', 'undelegate', '{ "from": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi2', 'tokens', 'undelegate', '{ "from": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678904, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '35.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '25.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '55'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '120'); + await assertPoolMiningPower('TKN-TKN_MTKN', '175'); + + transactions = []; + transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678905, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-02T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '40.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '30.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); + await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + + transactions = []; + const unstakeId = getNextTxId(); + const unstakeId2 = getNextTxId(); + transactions.push(new Transaction(12345678906, unstakeId, 'satoshi', 'tokens', 'unstake', '{ "symbol": "TKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678906, unstakeId2, 'satoshi2', 'tokens', 'unstake', '{ "symbol": "MTKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678906, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-02T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '39.99999998'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '29.99999998'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999998'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999992'); + await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999999'); + + transactions = []; + transactions.push(new Transaction(12345678907, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678907, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-03T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertUserBalances('satoshi', 'TKN', '40.00000002', '39.99999995'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); + await assertUserBalances('satoshi2', 'MTKN', '0.00000002', '29.99999995'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999996'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999984'); + await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999998'); + + transactions = []; + transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId}", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi2', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId2}", "isSignedWithActiveKey": true }`)); + + block = { + refHiveBlockNumber: 12345678908, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-02T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertUserBalances('satoshi', 'TKN', '40.00000002', '39.99999998'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); + await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); + await assertUserBalances('satoshi2', 'MTKN', '0.000000002', '29.99999998'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999998'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999992'); + await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999999'); + + + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + + //it('should update mining power on delegation update', (done) => { + //}); + + //it('should not create mining pool', (done) => { + //}); + + it('should run basic lottery', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + await database1.init(conf.databaseURL, conf.databaseName); + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "4000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "50", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN'); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '10.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN', '50'); + await assertMiningPower('satoshi2', 'TKN-TKN', '10'); + await assertPoolMiningPower('TKN-TKN', '60'); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T01:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = (await database1.getLatestBlockInfo()); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); + assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); + + await assertUserBalances('satoshi', 'TKN', '41.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '10.00000000'); + + // run a few more times and count frequencies + const winnerCount = { 'satoshi': 0, 'satoshi2': 0 }; + for (let i = 0; i < 10; i += 1) { + transactions = []; + transactions.push(new Transaction(12345678904 + i, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + block = { + refHiveBlockNumber: 12345678904 + i, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T02:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + res = (await database1.getLatestBlockInfo()); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + winnerCount[lotteryEvent.data.winners[0].winner] += 1; + } + assert.equal(Object.values(winnerCount).reduce((x,y) => x+y, 0), 10); + assert(winnerCount['satoshi'] > winnerCount['satoshi2']); + await assertUserBalances('satoshi', 'TKN', (41 + winnerCount['satoshi']).toFixed(8), '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', winnerCount['satoshi2'].toFixed(8), '10.00000000'); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + + it('should run basic lottery with 2 tokenMiners', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + await database1.init(conf.databaseURL, conf.databaseName); + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "7000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "MTKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "MTKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "30", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN_MTKN'); + await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + + await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); + await assertPoolMiningPower('TKN-TKN_MTKN', '150'); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T01:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = (await database1.getLatestBlockInfo()); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN-TKN_MTKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); + assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); + + await assertUserBalances('satoshi', 'TKN', '51.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + + // run a few more times and count frequencies + const winnerCount = { 'satoshi': 0, 'satoshi2': 0 }; + for (let i = 0; i < 20; i += 1) { + transactions = []; + transactions.push(new Transaction(12345678904 + i, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + block = { + refHiveBlockNumber: 12345678904 + i, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T02:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + res = (await database1.getLatestBlockInfo()); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN-TKN_MTKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + winnerCount[lotteryEvent.data.winners[0].winner] += 1; + } + assert.equal(Object.values(winnerCount).reduce((x,y) => x+y, 0), 20); + assert(winnerCount['satoshi'] < winnerCount['satoshi2']); + await assertUserBalances('satoshi', 'TKN', (51 + winnerCount['satoshi']).toFixed(8), '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', winnerCount['satoshi2'].toFixed(8), '20.00000000'); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + +}); From 16cd56ce23549a933d39b4fdd460956b0d5de8a6 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Fri, 25 Sep 2020 14:00:36 +0000 Subject: [PATCH 02/11] add tests, add update method --- contracts/mining.js | 155 ++++++++++++----- libs/Block.js | 2 + test/mining.js | 411 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 504 insertions(+), 64 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index 7bab91f6..ec79f47c 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -12,6 +12,7 @@ actions.createSSC = async () => { const params = {}; params.poolCreationFee = '1000'; + params.poolUpdateFee = '300'; await api.db.insert('params', params); } }; @@ -19,30 +20,36 @@ actions.createSSC = async () => { actions.updateParams = async (payload) => { if (api.sender !== api.owner) return; - const { poolCreationFee } = payload; + const { poolCreationFee, poolUpdateFee } = payload; const params = await api.db.findOne('params', {}); params.poolCreationFee = poolCreationFee; + params.poolUpdateFee = poolUpdateFee; await api.db.update('params', params); }; -actions.updatePool = async (payload) => { - const { id } = payload; - - if (api.assert(id && typeof id === 'string', 'invalid params')) { - const pool = await api.db.findOne('pools', { id }); - - if (pool) { - if (api.assert(pool.owner === api.sender, 'must be the owner')) { - // TODO: update pool +const findAndProcessAll = async (contractName, table, query, callback) => { + let offset = 0; + let results = []; + let done = false; + while (!done) { + results = await api.db.findInTable(contractName, table, query, 1000, offset); + if (results) { + for (let i = 0; i < results.length; i += 1) { + await callback(results[i]); + } + if (results.length < 1000) { + done = true; + } else { + offset += 1000; } } } }; async function validateTokenMiners(tokenMiners) { - if (!api.assert(tokenMiners && typeof tokenMiners === 'object')) return false; + if (!api.assert(tokenMiners && typeof tokenMiners === 'object', 'tokenMiners invalid')) return false; const tokens = Object.keys(tokenMiners); if (!api.assert(tokens.length >= 1 && tokens.length <= 2, 'only 1 or 2 tokenMiners allowed')) return false; for (let i = 0; i < tokens.length; i += 1) { @@ -54,29 +61,20 @@ async function validateTokenMiners(tokenMiners) { return true; } -function generatePoolId(pool) { - const tokenMinerString = Object.keys(pool.tokenMiners).join('_'); - return `${pool.minedToken}-${tokenMinerString}`; -} - -const findAndProcessAll = async (contractName, table, query, callback) => { - let offset = 0; - let results = []; - let done = false; - while (!done) { - results = await api.db.findInTable(contractName, table, query, 1000, offset); - if (results) { - for (let i = 0; i < results.length; i += 1) { - await callback(results[i]); - } - if (results.length < 1000) { - done = true; - } else { - offset += 1000; - } +function validateTokenMinersChange(oldTokenMiners, tokenMiners) { + const tokens = Object.keys(tokenMiners); + if (!api.assert(tokens.length === Object.keys(oldTokenMiners).length, 'cannot change tokenMiners keys')) return false; + let changed = false; + for (let i = 0; i < tokens.length; i += 1) { + const token = tokens[i]; + if (!api.assert(oldTokenMiners[token], 'can only modify existing tokenMiner multiplier')) return false; + if (!api.assert(Number.isInteger(tokenMiners[token]) && tokenMiners[token] >= 1 && tokenMiners[token] <= 100, 'tokenMiner multiplier must be an integer from 1 to 100')) return false; + if (tokenMiners[token] !== oldTokenMiners[token]) { + changed = true; } } -}; + return { changed }; +} function computeMiningPower(miningPower, tokenMiners) { let power = api.BigNumber(0); @@ -89,7 +87,7 @@ function computeMiningPower(miningPower, tokenMiners) { return power; } -async function updateMiningPower(pool, token, account, stakedQuantity, delegatedQuantity) { +async function updateMiningPower(pool, token, account, stakedQuantity, delegatedQuantity, reset) { let miningPower = await api.db.findOne('miningPower', { id: pool.id, account }); let stake = api.BigNumber(stakedQuantity); let oldMiningPower = api.BigNumber(0); @@ -105,7 +103,7 @@ async function updateMiningPower(pool, token, account, stakedQuantity, delegated }; miningPower = await api.db.insert('miningPower', miningPower); } else { - if (!miningPower.balances[token]) { + if (!miningPower.balances[token] || reset) { miningPower.balances[token] = '0'; } miningPower.balances[token] = stake.plus(miningPower.balances[token]); @@ -117,17 +115,88 @@ async function updateMiningPower(pool, token, account, stakedQuantity, delegated return newMiningPower.minus(oldMiningPower); } -async function initMiningPower(pool, token) { +async function initMiningPower(pool, token, reset) { let totalAdjusted = api.BigNumber(0); await findAndProcessAll('tokens', 'balances', { symbol: token }, async (balance) => { const adjusted = await updateMiningPower( - pool, token, balance.account, balance.stake, balance.delegationsIn, + pool, token, balance.account, balance.stake, balance.delegationsIn, reset, ); totalAdjusted = totalAdjusted.plus(adjusted); }); return totalAdjusted; } +actions.updatePool = async (payload) => { + const { + id, lotteryWinners, lotteryIntervalHours, lotteryAmount, tokenMiners, active, + isSignedWithActiveKey, + } = payload; + + // get contract params + const params = await api.db.findOne('params', {}); + const { poolUpdateFee } = params; + // get api.sender's UTILITY_TOKEN_SYMBOL balance + // eslint-disable-next-line no-template-curly-in-string + const utilityTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'" }); + + const authorized = api.BigNumber(poolUpdateFee).lte(0) + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(poolUpdateFee); + + if (api.assert(authorized, 'you must have enough tokens to cover the update fee') + && api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + && api.assert(id && typeof id === 'string' + && lotteryAmount && typeof lotteryAmount === 'string' && !api.BigNumber(lotteryAmount).isNaN() && api.BigNumber(lotteryAmount).gt(0), 'invalid params')) { + if (api.assert(Number.isInteger(lotteryWinners) && lotteryWinners >= 1 && lotteryWinners <= 20, 'invalid lotteryWinners: integer between 1 and 20 only') + && api.assert(Number.isInteger(lotteryIntervalHours) && lotteryIntervalHours >= 1 && lotteryIntervalHours <= 720, 'invalid lotteryIntervalHours: integer between 1 and 720 only')) { + const pool = await api.db.findOne('pools', { id }); + if (api.assert(pool, 'pool id not found')) { + const minedTokenObject = await api.db.findOneInTable('tokens', 'tokens', { symbol: pool.minedToken }); + if (api.assert(minedTokenObject && minedTokenObject.issuer === api.sender, 'must be issuer of minedToken') + && api.assert(api.BigNumber(lotteryAmount).dp() <= minedTokenObject.precision, 'minedToken precision mismatch for lotteryAmount')) { + const validMinersChange = validateTokenMinersChange(pool.tokenMiners, tokenMiners); + if (validMinersChange) { + pool.lotteryWinners = lotteryWinners; + pool.lotteryIntervalHours = lotteryIntervalHours; + pool.lotteryAmount = lotteryAmount; + pool.tokenMiners = tokenMiners; + pool.active = active; + + if (validMinersChange.changed) { + const tokenMinerSymbols = Object.keys(tokenMiners); + for (let i = 0; i < tokenMinerSymbols.length; i += 1) { + const token = tokenMinerSymbols[i]; + await api.db.insert('tokenPools', { symbol: token, id: pool.id }); + const adjustedPower = await initMiningPower(pool, token, true); + pool.totalPower = api.BigNumber(pool.totalPower).plus(adjustedPower); + } + } + + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + pool.nextLotteryTimestamp = api.BigNumber(blockDate.getTime()) + .plus(lotteryIntervalHours * 3600 * 1000).toNumber(); + + await api.db.update('pools', pool); + + // burn the token creation fees + if (api.BigNumber(poolUpdateFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: poolUpdateFee, isSignedWithActiveKey, + }); + } + } + } + } + } + } +}; + +function generatePoolId(pool) { + const tokenMinerString = Object.keys(pool.tokenMiners).join('_'); + return `${pool.minedToken}-${tokenMinerString}`; +} + actions.createPool = async (payload) => { const { lotteryWinners, lotteryIntervalHours, lotteryAmount, minedToken, tokenMiners, @@ -146,10 +215,10 @@ actions.createPool = async (payload) => { ? true : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(poolCreationFee); - if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fees') + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') && api.assert(minedToken && typeof minedToken === 'string' - && lotteryAmount && typeof lotteryAmount === 'string' && !api.BigNumber(lotteryAmount).isNaN(), 'invalid params')) { + && lotteryAmount && typeof lotteryAmount === 'string' && !api.BigNumber(lotteryAmount).isNaN() && api.BigNumber(lotteryAmount).gt(0), 'invalid params')) { if (api.assert(minedToken.length > 0 && minedToken.length <= 10, 'invalid symbol: uppercase letters only, max length of 10') && api.assert(Number.isInteger(lotteryWinners) && lotteryWinners >= 1 && lotteryWinners <= 20, 'invalid lotteryWinners: integer between 1 and 20 only') && api.assert(Number.isInteger(lotteryIntervalHours) && lotteryIntervalHours >= 1 && lotteryIntervalHours <= 720, 'invalid lotteryIntervalHours: integer between 1 and 720 only') @@ -162,7 +231,6 @@ actions.createPool = async (payload) => { && await validateTokenMiners(tokenMiners)) { const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const newPool = { - owner: api.sender, minedToken, lotteryWinners, lotteryIntervalHours, @@ -204,6 +272,7 @@ actions.checkPendingLotteries = async () => { await findAndProcessAll('mining', 'pools', { + active: true, nextLotteryTimestamp: { $lte: timestamp, }, @@ -216,7 +285,6 @@ actions.checkPendingLotteries = async () => { .toFixed(minedToken.precision); for (let i = 0; i < pool.lotteryWinners; i += 1) { winningNumbers[i] = api.BigNumber(pool.totalPower).multipliedBy(api.random()); - api.emit('miningLotteryDebug', winningNumbers[i]); } // iterate power desc let offset = 0; @@ -229,7 +297,6 @@ actions.checkPendingLotteries = async () => { miningPowers = await api.db.find('miningPower', { id: pool.id, power: { $gt: { $numberDecimal: '0' } } }, 1000, offset, [{ index: 'power', descending: true }]); for (let i = 0; i < miningPowers.length; i += 1) { const miningPower = miningPowers[i]; - api.emit('miningLotteryDebug', miningPower); nextCumulativePower = cumulativePower.plus(miningPower.power.$numberDecimal); for (let j = 0; j < pool.lotteryWinners; j += 1) { const currentWinningNumber = winningNumbers[j]; @@ -253,6 +320,10 @@ actions.checkPendingLotteries = async () => { offset += 1000; } api.emit('miningLottery', { poolId: pool.id, winners }); + // eslint-disable-next-line no-param-reassign + pool.nextLotteryTimestamp = api.BigNumber(blockDate.getTime()) + .plus(pool.lotteryIntervalHours * 3600 * 1000).toNumber(); + await api.db.update('pools', pool); }); } }; @@ -271,7 +342,7 @@ actions.handleStakeChange = async (payload) => { } else { adjusted = await updateMiningPower(pool, symbol, account, quantity, 0); } - pool.totalPower = adjusted.plus(pool.totalPower) + pool.totalPower = adjusted.plus(pool.totalPower); await api.db.update('pools', pool); }); } diff --git a/libs/Block.js b/libs/Block.js index c5ac1d97..9b807711 100644 --- a/libs/Block.js +++ b/libs/Block.js @@ -97,6 +97,8 @@ class Block { virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUnstakes', '')); virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUndelegations', '')); virtualTransactions.push(new Transaction(0, '', 'null', 'nft', 'checkPendingUndelegations', '')); + + // TODO: hive block number virtualTransactions.push(new Transaction(0, '', 'null', 'mining', 'checkPendingLotteries', '')); if (this.refHiveBlockNumber >= 45251626) { diff --git a/test/mining.js b/test/mining.js index 0c993f8e..014198d9 100644 --- a/test/mining.js +++ b/test/mining.js @@ -147,7 +147,8 @@ async function assertMiningPower(account, id, power) { assert.equal(res.power['$numberDecimal'], power, `${account} has ${id} power ${res.power['$numberDecimal']}, expected ${power}`); } -async function assertPoolMiningPower(id, power) { +async function assertPool(pool) { + const { id } = pool; let res = await database1.findOne({ contract: 'mining', table: 'pools', @@ -158,7 +159,9 @@ async function assertPoolMiningPower(id, power) { assert.ok(res, `Pool ${id} not found.`); - assert.equal(res.totalPower, power, `Pool ${id} has total power ${res.totalPower}, expected ${power}`); + Object.keys(pool).forEach(k => { + assert.equal(res[k], pool[k], `Pool ${id} has ${k} ${res[k]}, expected ${pool[k]}`); + }); } async function assertTokenPool(symbol, poolId) { @@ -262,7 +265,95 @@ describe('mining', function () { }) }); - it.only('should update mining power on stake update', (done) => { + it('should not create mining pool', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + + await database1.init(conf.databaseURL, conf.databaseName); + + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "3000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolCreationFee": 0 }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": 2, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "12345678901", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": "1", "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 0, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 21, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": "0", "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 0, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 721, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "NOTKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "satoshi", "quantity": "1000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": "tokenMiners", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "TKN2": 2, "TKN3": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "2000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"NOTKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": "garbage"}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 0}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 101}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "-1", "minedToken": "TKN", "tokenMiners": {"MTKN": 1}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = await database1.getLatestBlockInfo(); + let txs = res.transactions; + + assertError(txs[6], 'you must have enough tokens to cover the creation fee'); + assertError(txs[8], 'you must use a custom_json signed with your active key'); + assertError(txs[9], 'invalid params'); + assertError(txs[10], 'invalid params'); + assertError(txs[11], 'invalid symbol: uppercase letters only, max length of 10'); + assertError(txs[12], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[13], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[14], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[15], 'invalid lotteryIntervalHours: integer between 1 and 720 only'); + assertError(txs[16], 'invalid lotteryIntervalHours: integer between 1 and 720 only'); + assertError(txs[17], 'invalid lotteryIntervalHours: integer between 1 and 720 only'); + assertError(txs[18], 'minedToken does not exist'); + assertError(txs[20], 'must be issuer of minedToken'); + assertError(txs[21], 'minedToken precision mismatch for lotteryAmount'); + assertError(txs[22], 'tokenMiners invalid'); + assertError(txs[23], 'only 1 or 2 tokenMiners allowed'); + assertError(txs[26], 'tokenMiners must have staking enabled'); + assertError(txs[27], 'tokenMiners must have staking enabled'); + assertError(txs[29], 'tokenMiner multiplier must be an integer from 1 to 100'); + assertError(txs[30], 'tokenMiner multiplier must be an integer from 1 to 100'); + assertError(txs[31], 'tokenMiner multiplier must be an integer from 1 to 100'); + assertError(txs[32], 'invalid params'); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + + }); + + it('should update mining power on stake and delegation updates', (done) => { new Promise(async (resolve) => { await loadPlugin(blockchain); database1 = new Database(); @@ -309,7 +400,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); - await assertPoolMiningPower('TKN-TKN_MTKN', '150'); + await assertPool({id: 'TKN-TKN_MTKN', totalPower: '150'}); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); @@ -332,7 +423,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); - await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); transactions = []; transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'delegate', '{ "to": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); @@ -357,7 +448,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '75'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '125'); - await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); transactions = []; transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'tokens', 'undelegate', '{ "from": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); @@ -382,7 +473,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '55'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '120'); - await assertPoolMiningPower('TKN-TKN_MTKN', '175'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '175' }); transactions = []; transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -404,7 +495,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); - await assertPoolMiningPower('TKN-TKN_MTKN', '200'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); transactions = []; const unstakeId = getNextTxId(); @@ -429,7 +520,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999998'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999992'); - await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999999'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.9999999' }); transactions = []; transactions.push(new Transaction(12345678907, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -451,7 +542,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999996'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999984'); - await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999998'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.9999998' }); transactions = []; transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId}", "isSignedWithActiveKey": true }`)); @@ -470,13 +561,187 @@ describe('mining', function () { await assertUserBalances('satoshi', 'TKN', '40.00000002', '39.99999998'); await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000', '0.00000000'); await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); - await assertUserBalances('satoshi2', 'MTKN', '0.000000002', '29.99999998'); + await assertUserBalances('satoshi2', 'MTKN', '0.00000002', '29.99999998'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999998'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999992'); - await assertPoolMiningPower('TKN-TKN_MTKN', '199.9999999'); + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999999'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999996'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.99999995' }); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + + it('should not update mining pool', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + + await database1.init(conf.databaseURL, conf.databaseName); + + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "3200", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN_MTKN'); + await assertPool({id: 'TKN-TKN_MTKN', totalPower: '0', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "300", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": 2, "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "blah", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "-15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2.7, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 0, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 21, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN9", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolUpdateFee": 0 }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "NOTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 101}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 0}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": "a"}, "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = await database1.getLatestBlockInfo(); + let txs = res.transactions; + + assertError(txs[0], 'you must have enough tokens to cover the update fee'); + assertError(txs[2], 'you must use a custom_json signed with your active key'); + assertError(txs[3], 'invalid params'); + assertError(txs[4], 'invalid params'); + assertError(txs[5], 'invalid params'); + assertError(txs[6], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[7], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[8], 'invalid lotteryWinners: integer between 1 and 20 only'); + assertError(txs[9], 'pool id not found'); + assertError(txs[11], 'must be issuer of minedToken'); + assertError(txs[12], 'minedToken precision mismatch for lotteryAmount'); + assertError(txs[13], 'cannot change tokenMiners keys'); + assertError(txs[14], 'can only modify existing tokenMiner multiplier'); + assertError(txs[15], 'tokenMiner multiplier must be an integer from 1 to 100'); + assertError(txs[16], 'tokenMiner multiplier must be an integer from 1 to 100'); + assertError(txs[17], 'tokenMiner multiplier must be an integer from 1 to 100'); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + + it('should update mining pool', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + + await database1.init(conf.databaseURL, conf.databaseName); + + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "7300", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "MTKN", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "MTKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "30", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "11", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN_MTKN'); + await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + + await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + await assertUserBalances('satoshi', 'MTKN', '84.00000000', '5.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '11.00000000'); + + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '64'); + await assertPool({id: 'TKN-TKN_MTKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN_MTKN'); + await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + await assertUserBalances('satoshi', 'MTKN', '84.00000000', '5.00000000'); + await assertUserBalances('satoshi2', 'MTKN', 0, '11.00000000'); + await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '75'); + await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '73'); + await assertPool({id: 'TKN-TKN_MTKN', totalPower: '148', lotteryWinners: 2, lotteryIntervalHours: 3, lotteryAmount: "15.7", nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime() }); resolve(); }) @@ -487,11 +752,109 @@ describe('mining', function () { }); }); - //it('should update mining power on delegation update', (done) => { - //}); + it.only('should not run basic lottery when inactive', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + await database1.init(conf.databaseURL, conf.databaseName); + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "4000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "50", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": false, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN-TKN'); + + await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime(), active: false }); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T01:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = (await database1.getLatestBlockInfo()); + + assert(res.virtualTransactions.length === 0); + + transactions = []; + transactions.push(new Transaction(12345678903, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": true, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: 12345678903, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T02:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); - //it('should not create mining pool', (done) => { - //}); + res = (await database1.getLatestBlockInfo()); + assert(res.virtualTransactions.length === 0); + + await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime(), active: true }); + + transactions = []; + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678904, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T03:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + res = (await database1.getLatestBlockInfo()); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi2"); + assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); + + await assertUserBalances('satoshi', 'TKN', '40.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', '1.00000000', '10.00000000'); + + + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); it('should run basic lottery', (done) => { new Promise(async (resolve) => { @@ -529,7 +892,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN', '50'); await assertMiningPower('satoshi2', 'TKN-TKN', '10'); - await assertPoolMiningPower('TKN-TKN', '60'); + await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -557,14 +920,16 @@ describe('mining', function () { // run a few more times and count frequencies const winnerCount = { 'satoshi': 0, 'satoshi2': 0 }; + const lotteryDate = new Date('2018-06-01T01:00:00.000Z'); for (let i = 0; i < 10; i += 1) { transactions = []; transactions.push(new Transaction(12345678904 + i, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + lotteryDate.setHours(lotteryDate.getHours() + i + 1); block = { refHiveBlockNumber: 12345678904 + i, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', - timestamp: '2018-06-01T02:00:00', + timestamp: lotteryDate.toISOString().replace('.000Z', ''), transactions, }; await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); @@ -637,7 +1002,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); - await assertPoolMiningPower('TKN-TKN_MTKN', '150'); + await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -665,14 +1030,16 @@ describe('mining', function () { // run a few more times and count frequencies const winnerCount = { 'satoshi': 0, 'satoshi2': 0 }; + const lotteryDate = new Date('2018-06-01T01:00:00.000Z'); for (let i = 0; i < 20; i += 1) { transactions = []; transactions.push(new Transaction(12345678904 + i, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + lotteryDate.setHours(lotteryDate.getHours() + i + 1); block = { refHiveBlockNumber: 12345678904 + i, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', - timestamp: '2018-06-01T02:00:00', + timestamp: lotteryDate.toISOString().replace('.000Z', ''), transactions, }; await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); From 40f2f4ffda843072187ccfa81216d7987dff1f29 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Fri, 25 Sep 2020 21:03:12 +0000 Subject: [PATCH 03/11] do not use general maps, use arrays for balances + tokenMiners, no key can have "." in mongodb --- contracts/mining.js | 104 ++++++++++-------- test/mining.js | 259 ++++++++++++++++++++++++++------------------ 2 files changed, 212 insertions(+), 151 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index ec79f47c..f69b6789 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -49,27 +49,35 @@ const findAndProcessAll = async (contractName, table, query, callback) => { }; async function validateTokenMiners(tokenMiners) { - if (!api.assert(tokenMiners && typeof tokenMiners === 'object', 'tokenMiners invalid')) return false; - const tokens = Object.keys(tokenMiners); - if (!api.assert(tokens.length >= 1 && tokens.length <= 2, 'only 1 or 2 tokenMiners allowed')) return false; - for (let i = 0; i < tokens.length; i += 1) { - const token = tokens[i]; - const tokenObject = await api.db.findOneInTable('tokens', 'tokens', { symbol: token }); - if (!api.assert(tokenObject && tokenObject.stakingEnabled, 'tokenMiners must have staking enabled')) return false; - if (!api.assert(Number.isInteger(tokenMiners[token]) && tokenMiners[token] >= 1 && tokenMiners[token] <= 100, 'tokenMiner multiplier must be an integer from 1 to 100')) return false; + if (!api.assert(tokenMiners && Array.isArray(tokenMiners), 'tokenMiners invalid')) return false; + if (!api.assert(tokenMiners.length >= 1 && tokenMiners.length <= 2, + 'only 1 or 2 tokenMiners allowed')) return false; + const tokenMinerSymbols = new Set(); + for (let i = 0; i < tokenMiners.length; i += 1) { + const tokenMinerConfig = tokenMiners[i]; + if (!api.assert(tokenMinerConfig && tokenMinerConfig.symbol + && typeof (tokenMinerConfig.symbol) === 'string', 'tokenMiners invalid')) return false; + if (!api.assert(!tokenMinerSymbols.has(tokenMinerConfig.symbol), 'tokenMiners cannot have duplicate symbols')) return false; + tokenMinerSymbols.add(tokenMinerConfig.symbol) + const { symbol } = tokenMinerConfig; + const token = await api.db.findOneInTable('tokens', 'tokens', { symbol }); + if (!api.assert(token && token.stakingEnabled, 'tokenMiners must have staking enabled')) return false; + if (!api.assert(Number.isInteger(tokenMinerConfig.multiplier) + && tokenMinerConfig.multiplier >= 1 && tokenMinerConfig.multiplier <= 100, + 'tokenMiner multiplier must be an integer from 1 to 100')) return false; } return true; } function validateTokenMinersChange(oldTokenMiners, tokenMiners) { - const tokens = Object.keys(tokenMiners); - if (!api.assert(tokens.length === Object.keys(oldTokenMiners).length, 'cannot change tokenMiners keys')) return false; + if (!api.assert(tokenMiners.length === oldTokenMiners.length, 'cannot change which tokens are in tokenMiners')) return false; let changed = false; - for (let i = 0; i < tokens.length; i += 1) { - const token = tokens[i]; - if (!api.assert(oldTokenMiners[token], 'can only modify existing tokenMiner multiplier')) return false; - if (!api.assert(Number.isInteger(tokenMiners[token]) && tokenMiners[token] >= 1 && tokenMiners[token] <= 100, 'tokenMiner multiplier must be an integer from 1 to 100')) return false; - if (tokenMiners[token] !== oldTokenMiners[token]) { + for (let i = 0; i < tokenMiners.length; i += 1) { + const oldConfig = oldTokenMiners[i]; + const newConfig = tokenMiners[i]; + if (!api.assert(oldConfig.symbol === newConfig.symbol, 'cannot change which tokens are in tokenMiners')) return false; + if (!api.assert(Number.isInteger(newConfig.multiplier) && newConfig.multiplier >= 1 && newConfig.multiplier <= 100, 'tokenMiner multiplier must be an integer from 1 to 100')) return false; + if (oldConfig.multiplier !== newConfig.multiplier) { changed = true; } } @@ -78,12 +86,12 @@ function validateTokenMinersChange(oldTokenMiners, tokenMiners) { function computeMiningPower(miningPower, tokenMiners) { let power = api.BigNumber(0); - Object.keys(tokenMiners).forEach((token) => { - if (miningPower.balances[token]) { - power = power.plus(api.BigNumber(miningPower.balances[token]) - .multipliedBy(tokenMiners[token])); + for (let i = 0; i < tokenMiners.length; i += 1) { + if (miningPower.balances[i]) { + power = power.plus(api.BigNumber(miningPower.balances[i]) + .multipliedBy(tokenMiners[i].multiplier)); } - }); + } return power; } @@ -92,9 +100,10 @@ async function updateMiningPower(pool, token, account, stakedQuantity, delegated let stake = api.BigNumber(stakedQuantity); let oldMiningPower = api.BigNumber(0); stake = stake.plus(delegatedQuantity); + const tokenIndex = pool.tokenMiners.findIndex(t => t.symbol === token); if (!miningPower) { const balances = {}; - balances[token] = stake; + balances[tokenIndex] = stake; miningPower = { id: pool.id, account, @@ -103,10 +112,10 @@ async function updateMiningPower(pool, token, account, stakedQuantity, delegated }; miningPower = await api.db.insert('miningPower', miningPower); } else { - if (!miningPower.balances[token] || reset) { - miningPower.balances[token] = '0'; + if (!miningPower.balances[tokenIndex] || reset) { + miningPower.balances[tokenIndex] = '0'; } - miningPower.balances[token] = stake.plus(miningPower.balances[token]); + miningPower.balances[tokenIndex] = stake.plus(miningPower.balances[tokenIndex]); oldMiningPower = miningPower.power.$numberDecimal; } const newMiningPower = computeMiningPower(miningPower, pool.tokenMiners); @@ -163,11 +172,10 @@ actions.updatePool = async (payload) => { pool.active = active; if (validMinersChange.changed) { - const tokenMinerSymbols = Object.keys(tokenMiners); - for (let i = 0; i < tokenMinerSymbols.length; i += 1) { - const token = tokenMinerSymbols[i]; - await api.db.insert('tokenPools', { symbol: token, id: pool.id }); - const adjustedPower = await initMiningPower(pool, token, true); + for (let i = 0; i < tokenMiners.length; i += 1) { + const tokenConfig = tokenMiners[i]; + await api.db.insert('tokenPools', { symbol: tokenConfig.symbol, id: pool.id }); + const adjustedPower = await initMiningPower(pool, tokenConfig.symbol, true); pool.totalPower = api.BigNumber(pool.totalPower).plus(adjustedPower); } } @@ -193,8 +201,8 @@ actions.updatePool = async (payload) => { }; function generatePoolId(pool) { - const tokenMinerString = Object.keys(pool.tokenMiners).join('_'); - return `${pool.minedToken}-${tokenMinerString}`; + const tokenMinerString = pool.tokenMiners.map(t => t.symbol.replace('.', '-')).sort().join(','); + return `${pool.minedToken.replace('.', '-')}:${tokenMinerString}`; } actions.createPool = async (payload) => { @@ -243,22 +251,24 @@ actions.createPool = async (payload) => { }; newPool.id = generatePoolId(newPool); - const tokenMinerSymbols = Object.keys(tokenMiners); - for (let i = 0; i < tokenMinerSymbols.length; i += 1) { - const token = tokenMinerSymbols[i]; - await api.db.insert('tokenPools', { symbol: token, id: newPool.id }); - const adjustedPower = await initMiningPower(newPool, token); - newPool.totalPower = api.BigNumber(newPool.totalPower) - .plus(adjustedPower); - } - await api.db.insert('pools', newPool); - - // burn the token creation fees - if (api.BigNumber(poolCreationFee).gt(0)) { - await api.executeSmartContract('tokens', 'transfer', { - // eslint-disable-next-line no-template-curly-in-string - to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: poolCreationFee, isSignedWithActiveKey, - }); + const existingPool = await api.db.findOne('pools', { id: newPool.id }); + if (api.assert(!existingPool, 'pool already exists')) { + for (let i = 0; i < tokenMiners.length; i += 1) { + const tokenConfig = tokenMiners[i]; + await api.db.insert('tokenPools', { symbol: tokenConfig.symbol, id: newPool.id }); + const adjustedPower = await initMiningPower(newPool, tokenConfig.symbol); + newPool.totalPower = api.BigNumber(newPool.totalPower) + .plus(adjustedPower); + } + await api.db.insert('pools', newPool); + + // burn the token creation fees + if (api.BigNumber(poolCreationFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: poolCreationFee, isSignedWithActiveKey, + }); + } } } } diff --git a/test/mining.js b/test/mining.js index 014198d9..229483e0 100644 --- a/test/mining.js +++ b/test/mining.js @@ -279,33 +279,37 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolCreationFee": 0 }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": false }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": 2, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "12345678901", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": "1", "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 0, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 21, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": "0", "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 0, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 721, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "NOTKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": 2, "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "12345678901", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": "1", "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 0, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 21, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": "0", "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 0, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 721, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "NOTKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "satoshi", "quantity": "1000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": "tokenMiners", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "TKN2": 2, "TKN3": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "TKN2", "multiplier": 2}, {"symbol": "TKN3", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "2000", "isSignedWithActiveKey": true }`)); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000" }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"NOTKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "NOTKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": "garbage"}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 0}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"MTKN": 101}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "-1", "minedToken": "TKN", "tokenMiners": {"MTKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": "garbage"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": 0}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": 101}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "-1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "MTKN", "multiplier": 1}, {"symbol": "TKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "TKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -342,6 +346,53 @@ describe('mining', function () { assertError(txs[30], 'tokenMiner multiplier must be an integer from 1 to 100'); assertError(txs[31], 'tokenMiner multiplier must be an integer from 1 to 100'); assertError(txs[32], 'invalid params'); + assertError(txs[34], 'pool already exists'); + assertError(txs[35], 'pool already exists'); + assertError(txs[36], 'tokenMiners cannot have duplicate symbols'); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + + }); + + it('should create mining pool', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + + await database1.init(conf.databaseURL, conf.databaseName); + + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "4200", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TEST.TKN", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TEST.MTKN", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TEST.TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TEST.MTKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TEST.TKN", "tokenMiners": [{"symbol": "TEST.TKN", "multiplier": 1}, {"symbol": "TEST.MTKN", "multiplier": 2}], "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + let res = await database1.getLatestBlockInfo(); + let txs = res.transactions; + + await assertNoErrorInLastBlock(); + + await assertPool({id: 'TEST-TKN:TEST-MTKN,TEST-TKN', totalPower: '0'}); resolve(); }) @@ -376,7 +427,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "20", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 4}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -390,17 +441,17 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN_MTKN'); - await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + await assertTokenPool('TKN', 'TKN:MTKN,TKN'); + await assertTokenPool('MTKN', 'TKN:MTKN,TKN'); await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); - await assertPool({id: 'TKN-TKN_MTKN', totalPower: '150'}); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '100'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '150'}); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); @@ -421,9 +472,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'TKN', '40.00000000', '40.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '30.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '60'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '140'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '200' }); transactions = []; transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'delegate', '{ "to": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); @@ -446,9 +497,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '25.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '75'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '125'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '75'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '125'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '200' }); transactions = []; transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'tokens', 'undelegate', '{ "from": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); @@ -471,9 +522,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '25.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '55'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '120'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '175' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '55'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '120'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '175' }); transactions = []; transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -493,9 +544,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '30.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '60'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '140'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '200' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '60'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '140'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '200' }); transactions = []; const unstakeId = getNextTxId(); @@ -518,9 +569,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '29.99999998'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999998'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999992'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.9999999' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '59.99999998'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '139.99999992'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '199.9999999' }); transactions = []; transactions.push(new Transaction(12345678907, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -540,9 +591,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); await assertUserBalances('satoshi2', 'MTKN', '0.00000002', '29.99999995'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999996'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999984'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.9999998' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '59.99999996'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '139.99999984'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '199.9999998' }); transactions = []; transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId}", "isSignedWithActiveKey": true }`)); @@ -563,9 +614,9 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '65.00000000', '5.00000000', '0.00000000'); await assertUserBalances('satoshi2', 'MTKN', '0.00000002', '29.99999998'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '59.99999999'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '139.99999996'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '199.99999995' }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '59.99999999'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '139.99999996'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '199.99999995' }); resolve(); }) @@ -591,7 +642,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "MTKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "MTKN", "unstakingCooldown": 2, "numberTransactions": 2, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 4}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -605,28 +656,28 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN_MTKN'); - await assertPool({id: 'TKN-TKN_MTKN', totalPower: '0', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); + await assertTokenPool('TKN', 'TKN:MTKN,TKN'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '0', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); transactions = []; - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "300", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": false }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": 2, "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "blah", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "-15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2.7, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 0, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 21, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN9", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": 2, "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "blah", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "-15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2.7, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 0, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 21, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN9", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolUpdateFee": 0 }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "NOTKN": 3}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 101}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 0}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": "a"}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "NOTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 101}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 0}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": "a"}], "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: 12345678902, @@ -652,8 +703,8 @@ describe('mining', function () { assertError(txs[9], 'pool id not found'); assertError(txs[11], 'must be issuer of minedToken'); assertError(txs[12], 'minedToken precision mismatch for lotteryAmount'); - assertError(txs[13], 'cannot change tokenMiners keys'); - assertError(txs[14], 'can only modify existing tokenMiner multiplier'); + assertError(txs[13], 'cannot change which tokens are in tokenMiners'); + assertError(txs[14], 'cannot change which tokens are in tokenMiners'); assertError(txs[15], 'tokenMiner multiplier must be an integer from 1 to 100'); assertError(txs[16], 'tokenMiner multiplier must be an integer from 1 to 100'); assertError(txs[17], 'tokenMiner multiplier must be an integer from 1 to 100'); @@ -690,7 +741,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "11", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 720, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 4}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -704,20 +755,20 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN_MTKN'); - await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + await assertTokenPool('TKN', 'TKN:MTKN,TKN'); + await assertTokenPool('MTKN', 'TKN:MTKN,TKN'); await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); await assertUserBalances('satoshi', 'MTKN', '84.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '11.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '64'); - await assertPool({id: 'TKN-TKN_MTKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '64'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); transactions = []; - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN_MTKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": {"TKN": 2, "MTKN": 3}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: 12345678902, @@ -731,17 +782,17 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN_MTKN'); - await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + await assertTokenPool('TKN', 'TKN:MTKN,TKN'); + await assertTokenPool('MTKN', 'TKN:MTKN,TKN'); await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); await assertUserBalances('satoshi', 'MTKN', '84.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '11.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '75'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '73'); - await assertPool({id: 'TKN-TKN_MTKN', totalPower: '148', lotteryWinners: 2, lotteryIntervalHours: 3, lotteryAmount: "15.7", nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime() }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '75'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '73'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '148', lotteryWinners: 2, lotteryIntervalHours: 3, lotteryAmount: "15.7", nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime() }); resolve(); }) @@ -752,7 +803,7 @@ describe('mining', function () { }); }); - it.only('should not run basic lottery when inactive', (done) => { + it('should not run basic lottery when inactive', (done) => { new Promise(async (resolve) => { await loadPlugin(blockchain); database1 = new Database(); @@ -767,8 +818,8 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "50", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": false, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": false, "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -782,9 +833,9 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN'); + await assertTokenPool('TKN', 'TKN:TKN'); - await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime(), active: false }); + await assertPool({ id: 'TKN:TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime(), active: false }); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -803,7 +854,7 @@ describe('mining', function () { assert(res.virtualTransactions.length === 0); transactions = []; - transactions.push(new Transaction(12345678903, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN-TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": true, "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:TKN", "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "active": true, "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: 12345678903, @@ -819,7 +870,7 @@ describe('mining', function () { res = (await database1.getLatestBlockInfo()); assert(res.virtualTransactions.length === 0); - await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime(), active: true }); + await assertPool({ id: 'TKN:TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T03:00:00.000Z').getTime(), active: true }); transactions = []; transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -837,7 +888,7 @@ describe('mining', function () { let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); assert.ok(lotteryEvent, 'Expected to find miningLottery event'); - assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.poolId, 'TKN:TKN'); assert.equal(lotteryEvent.data.winners.length, 1); assert.equal(lotteryEvent.data.winners[0].winner, "satoshi2"); assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); @@ -871,7 +922,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "50", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -885,14 +936,14 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN'); + await assertTokenPool('TKN', 'TKN:TKN'); await assertUserBalances('satoshi', 'TKN', '40.00000000', '50.00000000'); await assertUserBalances('satoshi2', 'TKN', 0, '10.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN', '50'); - await assertMiningPower('satoshi2', 'TKN-TKN', '10'); - await assertPool({ id: 'TKN-TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); + await assertMiningPower('satoshi', 'TKN:TKN', '50'); + await assertMiningPower('satoshi2', 'TKN:TKN', '10'); + await assertPool({ id: 'TKN:TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -910,7 +961,7 @@ describe('mining', function () { let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); assert.ok(lotteryEvent, 'Expected to find miningLottery event'); - assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.poolId, 'TKN:TKN'); assert.equal(lotteryEvent.data.winners.length, 1); assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); @@ -939,7 +990,7 @@ describe('mining', function () { lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); assert.ok(lotteryEvent, 'Expected to find miningLottery event'); - assert.equal(lotteryEvent.data.poolId, 'TKN-TKN'); + assert.equal(lotteryEvent.data.poolId, 'TKN:TKN'); assert.equal(lotteryEvent.data.winners.length, 1); winnerCount[lotteryEvent.data.winners[0].winner] += 1; } @@ -978,7 +1029,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "20", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "MTKN", "quantity": "20", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": {"TKN": 1, "MTKN": 4}, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}, {"symbol": "MTKN", "multiplier": 4}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: 12345678901, @@ -992,17 +1043,17 @@ describe('mining', function () { await assertNoErrorInLastBlock(); - await assertTokenPool('TKN', 'TKN-TKN_MTKN'); - await assertTokenPool('MTKN', 'TKN-TKN_MTKN'); + await assertTokenPool('TKN', 'TKN:MTKN,TKN'); + await assertTokenPool('MTKN', 'TKN:MTKN,TKN'); await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); - await assertMiningPower('satoshi', 'TKN-TKN_MTKN', '50'); - await assertMiningPower('satoshi2', 'TKN-TKN_MTKN', '100'); - await assertPool({ id: 'TKN-TKN_MTKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '100'); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); transactions = []; transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -1020,7 +1071,7 @@ describe('mining', function () { let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); assert.ok(lotteryEvent, 'Expected to find miningLottery event'); - assert.equal(lotteryEvent.data.poolId, 'TKN-TKN_MTKN'); + assert.equal(lotteryEvent.data.poolId, 'TKN:MTKN,TKN'); assert.equal(lotteryEvent.data.winners.length, 1); assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); @@ -1049,7 +1100,7 @@ describe('mining', function () { lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); assert.ok(lotteryEvent, 'Expected to find miningLottery event'); - assert.equal(lotteryEvent.data.poolId, 'TKN-TKN_MTKN'); + assert.equal(lotteryEvent.data.poolId, 'TKN:MTKN,TKN'); assert.equal(lotteryEvent.data.winners.length, 1); winnerCount[lotteryEvent.data.winners[0].winner] += 1; } From 16962ece8589bfa708ba957aa30049b494c7d514 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Fri, 25 Sep 2020 21:06:03 +0000 Subject: [PATCH 04/11] fix lint --- contracts/mining.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mining.js b/contracts/mining.js index f69b6789..68d2e300 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -58,7 +58,7 @@ async function validateTokenMiners(tokenMiners) { if (!api.assert(tokenMinerConfig && tokenMinerConfig.symbol && typeof (tokenMinerConfig.symbol) === 'string', 'tokenMiners invalid')) return false; if (!api.assert(!tokenMinerSymbols.has(tokenMinerConfig.symbol), 'tokenMiners cannot have duplicate symbols')) return false; - tokenMinerSymbols.add(tokenMinerConfig.symbol) + tokenMinerSymbols.add(tokenMinerConfig.symbol); const { symbol } = tokenMinerConfig; const token = await api.db.findOneInTable('tokens', 'tokens', { symbol }); if (!api.assert(token && token.stakingEnabled, 'tokenMiners must have staking enabled')) return false; From eca96110b0b179b9099abe988dbd41c00d30a615 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Fri, 25 Sep 2020 23:41:00 +0000 Subject: [PATCH 05/11] change call as owner, fix tests by adding mining contract --- contracts/mining.js | 4 ++-- contracts/tokens.js | 2 +- test/smarttokens.js | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index 68d2e300..5f27e357 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -342,8 +342,8 @@ actions.handleStakeChange = async (payload) => { const { account, symbol, quantity, delegated, callingContractInfo, } = payload; - if (api.assert(callingContractInfo.name === 'tokens' - && api.sender === api.owner, 'must be called from tokens contract')) { + if (api.assert(callingContractInfo && callingContractInfo.name === 'tokens', + 'must be called from tokens contract')) { await findAndProcessAll('mining', 'tokenPools', { symbol }, async (tokenPool) => { const pool = await api.db.findOne('pools', { id: tokenPool.id }); let adjusted; diff --git a/contracts/tokens.js b/contracts/tokens.js index 6adeb903..de552c23 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -1611,7 +1611,7 @@ actions.stake = async (payload) => { // await api.executeSmartContract // ('witnesses', 'updateWitnessesApprovals', { account: api.sender }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity }); } } diff --git a/test/smarttokens.js b/test/smarttokens.js index 2252f1c1..6dc168ce 100644 --- a/test/smarttokens.js +++ b/test/smarttokens.js @@ -95,7 +95,7 @@ const unloadPlugin = (plugin) => { currentJobId = 0; } -function setupContractPayload(file) { +function setupContractPayload(name, file) { let contractCode = fs.readFileSync(file); contractCode = contractCode.toString(); contractCode = contractCode.replace(/'\$\{CONSTANTS.UTILITY_TOKEN_PRECISION\}\$'/g, CONSTANTS.UTILITY_TOKEN_PRECISION); @@ -105,14 +105,15 @@ function setupContractPayload(file) { let base64ContractCode = Base64.encode(contractCode); return { - name: 'tokens', + name, params: '', code: base64ContractCode, }; } -const contractPayload = setupContractPayload('./contracts/tokens.js'); -const oldContractPayload = setupContractPayload('./contracts/testing/tokens_20200923.js'); +const contractPayload = setupContractPayload('tokens', './contracts/tokens.js'); +const oldContractPayload = setupContractPayload('tokens', './contracts/testing/tokens_20200923.js'); +const miningContractPayload = setupContractPayload('mining', './contracts/mining.js'); async function assertUserBalances({ account, symbol, balance, stake, pendingUnstake, delegationsOut, delegationsIn }) { let res = await database1.findOne({ @@ -364,6 +365,7 @@ describe('smart tokens', function () { await database1.init(conf.databaseURL, conf.databaseName); let transactions = []; transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(miningContractPayload))); transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "3000", "isSignedWithActiveKey": true }`)); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); @@ -563,6 +565,7 @@ describe('smart tokens', function () { await database1.init(conf.databaseURL, conf.databaseName); let transactions = []; transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(miningContractPayload))); transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "3000", "isSignedWithActiveKey": true }`)); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); From bf068c7728dbfbf730b14da0edb11e61e603b0b9 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 01:53:20 +0000 Subject: [PATCH 06/11] allow controls on how much updating is happen, split update into multiple blocks if necessary. TODO: add testing for the block split --- contracts/mining.js | 211 ++++++++++++++++++++++++++++++-------------- libs/Block.js | 10 ++- test/mining.js | 115 +++++++++++++++++------- 3 files changed, 234 insertions(+), 102 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index 5f27e357..cf845573 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -1,4 +1,5 @@ /* eslint-disable no-await-in-loop */ +/* eslint no-underscore-dangle: ["error", { "allow": ["_id"] }] */ /* global actions, api */ actions.createSSC = async () => { @@ -13,6 +14,9 @@ actions.createSSC = async () => { const params = {}; params.poolCreationFee = '1000'; params.poolUpdateFee = '300'; + params.maxLotteriesPerBlock = 20; + params.maxBalancesProcessedPerBlock = 10000; + params.processQueryLimit = 1000; await api.db.insert('params', params); } }; @@ -124,17 +128,63 @@ async function updateMiningPower(pool, token, account, stakedQuantity, delegated return newMiningPower.minus(oldMiningPower); } -async function initMiningPower(pool, token, reset) { - let totalAdjusted = api.BigNumber(0); - await findAndProcessAll('tokens', 'balances', { symbol: token }, async (balance) => { - const adjusted = await updateMiningPower( - pool, token, balance.account, balance.stake, balance.delegationsIn, reset, - ); - totalAdjusted = totalAdjusted.plus(adjusted); - }); - return totalAdjusted; +async function initMiningPower(pool, params, token, lastAccountId) { + let adjustedPower = api.BigNumber(0); + let offset = 0; + let lastAccountIdProcessed = lastAccountId; + let complete = false; + let balances; + while (!complete || offset < params.maxBalancesProcessedPerBlock) { + balances = await api.db.findInTable('tokens', 'balances', { symbol: token, _id: { $gt: lastAccountId } }, params.processQueryLimit, offset, [{ index: '_id', descending: false }]); + for (let i = 0; i < balances.length; i += 1) { + const balance = balances[i]; + if (api.BigNumber(balance.stake).gt(0) || api.BigNumber(balance.delegationsIn).gt(0)) { + const adjusted = await updateMiningPower( + pool, token, balance.account, balance.stake, balance.delegationsIn, /* reset */ true, + ); + adjustedPower = adjustedPower.plus(adjusted); + } + lastAccountIdProcessed = balance._id; + } + if (balances.length < params.processQueryLimit) { + complete = true; + } + offset += params.processQueryLimit; + } + return { adjustedPower, nextAccountId: lastAccountIdProcessed, complete }; } +async function resumePowerUpdate(pool, params) { + let { inProgress, tokenIndex, lastAccountId } = pool.updating; + if (!inProgress) { + return; + } + + const tokenConfig = pool.tokenMiners[tokenIndex]; + const { adjustedPower, nextAccountId, complete } = await initMiningPower( + pool, params, tokenConfig.symbol, lastAccountId, + ); + // eslint-disable-next-line no-param-reassign + pool.totalPower = api.BigNumber(pool.totalPower) + .plus(adjustedPower); + if (complete) { + tokenIndex += 1; + lastAccountId = 0; + if (tokenIndex === pool.tokenMiners.length) { + inProgress = false; + tokenIndex = 0; + } + } else { + lastAccountId = nextAccountId; + } + const { updating } = pool; + updating.inProgress = inProgress; + updating.tokenIndex = tokenIndex; + updating.lastAccountId = lastAccountId; + await api.db.update('pools', pool); +} + + actions.updatePool = async (payload) => { const { id, lotteryWinners, lotteryIntervalHours, lotteryAmount, tokenMiners, active, @@ -172,12 +222,9 @@ actions.updatePool = async (payload) => { pool.active = active; if (validMinersChange.changed) { - for (let i = 0; i < tokenMiners.length; i += 1) { - const tokenConfig = tokenMiners[i]; - await api.db.insert('tokenPools', { symbol: tokenConfig.symbol, id: pool.id }); - const adjustedPower = await initMiningPower(pool, tokenConfig.symbol, true); - pool.totalPower = api.BigNumber(pool.totalPower).plus(adjustedPower); - } + pool.updating.inProgress = true; + pool.updating.tokenIndex = 0; + pool.updating.lastAccountId = 0; } const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); @@ -256,10 +303,12 @@ actions.createPool = async (payload) => { for (let i = 0; i < tokenMiners.length; i += 1) { const tokenConfig = tokenMiners[i]; await api.db.insert('tokenPools', { symbol: tokenConfig.symbol, id: newPool.id }); - const adjustedPower = await initMiningPower(newPool, tokenConfig.symbol); - newPool.totalPower = api.BigNumber(newPool.totalPower) - .plus(adjustedPower); } + newPool.updating = { + inProgress: true, + tokenIndex: 0, + lastAccountId: 0, + }; await api.db.insert('pools', newPool); // burn the token creation fees @@ -275,66 +324,94 @@ actions.createPool = async (payload) => { } }; +async function runLottery(pool, params) { + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + const winningNumbers = []; + const minedToken = await api.db.findOneInTable('tokens', 'tokens', + { symbol: pool.minedToken }); + const winningAmount = api.BigNumber(pool.lotteryAmount).dividedBy(pool.lotteryWinners) + .toFixed(minedToken.precision); + for (let i = 0; i < pool.lotteryWinners; i += 1) { + winningNumbers[i] = api.BigNumber(pool.totalPower).multipliedBy(api.random()); + } + let offset = 0; + let miningPowers; + let cumulativePower = api.BigNumber(0); + let nextCumulativePower = api.BigNumber(0); + let computedWinners = 0; + const winners = []; + while (computedWinners < pool.lotteryWinners) { + miningPowers = await api.db.find('miningPower', { id: pool.id, power: { $gt: { $numberDecimal: '0' } } }, + params.processQueryLimit, + offset, + [{ index: 'power', descending: true }, { index: '_id', descending: false }]); + for (let i = 0; i < miningPowers.length; i += 1) { + const miningPower = miningPowers[i]; + nextCumulativePower = cumulativePower.plus(miningPower.power.$numberDecimal); + for (let j = 0; j < pool.lotteryWinners; j += 1) { + const currentWinningNumber = winningNumbers[j]; + if (cumulativePower.lte(currentWinningNumber) + && nextCumulativePower.gt(currentWinningNumber)) { + computedWinners += 1; + winners.push({ + winner: miningPower.account, + winningNumber: currentWinningNumber, + winningAmount, + }); + } + } + cumulativePower = nextCumulativePower; + } + if (computedWinners === pool.lotteryWinners || miningPowers.length < params.processQueryLimit) { + break; + } + offset += params.processQueryLimit; + } + api.emit('miningLottery', { poolId: pool.id, winners }); + for (let i = 0; i < winners.length; i += 1) { + const winner = winners[i]; + await api.executeSmartContract('tokens', 'issue', + { to: winner.winner, symbol: minedToken.symbol, quantity: winningAmount }); + } + // eslint-disable-next-line no-param-reassign + pool.nextLotteryTimestamp = api.BigNumber(blockDate.getTime()) + .plus(pool.lotteryIntervalHours * 3600 * 1000).toNumber(); + await api.db.update('pools', pool); +} + actions.checkPendingLotteries = async () => { if (api.assert(api.sender === 'null', 'not authorized')) { const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const timestamp = blockDate.getTime(); - await findAndProcessAll('mining', 'pools', + const params = await api.db.findOne('params', {}); + const updatingLotteries = await api.db.find('pools', + { + 'updating.inProgress': true, + }, + params.maxLotteriesPerBlock, + 0, + [{ index: 'id', descending: false }]); + for (let i = 0; i < updatingLotteries.length; i += 1) { + const pool = updatingLotteries[i]; + await resumePowerUpdate(pool, params); + } + const pendingLotteries = await api.db.find('pools', { active: true, + 'updating.inProgress': false, nextLotteryTimestamp: { $lte: timestamp, }, }, - async (pool) => { - const winningNumbers = []; - const minedToken = await api.db.findOneInTable('tokens', 'tokens', - { symbol: pool.minedToken }); - const winningAmount = api.BigNumber(pool.lotteryAmount).dividedBy(pool.lotteryWinners) - .toFixed(minedToken.precision); - for (let i = 0; i < pool.lotteryWinners; i += 1) { - winningNumbers[i] = api.BigNumber(pool.totalPower).multipliedBy(api.random()); - } - // iterate power desc - let offset = 0; - let miningPowers; - let cumulativePower = api.BigNumber(0); - let nextCumulativePower = api.BigNumber(0); - let computedWinners = 0; - const winners = []; - while (computedWinners < pool.lotteryWinners) { - miningPowers = await api.db.find('miningPower', { id: pool.id, power: { $gt: { $numberDecimal: '0' } } }, 1000, offset, [{ index: 'power', descending: true }]); - for (let i = 0; i < miningPowers.length; i += 1) { - const miningPower = miningPowers[i]; - nextCumulativePower = cumulativePower.plus(miningPower.power.$numberDecimal); - for (let j = 0; j < pool.lotteryWinners; j += 1) { - const currentWinningNumber = winningNumbers[j]; - if (cumulativePower.lte(currentWinningNumber) - && nextCumulativePower.gt(currentWinningNumber)) { - computedWinners += 1; - winners.push({ - winner: miningPower.account, - winningNumber: currentWinningNumber, - winningAmount, - }); - await api.executeSmartContract('tokens', 'issue', - { to: miningPower.account, symbol: minedToken.symbol, quantity: winningAmount }); - } - } - cumulativePower = nextCumulativePower; - } - if (computedWinners === pool.lotteryWinners || miningPowers.length < 1000) { - break; - } - offset += 1000; - } - api.emit('miningLottery', { poolId: pool.id, winners }); - // eslint-disable-next-line no-param-reassign - pool.nextLotteryTimestamp = api.BigNumber(blockDate.getTime()) - .plus(pool.lotteryIntervalHours * 3600 * 1000).toNumber(); - await api.db.update('pools', pool); - }); + params.maxLotteriesPerBlock, + 0, + [{ index: 'id', descending: false }]); + + for (let i = 0; i < pendingLotteries.length; i += 1) { + const pool = pendingLotteries[i]; + await runLottery(pool, params); + } } }; diff --git a/libs/Block.js b/libs/Block.js index 9b807711..a558e2b6 100644 --- a/libs/Block.js +++ b/libs/Block.js @@ -98,12 +98,12 @@ class Block { virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUndelegations', '')); virtualTransactions.push(new Transaction(0, '', 'null', 'nft', 'checkPendingUndelegations', '')); - // TODO: hive block number - virtualTransactions.push(new Transaction(0, '', 'null', 'mining', 'checkPendingLotteries', '')); - if (this.refHiveBlockNumber >= 45251626) { virtualTransactions.push(new Transaction(0, '', 'null', 'botcontroller', 'tick', '')); } + if (this.refHiveBlockNumber >= 47746850) { + virtualTransactions.push(new Transaction(0, '', 'null', 'mining', 'checkPendingLotteries', '')); + } // TODO: cleanup // if (this.refHiveBlockNumber >= 37899120) { @@ -142,6 +142,10 @@ class Block { && transaction.action === 'tick' && transaction.logs === '{"errors":["contract doesn\'t exist"]}') { // don't save logs + } else if (transaction.contract === 'mining' + && transaction.action === 'checkPendingLotteries' + && transaction.logs === '{"errors":["contract doesn\'t exist"]}') { + // don't save logs } else { this.virtualTransactions.push(transaction); } diff --git a/test/mining.js b/test/mining.js index 229483e0..fd0e2701 100644 --- a/test/mining.js +++ b/test/mining.js @@ -449,16 +449,33 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '30'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '20'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}); + + // allow mining power update to resume + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '100'); await assertPool({id: 'TKN:MTKN,TKN', totalPower: '150'}); transactions = []; - transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi2", "symbol": "MTKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to": "satoshi2", "symbol": "MTKN", "quantity": "10", "isSignedWithActiveKey": true }')); block = { - refHiveBlockNumber: 12345678902, + refHiveBlockNumber: 12345678903, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-01T00:00:00', @@ -477,11 +494,11 @@ describe('mining', function () { await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '200' }); transactions = []; - transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'tokens', 'delegate', '{ "to": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi2', 'tokens', 'delegate', '{ "to": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'tokens', 'delegate', '{ "to": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi2', 'tokens', 'delegate', '{ "to": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); block = { - refHiveBlockNumber: 12345678903, + refHiveBlockNumber: 12345678904, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-01T00:00:00', @@ -502,11 +519,11 @@ describe('mining', function () { await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '200' }); transactions = []; - transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'tokens', 'undelegate', '{ "from": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi2', 'tokens', 'undelegate', '{ "from": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'tokens', 'undelegate', '{ "from": "satoshi2", "symbol": "TKN", "quantity": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi2', 'tokens', 'undelegate', '{ "from": "satoshi", "symbol": "MTKN", "quantity": "5", "isSignedWithActiveKey": true }')); block = { - refHiveBlockNumber: 12345678904, + refHiveBlockNumber: 12345678905, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-01T00:00:00', @@ -527,10 +544,10 @@ describe('mining', function () { await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '175' }); transactions = []; - transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + transactions.push(new Transaction(12345678906, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); block = { - refHiveBlockNumber: 12345678905, + refHiveBlockNumber: 12345678906, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-02T00:00:00', @@ -551,11 +568,11 @@ describe('mining', function () { transactions = []; const unstakeId = getNextTxId(); const unstakeId2 = getNextTxId(); - transactions.push(new Transaction(12345678906, unstakeId, 'satoshi', 'tokens', 'unstake', '{ "symbol": "TKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678906, unstakeId2, 'satoshi2', 'tokens', 'unstake', '{ "symbol": "MTKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678907, unstakeId, 'satoshi', 'tokens', 'unstake', '{ "symbol": "TKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678907, unstakeId2, 'satoshi2', 'tokens', 'unstake', '{ "symbol": "MTKN", "quantity": "0.00000005", "isSignedWithActiveKey": true }')); block = { - refHiveBlockNumber: 12345678906, + refHiveBlockNumber: 12345678907, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-02T00:00:00', @@ -574,10 +591,10 @@ describe('mining', function () { await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '199.9999999' }); transactions = []; - transactions.push(new Transaction(12345678907, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); block = { - refHiveBlockNumber: 12345678907, + refHiveBlockNumber: 12345678908, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-03T00:00:00', @@ -596,11 +613,11 @@ describe('mining', function () { await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '199.9999998' }); transactions = []; - transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId}", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(12345678908, getNextTxId(), 'satoshi2', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId2}", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678909, getNextTxId(), 'satoshi', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId}", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678909, getNextTxId(), 'satoshi2', 'tokens', 'cancelUnstake', `{ "txID": "${unstakeId2}", "isSignedWithActiveKey": true }`)); block = { - refHiveBlockNumber: 12345678908, + refHiveBlockNumber: 12345678909, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-02T00:00:00', @@ -763,15 +780,32 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '84.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '11.00000000'); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '30'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '20'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}); + + // allow mining power update to resume + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '64'); await assertPool({id: 'TKN:MTKN,TKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); transactions = []; - transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); block = { - refHiveBlockNumber: 12345678902, + refHiveBlockNumber: 12345678903, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-01T00:00:00', @@ -890,11 +924,11 @@ describe('mining', function () { assert.ok(lotteryEvent, 'Expected to find miningLottery event'); assert.equal(lotteryEvent.data.poolId, 'TKN:TKN'); assert.equal(lotteryEvent.data.winners.length, 1); - assert.equal(lotteryEvent.data.winners[0].winner, "satoshi2"); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); - await assertUserBalances('satoshi', 'TKN', '40.00000000', '50.00000000'); - await assertUserBalances('satoshi2', 'TKN', '1.00000000', '10.00000000'); + await assertUserBalances('satoshi', 'TKN', '41.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', '0', '10.00000000'); @@ -956,7 +990,7 @@ describe('mining', function () { transactions, }; await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); - + let res = (await database1.getLatestBlockInfo()); let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); @@ -1051,15 +1085,32 @@ describe('mining', function () { await assertUserBalances('satoshi', 'MTKN', '75.00000000', '5.00000000'); await assertUserBalances('satoshi2', 'MTKN', 0, '20.00000000'); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '30'); + await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '20'); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}); + + // allow mining power update to resume + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '100'); await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); transactions = []; - transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); block = { - refHiveBlockNumber: 12345678902, + refHiveBlockNumber: 12345678903, refHiveBlockId: 'ABCD1', prevRefHiveBlockId: 'ABCD2', timestamp: '2018-06-01T01:00:00', @@ -1073,11 +1124,11 @@ describe('mining', function () { assert.ok(lotteryEvent, 'Expected to find miningLottery event'); assert.equal(lotteryEvent.data.poolId, 'TKN:MTKN,TKN'); assert.equal(lotteryEvent.data.winners.length, 1); - assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi2"); assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); - await assertUserBalances('satoshi', 'TKN', '51.00000000', '30.00000000'); - await assertUserBalances('satoshi2', 'TKN', 0, '20.00000000'); + await assertUserBalances('satoshi', 'TKN', '50.00000000', '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', '1.00000000', '20.00000000'); // run a few more times and count frequencies const winnerCount = { 'satoshi': 0, 'satoshi2': 0 }; @@ -1106,8 +1157,8 @@ describe('mining', function () { } assert.equal(Object.values(winnerCount).reduce((x,y) => x+y, 0), 20); assert(winnerCount['satoshi'] < winnerCount['satoshi2']); - await assertUserBalances('satoshi', 'TKN', (51 + winnerCount['satoshi']).toFixed(8), '30.00000000'); - await assertUserBalances('satoshi2', 'TKN', winnerCount['satoshi2'].toFixed(8), '20.00000000'); + await assertUserBalances('satoshi', 'TKN', (50 + winnerCount['satoshi']).toFixed(8), '30.00000000'); + await assertUserBalances('satoshi2', 'TKN', (1 + winnerCount['satoshi2']).toFixed(8), '20.00000000'); resolve(); }) From 2be96b33bd39bbb28d0c7852dc6261642bac74b2 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 15:32:40 +0000 Subject: [PATCH 07/11] added resumable logic and throttles to parameters, with tests --- contracts/mining.js | 36 ++++++++-- test/mining.js | 161 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 188 insertions(+), 9 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index cf845573..b78ebc86 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -24,12 +24,37 @@ actions.createSSC = async () => { actions.updateParams = async (payload) => { if (api.sender !== api.owner) return; - const { poolCreationFee, poolUpdateFee } = payload; + const { + poolCreationFee, + poolUpdateFee, + maxLotteriesPerBlock, + maxBalancesProcessedPerBlock, + processQueryLimit, + } = payload; const params = await api.db.findOne('params', {}); - params.poolCreationFee = poolCreationFee; - params.poolUpdateFee = poolUpdateFee; + if (poolCreationFee) { + if (!api.assert(typeof poolCreationFee === 'string' && !api.BigNumber(poolCreationFee).isNaN() && api.BigNumber(poolCreationFee).gt(0), 'invalid poolCreationFee')) return; + params.poolCreationFee = poolCreationFee; + } + if (poolUpdateFee) { + if (!api.assert(typeof poolUpdateFee === 'string' && !api.BigNumber(poolUpdateFee).isNaN() && api.BigNumber(poolUpdateFee).gt(0), 'invalid poolUpdateFee')) return; + params.poolUpdateFee = poolUpdateFee; + } + if (maxLotteriesPerBlock) { + if (!api.assert(Number.isInteger(maxLotteriesPerBlock) && maxLotteriesPerBlock >= 1, 'invalid maxLotteriesPerBlock')) return; + params.maxLotteriesPerBlock = maxLotteriesPerBlock; + } + if (maxBalancesProcessedPerBlock) { + if (!api.assert(Number.isInteger(maxBalancesProcessedPerBlock) && maxBalancesProcessedPerBlock >= 1, 'invalid maxBalancesProcessedPerBlock')) return; + params.maxBalancesProcessedPerBlock = maxBalancesProcessedPerBlock; + } + if (processQueryLimit) { + if (!api.assert(Number.isInteger(processQueryLimit) && processQueryLimit >= 1, 'invalid processQueryLimit')) return; + params.processQueryLimit = processQueryLimit; + } + await api.db.update('params', params); }; @@ -134,8 +159,10 @@ async function initMiningPower(pool, params, token, lastAccountId) { let lastAccountIdProcessed = lastAccountId; let complete = false; let balances; - while (!complete || offset < params.maxBalancesProcessedPerBlock) { + api.emit('debug', { pool, params, token, lastAccountId }); + while (!complete && offset < params.maxBalancesProcessedPerBlock) { balances = await api.db.findInTable('tokens', 'balances', { symbol: token, _id: { $gt: lastAccountId } }, params.processQueryLimit, offset, [{ index: '_id', descending: false }]); + api.emit('debug', balances); for (let i = 0; i < balances.length; i += 1) { const balance = balances[i]; if (api.BigNumber(balance.stake).gt(0) || api.BigNumber(balance.delegationsIn).gt(0)) { @@ -151,6 +178,7 @@ async function initMiningPower(pool, params, token, lastAccountId) { } offset += params.processQueryLimit; } + api.emit('debug', { adjustedPower, lastAccountIdProcessed, complete }); return { adjustedPower, nextAccountId: lastAccountIdProcessed, complete }; } diff --git a/test/mining.js b/test/mining.js index fd0e2701..7f2e5b76 100644 --- a/test/mining.js +++ b/test/mining.js @@ -124,6 +124,10 @@ async function assertUserBalances(account, symbol, balance, stake, delegationsIn } }); + if (!balance && !stake && !delegationsIn) { + assert(!res, `Balance found for ${account}, ${symbol}, expected none.`); + return; + } assert.ok(res, `No balance for ${account}, ${symbol}`); assert.equal(res.balance, balance, `${account} has ${symbol} balance ${res.balance}, expected ${balance}`); @@ -142,12 +146,16 @@ async function assertMiningPower(account, id, power) { account, } }); + if (!power) { + assert(!res, `Power found for ${account} in pool ${id}, expected to be missing.`); + return; + } assert.ok(res, `No power for ${account} in pool ${id}`); assert.equal(res.power['$numberDecimal'], power, `${account} has ${id} power ${res.power['$numberDecimal']}, expected ${power}`); } -async function assertPool(pool) { +async function assertPool(pool, updating) { const { id } = pool; let res = await database1.findOne({ contract: 'mining', @@ -162,6 +170,11 @@ async function assertPool(pool) { Object.keys(pool).forEach(k => { assert.equal(res[k], pool[k], `Pool ${id} has ${k} ${res[k]}, expected ${pool[k]}`); }); + if (updating) { + Object.keys(updating).forEach(k => { + assert.equal(res.updating[k], updating[k], `Pool ${id} has updating.${k} ${res.updating[k]}, expected ${updating[k]}`); + }); + } } async function assertTokenPool(symbol, poolId) { @@ -782,7 +795,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '30'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '20'); - await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}, { inProgress: true, lastAccountId: 0, tokenIndex: 1 }); // allow mining power update to resume transactions = []; @@ -799,7 +812,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '64'); - await assertPool({id: 'TKN:MTKN,TKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '114', lotteryWinners: 1, lotteryIntervalHours: 720, lotteryAmount: "1", nextLotteryTimestamp: new Date('2018-07-01T00:00:00.000Z').getTime()}, { inProgress: false, lastAccountId: 0, tokenIndex: 0 }); transactions = []; transactions.push(new Transaction(12345678903, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); @@ -1087,7 +1100,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '30'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '20'); - await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}); + await assertPool({id: 'TKN:MTKN,TKN', totalPower: '50'}, { inProgress: true, lastAccountId: 0, tokenIndex: 1 }); // allow mining power update to resume transactions = []; @@ -1104,7 +1117,7 @@ describe('mining', function () { await assertMiningPower('satoshi', 'TKN:MTKN,TKN', '50'); await assertMiningPower('satoshi2', 'TKN:MTKN,TKN', '100'); - await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }); + await assertPool({ id: 'TKN:MTKN,TKN', totalPower: '150', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: false, lastAccountId: 0, tokenIndex: 0 }); transactions = []; transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); @@ -1169,4 +1182,142 @@ describe('mining', function () { }); }); + it('should cap lotteries run', (done) => { + new Promise(async (resolve) => { + await loadPlugin(blockchain); + database1 = new Database(); + await database1.init(conf.databaseURL, conf.databaseName); + let transactions = []; + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "harpagon", "quantity": "5200", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKN", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'issue', '{ "symbol": "TKN", "quantity": "100", "to": "satoshi", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi", "symbol": "TKN", "quantity": "50", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'satoshi', 'tokens', 'stake', '{ "to":"satoshi", "to":"satoshi2", "symbol": "TKN", "quantity": "10", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TKNB", "precision": 8, "maxSupply": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKNB", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKNB", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "maxBalancesProcessedPerBlock": 2, "processQueryLimit": 1, "maxLotteriesPerBlock": 1 }')); + + let block = { + refHiveBlockNumber: 12345678901, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertNoErrorInLastBlock(); + + await assertTokenPool('TKN', 'TKN:TKN'); + + await assertUserBalances('harpagon', 'TKN', '0.00000000', 0); + await assertUserBalances('satoshi', 'TKN', '40.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '10.00000000'); + + await assertMiningPower('satoshi', 'TKN:TKN', '50'); + await assertMiningPower('satoshi2', 'TKN:TKN', 0); + await assertPool({ id: 'TKN:TKN', totalPower: '50', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: true, lastAccountId: 7, tokenIndex: 0 }); + await assertPool({ id: 'TKNB:TKN', totalPower: '0', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: true, lastAccountId: 0, tokenIndex: 0 }); + + transactions = []; + transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678902, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertMiningPower('satoshi2', 'TKN:TKN', '10'); + await assertPool({ id: 'TKN:TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: false, lastAccountId: 0, tokenIndex: 0 }); + await assertPool({ id: 'TKNB:TKN', totalPower: '0', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: true, lastAccountId: 0, tokenIndex: 0 }); + + transactions = []; + transactions.push(new Transaction(12345678903, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678903, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertMiningPower('satoshi', 'TKNB:TKN', '50'); + await assertMiningPower('satoshi2', 'TKNB:TKN', 0); + await assertPool({ id: 'TKNB:TKN', totalPower: '50', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: true, lastAccountId: 7, tokenIndex: 0 }); + + transactions = []; + transactions.push(new Transaction(12345678904, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678904, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T01:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + await assertMiningPower('satoshi2', 'TKNB:TKN', '10'); + await assertPool({ id: 'TKNB:TKN', totalPower: '60', nextLotteryTimestamp: new Date('2018-06-01T01:00:00.000Z').getTime() }, { inProgress: false, lastAccountId: 0, tokenIndex: 0 }); + + let res = (await database1.getLatestBlockInfo()); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + assert(virtualEventLog.events.filter(x => x.event === 'miningLottery').length === 1, 'Expected 1 miningLottery'); + let lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKN:TKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi"); + assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); + + await assertUserBalances('satoshi', 'TKN', '41.00000000', '50.00000000'); + await assertUserBalances('satoshi2', 'TKN', 0, '10.00000000'); + + transactions = []; + transactions.push(new Transaction(12345678905, getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: 12345678905, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T01:00:00', + transactions, + }; + await send(blockchain.PLUGIN_NAME, 'MASTER', { action: blockchain.PLUGIN_ACTIONS.PRODUCE_NEW_BLOCK_SYNC, payload: block }); + + res = (await database1.getLatestBlockInfo()); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + assert(virtualEventLog.events.filter(x => x.event === 'miningLottery').length === 1, 'Expected only 1 miningLottery'); + lotteryEvent = virtualEventLog.events.find(x => x.event === 'miningLottery'); + assert.ok(lotteryEvent, 'Expected to find miningLottery event'); + assert.equal(lotteryEvent.data.poolId, 'TKNB:TKN'); + assert.equal(lotteryEvent.data.winners.length, 1); + assert.equal(lotteryEvent.data.winners[0].winner, "satoshi2"); + assert.equal(lotteryEvent.data.winners[0].winningAmount, "1.00000000"); + + await assertUserBalances('satoshi', 'TKNB', 0, 0); + await assertUserBalances('satoshi2', 'TKNB', '1.00000000', 0); + + resolve(); + }) + .then(() => { + unloadPlugin(blockchain); + database1.close(); + done(); + }); + }); + }); From 2b9a95e6898b0316c4b050c9b3fb77000a9bed27 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 15:59:59 +0000 Subject: [PATCH 08/11] remove debug --- contracts/mining.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index b78ebc86..10bfd8c7 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -159,10 +159,8 @@ async function initMiningPower(pool, params, token, lastAccountId) { let lastAccountIdProcessed = lastAccountId; let complete = false; let balances; - api.emit('debug', { pool, params, token, lastAccountId }); while (!complete && offset < params.maxBalancesProcessedPerBlock) { balances = await api.db.findInTable('tokens', 'balances', { symbol: token, _id: { $gt: lastAccountId } }, params.processQueryLimit, offset, [{ index: '_id', descending: false }]); - api.emit('debug', balances); for (let i = 0; i < balances.length; i += 1) { const balance = balances[i]; if (api.BigNumber(balance.stake).gt(0) || api.BigNumber(balance.delegationsIn).gt(0)) { @@ -178,7 +176,6 @@ async function initMiningPower(pool, params, token, lastAccountId) { } offset += params.processQueryLimit; } - api.emit('debug', { adjustedPower, lastAccountIdProcessed, complete }); return { adjustedPower, nextAccountId: lastAccountIdProcessed, complete }; } From 564663dfc73675abda7f731726564c019c141c21 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 16:01:03 +0000 Subject: [PATCH 09/11] use regular executeSmartContract --- contracts/tokens.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/tokens.js b/contracts/tokens.js index de552c23..3625dc30 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -1482,7 +1482,7 @@ const processUnstake = async (unstake) => { if (symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'") { // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', { account }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity: api.BigNumber(tokensToRelease).negated() }); } } @@ -1656,7 +1656,7 @@ actions.stakeFromContract = async (payload) => { // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', // { account: finalTo }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity }); } } @@ -1695,7 +1695,7 @@ const startUnstake = async (account, token, quantity) => { if (token.symbol === "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'") { // await api.executeSmartContract('witnesses', 'updateWitnessesApprovals', { account }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', { + await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol: token.symbol, quantity: api.BigNumber(nextTokensToRelease).negated(), @@ -1795,7 +1795,7 @@ const processCancelUnstake = async (unstake) => { // await api.executeSmartContract // ('witnesses', 'updateWitnessesApprovals', { account: api.sender }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity: tokensToRelease }); return true; @@ -1998,11 +1998,11 @@ actions.delegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalTo }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity, delegated: true, }); - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); } else { // if a delegation already exists, increase it @@ -2043,11 +2043,11 @@ actions.delegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalTo }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity, delegated: true, }); - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); } } @@ -2143,7 +2143,7 @@ actions.undelegate = async (payload) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account: finalFrom }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account: finalFrom, symbol, @@ -2195,7 +2195,7 @@ const processUndelegation = async (undelegation) => { // await api.executeSmartContract('witnesses', // 'updateWitnessesApprovals', { account }); } - await api.executeSmartContractAsOwner('mining', 'handleStakeChange', + await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity }); } } From 202fe0737e527a5af9bc7a5535a32b43dca0d171 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 16:03:42 +0000 Subject: [PATCH 10/11] fix update params logic to enable 0 for fees --- contracts/mining.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/mining.js b/contracts/mining.js index 10bfd8c7..88bbdbe0 100644 --- a/contracts/mining.js +++ b/contracts/mining.js @@ -35,11 +35,11 @@ actions.updateParams = async (payload) => { const params = await api.db.findOne('params', {}); if (poolCreationFee) { - if (!api.assert(typeof poolCreationFee === 'string' && !api.BigNumber(poolCreationFee).isNaN() && api.BigNumber(poolCreationFee).gt(0), 'invalid poolCreationFee')) return; + if (!api.assert(typeof poolCreationFee === 'string' && !api.BigNumber(poolCreationFee).isNaN() && api.BigNumber(poolCreationFee).gte(0), 'invalid poolCreationFee')) return; params.poolCreationFee = poolCreationFee; } if (poolUpdateFee) { - if (!api.assert(typeof poolUpdateFee === 'string' && !api.BigNumber(poolUpdateFee).isNaN() && api.BigNumber(poolUpdateFee).gt(0), 'invalid poolUpdateFee')) return; + if (!api.assert(typeof poolUpdateFee === 'string' && !api.BigNumber(poolUpdateFee).isNaN() && api.BigNumber(poolUpdateFee).gte(0), 'invalid poolUpdateFee')) return; params.poolUpdateFee = poolUpdateFee; } if (maxLotteriesPerBlock) { From fa2ab60c90d77de186ff9b6b41a64c79898898b8 Mon Sep 17 00:00:00 2001 From: Evan Chou Date: Wed, 30 Sep 2020 16:05:06 +0000 Subject: [PATCH 11/11] actually fix update params logic to enable 0 for fees (string fees) --- test/mining.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mining.js b/test/mining.js index 7f2e5b76..6c0d2f9d 100644 --- a/test/mining.js +++ b/test/mining.js @@ -293,7 +293,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableStaking', '{ "symbol": "TKN", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'tokens', 'enableDelegation', '{ "symbol": "TKN", "undelegationCooldown": 7, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolCreationFee": 0 }')); + transactions.push(new Transaction(12345678901, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolCreationFee": "0" }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": false }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": 2, "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678901, getNextTxId(), 'harpagon', 'mining', 'createPool', '{ "lotteryWinners": 1, "lotteryIntervalHours": 1, "lotteryAmount": "1", "minedToken": "", "tokenMiners": [{"symbol": "TKN", "multiplier": 1}], "isSignedWithActiveKey": true }')); @@ -700,7 +700,7 @@ describe('mining', function () { transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 0, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 21, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN9", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolUpdateFee": 0 }')); + transactions.push(new Transaction(12345678902, getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'mining', 'updateParams', '{ "poolUpdateFee": "0" }')); transactions.push(new Transaction(12345678902, getNextTxId(), 'satoshi', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "15.7", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "0.000000001", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}, {"symbol": "MTKN", "multiplier": 3}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(12345678902, getNextTxId(), 'harpagon', 'mining', 'updatePool', '{ "id": "TKN:MTKN,TKN", "lotteryWinners": 2, "lotteryIntervalHours": 3, "lotteryAmount": "1", "minedToken": "TKN", "tokenMiners": [{"symbol": "TKN", "multiplier": 2}], "isSignedWithActiveKey": true }'));