From 89dcada227d8b5dc9e80c7fa8665e6f3604df046 Mon Sep 17 00:00:00 2001 From: donchate Date: Tue, 7 Dec 2021 21:36:24 +0000 Subject: [PATCH 1/6] roles contract initial CRUD --- contracts/roles.js | 853 ++++++++++++++++++++++++ contracts/tokens.js | 11 + test/roles.js | 1517 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2381 insertions(+) create mode 100644 contracts/roles.js create mode 100644 test/roles.js diff --git a/contracts/roles.js b/contracts/roles.js new file mode 100644 index 00000000..fa94b28c --- /dev/null +++ b/contracts/roles.js @@ -0,0 +1,853 @@ +/* eslint-disable max-len */ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable no-await-in-loop */ +/* global actions, api */ + +const ContractName = 'roles'; +const FeeMethod = ['burn', 'issuer']; + +actions.createSSC = async () => { + const tableExists = await api.db.tableExists('instances'); + if (tableExists === false) { + await api.db.createTable('instances', ['id', 'lastTickTime']); + await api.db.createTable('roles', [ + 'instanceId', + { name: 'byLastTickTime', index: { instanceId: 1, active: 1, lastTickTime: 1 } }, + ]); + await api.db.createTable('candidates', [ + { name: 'byApprovalWeight', index: { roleId: 1, approvalWeight: 1, active: 1 } }, + ], { primaryKey: ['account', 'roleId'] }); + await api.db.createTable('approvals', ['from', 'to']); + await api.db.createTable('accounts', [], { primaryKey: ['account'] }); + await api.db.createTable('params'); + + const params = {}; + params.instanceCreationFee = '500'; + params.instanceUpdateFee = '100'; + params.instanceTickHours = '24'; + params.roleCreationFee = '50'; + params.roleUpdateFee = '25'; + params.maxSlots = 40; + params.maxInstancePerBlock = 1; + params.maxTxPerBlock = 40; + params.maxAccountApprovals = 50; + params.processQueryLimit = 1000; + await api.db.insert('params', params); + } +}; + +actions.updateParams = async (payload) => { + const { + instanceCreationFee, + instanceUpdateFee, + instanceTickHours, + roleCreationFee, + roleUpdateFee, + maxSlots, + maxInstancePerBlock, + maxTxPerBlock, + maxAccountApprovals, + processQueryLimit, + } = payload; + + if (api.sender !== api.owner) return; + const params = await api.db.findOne('params', {}); + if (instanceCreationFee) { + if (!api.assert(typeof instanceCreationFee === 'string' && !api.BigNumber(instanceCreationFee).isNaN() && api.BigNumber(instanceCreationFee).gte(0), 'invalid instanceCreationFee')) return; + params.instanceCreationFee = instanceCreationFee; + } + if (instanceUpdateFee) { + if (!api.assert(typeof instanceUpdateFee === 'string' && !api.BigNumber(instanceUpdateFee).isNaN() && api.BigNumber(instanceUpdateFee).gte(0), 'invalid instanceUpdateFee')) return; + params.instanceUpdateFee = instanceUpdateFee; + } + if (instanceTickHours) { + if (!api.assert(typeof instanceTickHours === 'string' && api.BigNumber(instanceTickHours).isInteger() && api.BigNumber(instanceTickHours).gte(1), 'invalid instanceTickHours')) return; + params.instanceTickHours = instanceTickHours; + } + if (roleCreationFee) { + if (!api.assert(typeof roleCreationFee === 'string' && !api.BigNumber(roleCreationFee).isNaN() && api.BigNumber(roleCreationFee).gte(0), 'invalid roleCreationFee')) return; + params.roleCreationFee = roleCreationFee; + } + if (roleUpdateFee) { + if (!api.assert(typeof roleUpdateFee === 'string' && !api.BigNumber(roleUpdateFee).isNaN() && api.BigNumber(roleUpdateFee).gte(0), 'invalid roleUpdateFee')) return; + params.roleUpdateFee = roleUpdateFee; + } + if (maxSlots) { + if (!api.assert(typeof maxSlots === 'string' && api.BigNumber(maxSlots).isInteger() && api.BigNumber(maxSlots).gte(1), 'invalid maxSlots')) return; + params.maxSlots = api.BigNumber(maxSlots).toNumber(); + } + if (maxInstancePerBlock) { + if (!api.assert(typeof maxInstancePerBlock === 'string' && api.BigNumber(maxInstancePerBlock).isInteger() && api.BigNumber(maxInstancePerBlock).gte(1), 'invalid maxInstancePerBlock')) return; + params.maxInstancePerBlock = api.BigNumber(maxInstancePerBlock).toNumber(); + } + if (maxTxPerBlock) { + if (!api.assert(typeof maxTxPerBlock === 'string' && api.BigNumber(maxTxPerBlock).isInteger() && api.BigNumber(maxTxPerBlock).gte(1), 'invalid maxTxPerBlock')) return; + params.maxTxPerBlock = api.BigNumber(maxTxPerBlock).toNumber(); + } + if (maxAccountApprovals) { + if (!api.assert(typeof maxAccountApprovals === 'string' && api.BigNumber(maxAccountApprovals).isInteger() && api.BigNumber(maxAccountApprovals).gte(1), 'invalid maxTxPerBlock')) return; + params.maxAccountApprovals = api.BigNumber(maxAccountApprovals).toNumber(); + } + if (processQueryLimit) { + if (!api.assert(typeof processQueryLimit === 'string' && api.BigNumber(processQueryLimit).isInteger() && api.BigNumber(processQueryLimit).gte(1), 'invalid processQueryLimit')) return; + params.processQueryLimit = api.BigNumber(processQueryLimit).toNumber(); + } + await api.db.update('params', params); +}; + +async function updateCandidateWeight(id, deltaApprovalWeight, deltaToken = null) { + const candidate = await api.db.findOne('candidates', { _id: id }); + if (candidate) { + if (deltaToken) { + const inst = await api.db.findOne('instances', { id: candidate.instanceId }); + if (inst.voteToken !== deltaToken.symbol) return true; + } + candidate.approvalWeight = { + $numberDecimal: api.BigNumber(candidate.approvalWeight.$numberDecimal) + .plus(deltaApprovalWeight), + }; + await api.db.update('candidates', candidate); + + const role = await api.db.findOne('roles', { _id: candidate.roleId }); + role.totalApprovalWeight = { + $numberDecimal: api.BigNumber(candidate.approvalWeight.$numberDecimal) + .plus(deltaApprovalWeight), + }; + await api.db.update('roles', role); + + return true; + } + return false; +} + +actions.createInstance = async (payload) => { + const { + voteToken, candidateFee, isSignedWithActiveKey, + } = payload; + + const params = await api.db.findOne('params', {}); + const { instanceCreationFee } = params; + + // 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(instanceCreationFee).lte(0) || api.sender === api.owner + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(instanceCreationFee); + + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') + && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(typeof candidateFee === 'object', 'invalid candidateFee object')) { + if (!api.assert(typeof candidateFee.method === 'string' && FeeMethod.indexOf(candidateFee.method) !== -1 + && typeof candidateFee.symbol === 'string' + && typeof candidateFee.amount === 'string' && api.BigNumber(candidateFee.amount).gte(0), 'invalid candidateFee properties')) return; + const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: candidateFee.symbol }); + if (!api.assert(feeTokenObj && api.BigNumber(candidateFee.amount).dp() <= feeTokenObj.precision, 'invalid candidateFee token or precision')) return; + + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: voteToken }); + if (!api.assert(voteTokenObj && voteTokenObj.stakingEnabled, 'voteToken must have staking enabled')) return; + + const now = new Date(`${api.hiveBlockTimestamp}.000Z`); + const newInstance = { + voteToken, + candidateFee, + active: false, + creator: api.sender, + lastTickTime: now.getTime(), + }; + const insertedInst = await api.db.insert('instances', newInstance); + + if (api.sender !== api.owner + && api.sender !== 'null' + && api.BigNumber(instanceCreationFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: instanceCreationFee, isSignedWithActiveKey, + }); + } + api.emit('createInstance', { id: insertedInst._id }); + } +}; + +actions.updateInstance = async (payload) => { + const { + instanceId, candidateFee, isSignedWithActiveKey, + } = payload; + + const params = await api.db.findOne('params', {}); + const { instanceUpdateFee } = params; + + // 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 authorizedUpdate = api.BigNumber(instanceUpdateFee).lte(0) || api.sender === api.owner + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(instanceUpdateFee); + + if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') + && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(candidateFee, 'specify at least one field to update')) { + const existingInst = await api.db.findOne('instances', { _id: instanceId }); + if (!api.assert(existingInst, 'instance not found') + || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; + + if (candidateFee) { + if (!api.assert(typeof candidateFee === 'object' + && typeof candidateFee.method === 'string' && FeeMethod.indexOf(candidateFee.method) !== -1 + && typeof candidateFee.symbol === 'string' + && typeof candidateFee.amount === 'string' && api.BigNumber(candidateFee.amount).gte(0), 'invalid candidateFee object')) return; + const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: candidateFee.symbol }); + if (!api.assert(feeTokenObj && api.BigNumber(candidateFee.amount).dp() <= feeTokenObj.precision, 'invalid candidateFee token or precision')) return; + existingInst.candidateFee = candidateFee; + } + + await api.db.update('instances', existingInst); + + // burn the token update fees + if (api.sender !== api.owner + && api.sender !== 'null' + && api.BigNumber(instanceUpdateFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: instanceUpdateFee, isSignedWithActiveKey, + }); + } + api.emit('updateInstance', { id: instanceId }); + } +}; + +actions.createRoles = async (payload) => { + const { + instanceId, roles, isSignedWithActiveKey, + } = payload; + + const params = await api.db.findOne('params', {}); + const { roleCreationFee } = params; + + // 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(roleCreationFee).lte(0) || api.sender === api.owner + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(roleCreationFee); + + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') + && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(typeof roles === 'object' && Array.isArray(roles) && roles.length > 0 && roles.length <= 50, 'invalid roles object')) { + const existingInst = await api.db.findOne('instances', { _id: instanceId }); + if (!api.assert(existingInst, 'instance not found') + || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; + + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingInst.voteToken }); + for (let i = 0; i < roles.length; i += 1) { + const role = roles[i]; + if (!api.assert(Object.keys(role).length === 5 + && typeof role.name === 'string' && role.name.length < 50 + && typeof role.voteThreshold === 'string' && api.BigNumber(role.voteThreshold).gte(0) && api.BigNumber(role.voteThreshold).dp() <= voteTokenObj.precision + && typeof role.mainSlots === 'string' && api.BigNumber(role.mainSlots).isInteger() && api.BigNumber(role.mainSlots).gt(0) && api.BigNumber(role.mainSlots).lte(params.maxSlots) + && typeof role.backupSlots === 'string' && api.BigNumber(role.backupSlots).isInteger() && api.BigNumber(role.backupSlots).gte(0) && api.BigNumber(role.backupSlots).lte(api.BigNumber(params.maxSlots).minus(role.mainSlots)) + && typeof role.tickHours === 'string' && api.BigNumber(role.tickHours).isInteger() && api.BigNumber(role.tickHours).gte(params.instanceTickHours), 'invalid roles properties')) return; + } + + const insertedRoles = []; + for (let i = 0; i < roles.length; i += 1) { + const newRole = { + instanceId: existingInst._id, + ...roles[i], + active: true, + lastTickTime: 0, + totalApprovalWeight: 0, + }; + const insertedRole = await api.db.insert('roles', newRole); + insertedRoles.push({ instanceId: insertedRole.instanceId, roleId: insertedRole._id, name: insertedRole.name }); + } + + if (api.sender !== api.owner + && api.sender !== 'null' + && api.BigNumber(roleCreationFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: roleCreationFee, isSignedWithActiveKey, + }); + } + api.emit('createRoles', { roles: insertedRoles }); + } +}; + +actions.updateRole = async (payload) => { + const { + instanceId, roleId, + active, name, voteThreshold, + mainSlots, backupSlots, tickHours, + isSignedWithActiveKey, + } = payload; + + const params = await api.db.findOne('params', {}); + const { roleUpdateFee } = params; + + // 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 authorizedUpdate = api.BigNumber(roleUpdateFee).lte(0) || api.sender === api.owner + ? true + : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(roleUpdateFee); + + if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') + && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(active || name || voteThreshold || mainSlots || backupSlots || tickHours, 'specify at least one field to update')) { + const existingRole = await api.db.findOne('roles', { _id: roleId }); + const existingInst = await api.db.findOne('instances', { _id: instanceId }); + if (!api.assert(existingRole, 'role not found') || !api.assert(existingInst, 'instance not found') + || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; + + if (active) { + existingRole.active = !!active; + } + if (name) { + if (!api.assert(typeof name === 'string' && name.length < 50, 'name must be a string less than 50 characters')) return; + existingRole.name = name; + } + if (voteThreshold) { + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: existingInst.voteToken }); + if (!api.assert(typeof voteThreshold === 'string' + && api.BigNumber(voteThreshold).gte(0) + && api.BigNumber(voteThreshold).dp() <= voteTokenObj.precision, 'voteThreshold must be greater than or equal to 0, precision matching voteToken')) return; + existingRole.voteThreshold = voteThreshold; + } + if (mainSlots) { + if (!api.assert(typeof mainSlots === 'string' + && api.BigNumber(mainSlots).isInteger() + && api.BigNumber(mainSlots).gt(0) + && api.BigNumber(mainSlots).lte(params.maxTxPerBlock), `mainSlots must be a integer between 1 - ${params.maxSlots}`)) return; + existingRole.mainSlots = mainSlots; + } + if (backupSlots) { + const remainingSlots = api.BigNumber(params.maxSlots).minus(existingRole.mainSlots); + if (!api.assert(typeof backupSlots === 'string' + && api.BigNumber(backupSlots).isInteger() + && api.BigNumber(backupSlots).gte(0) + && api.BigNumber(backupSlots).lte(remainingSlots), `backupSlots must be an integer between 0 - ${remainingSlots}`)) return; + existingRole.backupSlots = backupSlots; + } + if (tickHours) { + if (!api.assert(typeof tickHours === 'string' + && api.BigNumber(tickHours).isInteger() + && api.BigNumber(tickHours).gte(params.instanceTickHours), `tickHours must be an integer greater than or equal to ${params.instanceTickHours}`)) return; + existingRole.tickHours = tickHours; + } + + await api.db.update('roles', existingRole); + + // burn the token update fees + if (api.sender !== api.owner + && api.sender !== 'null' + && api.BigNumber(roleUpdateFee).gt(0)) { + await api.executeSmartContract('tokens', 'transfer', { + // eslint-disable-next-line no-template-curly-in-string + to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: roleUpdateFee, isSignedWithActiveKey, + }); + } + api.emit('updateRole', { id: existingRole._id }); + } +}; + +actions.setInstanceActive = async (payload) => { + const { + instanceId, + active, + isSignedWithActiveKey, + } = payload; + + if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key')) { + return; + } + const inst = await api.db.findOne('instances', { _id: instanceId }); + if (api.assert(inst, 'instance does not exist') + && api.assert(inst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) { + inst.active = !!active; + await api.db.update('instances', inst); + api.emit('setInstanceActive', { id: inst.id, active: inst.active }); + } +}; + +actions.applyForRole = async (payload) => { + const { + roleId, + isSignedWithActiveKey, + } = payload; + + if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && !api.assert(typeof roleId === 'string', 'invalid roleId')) { + return; + } + const role = await api.db.findOne('roles', { _id: roleId }); + const existingApply = await api.db.findOne('candidates', { roleId, account: api.sender }); + if (api.assert(role, 'role does not exist') + && api.assert(!existingApply, 'sender already applied for role')) { + const newCandidate = { + roleId, + account: api.sender, + active: true, + approvalWeight: 0, + }; + const insertedId = await api.db.insert('candidates', newCandidate); + api.emit('applyForRole', { id: roleId, candidateId: insertedId }); + } +}; + +actions.toggleApplyForRole = async (payload) => { + const { + roleId, + active, + isSignedWithActiveKey, + } = payload; + + if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && !api.assert(typeof roleId === 'string', 'invalid roleId') + && !api.assert(typeof active === 'string', 'invalid active')) { + return; + } + const role = await api.db.findOne('roles', { _id: roleId }); + const existingApply = await api.db.findOne('candidates', { roleId, account: api.sender }); + if (api.assert(role, 'role does not exist') + && api.assert(existingApply, 'application does not exist for sender')) { + existingApply.active = !!active; + await api.db.update('candidates', existingApply); + api.emit('toggleApplyForRole', { id: roleId, account: existingApply.account, active }); + } +}; + +// deposit actions + +async function updateTokenBalances(role, token, quantity) { + const upRole = role; + if (upRole.tokenBalances) { + const tIndex = upRole.tokenBalances.findIndex(t => t.symbol === token.symbol); + if (tIndex === -1) { + upRole.tokenBalances.push({ symbol: token.symbol, quantity }); + } else { + upRole.tokenBalances[tIndex].quantity = api.BigNumber(upRole.tokenBalances[tIndex].quantity) + .plus(quantity) + .toFixed(token.precision, api.BigNumber.ROUND_DOWN); + } + } else { + upRole.tokenBalances = [ + { symbol: token.symbol, quantity }, + ]; + } + return api.db.update('roles', upRole); +} + +actions.deposit = async (payload) => { + const { + roleId, symbol, quantity, + isSignedWithActiveKey, + } = payload; + + const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); + if (!api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + || !api.assert(typeof quantity === 'string' && api.BigNumber(quantity).gt(0), 'invalid quantity') + || !api.assert(api.BigNumber(quantity).dp() <= depToken.precision, 'quantity precision mismatch')) { + return; + } + + const role = await api.db.findOne('roles', { _id: roleId }); + if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { + const res = await api.executeSmartContract('tokens', 'transferToContract', { symbol, quantity, to: ContractName }); + if (res.errors === undefined + && res.events && res.events.find(el => el.contract === 'tokens' && el.event === 'transferToContract' && el.data.from === api.sender && el.data.to === ContractName && el.data.quantity === quantity) !== undefined) { + const result = await updateTokenBalances(role, depToken, quantity); + api.emit('deposit', { + roleId, + symbol, + quantity, + status: !!result, + }); + } + } +}; + +actions.receiveDtfTokens = async (payload) => { + const { + data, symbol, quantity, + callingContractInfo, + } = payload; + + if (!api.assert(callingContractInfo && callingContractInfo.name === 'tokenfunds', 'not authorized')) return; + if (!api.assert(typeof data === 'object' + && data.constructor.name === 'Object' + && 'roleId' in data && typeof data.roleId === 'string' + && api.BigNumber(data.roleId).isInteger(), 'invalid incoming payload')) return; + + const role = await api.db.findOne('roles', { _id: api.BigNumber(data.roleId).toNumber() }); + if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { + const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); + const result = await updateTokenBalances(role, depToken, quantity); + api.emit('receiveDtfTokens', { + roleId: data.roleId, + symbol, + quantity, + status: !!result, + }); + } +}; + +actions.receiveDistTokens = async (payload) => { + const { + data, symbol, quantity, + callingContractInfo, + } = payload; + + if (!api.assert(callingContractInfo && callingContractInfo.name === 'distribution', 'not authorized')) return; + if (!api.assert(typeof data === 'object' + && data.constructor.name === 'Object' + && 'roleId' in data && typeof data.roleId === 'string' + && api.BigNumber(data.roleId).isInteger(), 'invalid incoming payload')) return; + + const role = await api.db.findOne('roles', { _id: api.BigNumber(data.roleId).toNumber() }); + if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { + const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); + const result = await updateTokenBalances(role, depToken, quantity); + api.emit('receiveDistTokens', { + roleId: data.roleId, + symbol, + quantity, + status: !!result, + }); + } +}; + +// voting + +actions.approveCandidate = async (payload) => { + const { id } = payload; + const params = await api.db.findOne('params', {}); + + if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { + const candidate = await api.db.findOne('candidates', { _id: api.BigNumber(id).toNumber() }); + + if (api.assert(candidate, 'candidate does not exist') + && api.assert(candidate.active, 'candidate is not active')) { + const role = await api.db.findOne('roles', { id: candidate.roleId }); + if (!api.assert(role.active, 'role must be active to approve')) return; + + const inst = await api.db.findOne('instances', { id: role.instanceId }); + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.voteToken }); + let acct = await api.db.findOne('accounts', { account: api.sender }); + if (acct === null) { + acct = { + account: api.sender, + weights: [], + }; + acct = await api.db.insert('accounts', acct); + } + + let activeApprovals = 0; + const approvals = await api.db.find('approvals', + { from: api.sender, candidatePending: true }, + params.maxAccountApprovals, + 0, + [{ index: '_id', descending: true }]); + for (let index = 0; index < approvals.length; index += 1) { + const approval = approvals[index]; + const approvalCandidate = await api.db.findOne('candidates', { _id: approval.to }); + if (approvalCandidate && approvalCandidate.active) { + activeApprovals += 1; + } else { + approval.candidatePending = false; + await api.db.update('approvals', approval); + } + } + if (!api.assert(activeApprovals < params.maxAccountApprovals, `you can only approve ${params.maxAccountApprovals} active candidates`)) return; + + let approval = await api.db.findOne('approvals', { from: api.sender, to: candidate._id }); + if (api.assert(approval === null, 'you already approved this candidate')) { + approval = { + from: api.sender, + to: candidate._id, + candidatePending: true, + }; + await api.db.insert('approvals', approval); + + const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: inst.voteToken }); + let approvalWeight = 0; + if (balance && balance.stake) { + approvalWeight = balance.stake; + } + if (balance && balance.delegationsIn) { + approvalWeight = api.BigNumber(approvalWeight) + .plus(balance.delegationsIn) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); + } + const wIndex = acct.weights.findIndex(x => x.symbol === inst.voteToken); + if (wIndex !== -1) { + acct.weights[wIndex].weight = approvalWeight; + } else { + acct.weights.push({ symbol: inst.voteToken, weight: approvalWeight }); + } + await api.db.update('accounts', acct); + await updateCandidateWeight(candidate._id, approvalWeight); + api.emit('approveCandidate', { id: candidate._id }); + } + } + } +}; + +actions.disapproveCandidate = async (payload) => { + const { id } = payload; + + if (api.assert(typeof id === 'string' && api.BigNumber(id).isInteger(), 'invalid id')) { + const candidate = await api.db.findOne('candidates', { _id: api.BigNumber(id).toNumber() }); + if (api.assert(candidate, 'candidate does not exist')) { + const inst = await api.db.findOne('instances', { id: candidate.instanceId }); + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.voteToken }); + let acct = await api.db.findOne('accounts', { account: api.sender }); + if (acct === null) { + acct = { + account: api.sender, + weights: [], + }; + acct = await api.db.insert('accounts', acct); + } + + const approval = await api.db.findOne('approvals', { from: api.sender, to: candidate._id }); + if (api.assert(approval !== null, 'you have not approved this candidate')) { + await api.db.remove('approvals', approval); + + const balance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: inst.voteToken }); + let approvalWeight = 0; + if (balance && balance.stake) { + approvalWeight = balance.stake; + } + if (balance && balance.delegationsIn) { + approvalWeight = api.BigNumber(approvalWeight) + .plus(balance.delegationsIn) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); + } + const wIndex = acct.weights.findIndex(x => x.symbol === inst.voteToken); + if (wIndex !== -1) { + acct.weights[wIndex].weight = approvalWeight; + } else { + acct.weights.push({ symbol: inst.voteToken, weight: approvalWeight }); + } + await api.db.update('accounts', acct); + await updateCandidateWeight(candidate._id, api.BigNumber(approvalWeight).negated()); + api.emit('disapproveCandidate', { id: candidate._id }); + } + } + } +}; + +actions.updateCandidateApprovals = async (payload) => { + const { account, token, callingContractInfo } = payload; + + if (callingContractInfo === undefined) return; + if (callingContractInfo.name !== 'tokens') return; + + const acct = await api.db.findOne('accounts', { account }); + if (acct !== null) { + const params = await api.db.findOne('params', {}); + + // only update existing weights + const wIndex = acct.weights.findIndex(x => x.symbol === token.symbol); + if (wIndex !== -1) { + // calculate approval weight of the account + const balance = await api.db.findOneInTable('tokens', 'balances', { account, symbol: token.symbol }); + let approvalWeight = 0; + if (balance && balance.stake) { + approvalWeight = balance.stake; + } + + if (balance && balance.delegationsIn) { + approvalWeight = api.BigNumber(approvalWeight) + .plus(balance.delegationsIn) + .toFixed(token.precision, api.BigNumber.ROUND_HALF_UP); + } + + let oldApprovalWeight = 0; + oldApprovalWeight = acct.weights[wIndex].weight; + acct.weights[wIndex].weight = approvalWeight; + + const deltaApprovalWeight = api.BigNumber(approvalWeight) + .minus(oldApprovalWeight) + .dp(token.precision, api.BigNumber.ROUND_HALF_UP); + + if (!api.BigNumber(deltaApprovalWeight).eq(0)) { + await api.db.update('accounts', acct); + const approvals = await api.db.find('approvals', + { from: account, candidatePending: true }, + params.maxAccountApprovals, + 0, + [{ index: '_id', descending: true }]); + for (let index = 0; index < approvals.length; index += 1) { + const approval = approvals[index]; + const candidatePending = await updateCandidateWeight(approval.to, + deltaApprovalWeight, + token); + if (!candidatePending) { + approval.candidatePending = false; + await api.db.update('approvals', approval); + } + } + } + } + } +}; + +// ticks + +async function payRecipient(account, symbol, quantity, type = 'user', contractPayload = null) { + if (api.BigNumber(quantity).gt(0)) { + const res = await api.transferTokens(account, symbol, quantity, type); + if (type === 'contract' && contractPayload) { + await api.executeSmartContract(account, 'receiveRolesTokens', + { data: contractPayload, symbol, quantity }); + } + if (res.errors) { + api.debug(`Error paying out roles of ${quantity} ${symbol} to ${account} (TXID ${api.transactionId}): \n${res.errors}`); + return false; + } + return true; + } + return false; +} + +async function checkPendingCandidates(inst, params) { + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + const upInst = JSON.parse(JSON.stringify(inst)); + const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.voteToken }); + const voteTokenMinValue = api.BigNumber(1) + .dividedBy(api.BigNumber(10).pow(voteTokenObj.precision)); + const random = api.random(); + + const tickTime = api.BigNumber(blockDate.getTime()) + .minus(params.instanceTickHours * 3600 * 1000).toNumber(); + const pendingRoles = await api.db.find('roles', + { + instanceId: inst.id, + active: true, + lastTickTime: { + $lte: tickTime, + }, + }, + params.maxTxPerBlock, + 0, + [{ index: 'byLastTickTime', descending: false }, { index: '_id', descending: false }]); + + for (let i = 0; i < pendingRoles.length; i += 1) { + const funded = []; + const role = pendingRoles[i]; + const payTokens = role.tokenBalances.filter(t => api.BigNumber(t.quantity).gt(0)); + const totalSlots = api.BigNumber(role.mainSlots).plus(role.backupSlots).toNumber(); + + if (payTokens.length > 0) { + let offset = 0; + let candidates = await api.db.find('candidates', + { + roleId: role._id, + active: true, + approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, + }, + params.processQueryLimit, + offset, + [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); + + let accWeight = 0; + let backupWeight; + do { + for (let j = 0; j < candidates.length; j += 1) { + const candidate = candidates[j]; + if (funded.length >= role.mainSlots && backupWeight === null) { + backupWeight = api.BigNumber(accWeight) + .plus(voteTokenMinValue) + .plus(api.BigNumber(role.totalApprovalWeight) + .minus(accWeight) + .times(random) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)) + .toFixed(voteTokenObj.precision); + } + + accWeight = api.BigNumber(accWeight) + .plus(candidate.approvalWeight.$numberDecimal) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); + + if (candidate.active === true) { + if (funded.length < role.mainSlots || api.BigNumber(backupWeight).lte(accWeight)) { + funded.push({ + candidate: candidate._id, + account: candidate.account, + }); + } + } + + if (funded.length >= totalSlots) break; + } + + if (funded.length < totalSlots) { + offset += params.processQueryLimit; + candidates = await api.db.find('candidates', + { + roleId: role._id, + active: true, + approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, + }, + params.processQueryLimit, + offset, + [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); + } + } while (candidates.length > 0 && funded.length < totalSlots); + + for (let k = 0; k < funded.length; k += 1) { + const fund = funded[k]; + for (let l = 0; l < payTokens.length; l += 1) { + const payToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: payTokens[l].symbol }); + const payoutQty = api.BigNumber(payTokens[l].quantity) + .dividedBy(totalSlots) + .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); + if (api.BigNumber(payoutQty).gt(0)) { + const payResult = await payRecipient(fund.account, payTokens[l].symbol, payoutQty); + if (payResult) { + const tbIndex = role.tokenBalances.findIndex(b => b.symbol === payTokens[l].symbol); + role.tokenBalances[tbIndex].quantity = api.BigNumber(role.tokenBalances[tbIndex].quantity) + .minus(payoutQty) + .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); + api.emit('rolePayment', { + roleId: role._id, + account: fund.account, + symbol: payTokens[l].symbol, + quantity: payoutQty, + }); + } + } + } + } + } + } + + upInst.lastTickTime = blockDate.getTime(); + await api.db.update('instances', upInst); +} + +actions.checkPendingInstances = async () => { + if (api.assert(api.sender === 'null', 'not authorized')) { + const params = await api.db.findOne('params', {}); + const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); + const tickTime = api.BigNumber(blockDate.getTime()) + .minus(params.instanceTickHours * 3600 * 1000).toNumber(); + + const pendingInst = await api.db.find('instances', + { + active: true, + lastTickTime: { + $lte: tickTime, + }, + }, + params.maxInstancePerBlock, + 0, + [{ index: 'lastTickTime', descending: false }, { index: '_id', descending: false }]); + + for (let i = 0; i < pendingInst.length; i += 1) { + await checkPendingCandidates(pendingInst[i], params); + } + } +}; diff --git a/contracts/tokens.js b/contracts/tokens.js index d47a3481..601f339b 100644 --- a/contracts/tokens.js +++ b/contracts/tokens.js @@ -757,6 +757,7 @@ const processUnstake = async (unstake) => { await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity: api.BigNumber(nextTokensToRelease).negated() }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account, token }); } await api.db.update('balances', balance); @@ -894,6 +895,7 @@ actions.stake = async (payload) => { await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: finalTo, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: finalTo, token }); } } } @@ -940,6 +942,7 @@ actions.stakeFromContract = async (payload) => { await api.executeSmartContract('mining', 'handleStakeChange', { account: finalTo, symbol, quantity }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: finalTo, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: finalTo, token }); } } } @@ -1000,6 +1003,7 @@ const startUnstake = async (account, token, quantity) => { quantity: api.BigNumber(nextTokensToRelease).negated(), }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account, token }); } } else { return false; @@ -1099,6 +1103,7 @@ const processCancelUnstake = async (unstake) => { await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity: tokensToRelease }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account, token }); return true; } @@ -1291,6 +1296,8 @@ actions.delegate = async (payload) => { { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: api.sender, token }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: finalTo, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: api.sender, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: finalTo, token }); } else { // if a delegation already exists, increase it @@ -1340,6 +1347,8 @@ actions.delegate = async (payload) => { { account: api.sender, symbol, quantity: api.BigNumber(quantity).negated() }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: api.sender, token }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: finalTo, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: api.sender, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: finalTo, token }); } } } @@ -1443,6 +1452,7 @@ actions.undelegate = async (payload) => { delegated: true, }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account: finalFrom, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account: finalFrom, token }); } } } @@ -1492,6 +1502,7 @@ const processUndelegation = async (undelegation) => { await api.executeSmartContract('mining', 'handleStakeChange', { account, symbol, quantity }); await api.executeSmartContract('tokenfunds', 'updateProposalApprovals', { account, token }); + await api.executeSmartContract('roles', 'updateCandidateApprovals', { account, token }); } } }; diff --git a/test/roles.js b/test/roles.js new file mode 100644 index 00000000..0ab4a0be --- /dev/null +++ b/test/roles.js @@ -0,0 +1,1517 @@ +/* eslint-disable */ +const assert = require('assert').strict; +const { MongoClient } = require('mongodb'); +const dhive = require('@hiveio/dhive'); +const enchex = require('crypto-js/enc-hex'); +const BigNumber = require('bignumber.js'); + +const { CONSTANTS } = require('../libs/Constants'); +const { Database } = require('../libs/Database'); +const blockchain = require('../plugins/Blockchain'); +const { Transaction } = require('../libs/Transaction'); +const { setupContractPayload } = require('../libs/util/contractUtil'); +const { Fixture, conf } = require('../libs/util/testing/Fixture'); +const { TableAsserts } = require('../libs/util/testing/TableAsserts'); +const { assertError } = require('../libs/util/testing/Asserts'); + +const tokensContractPayload = setupContractPayload('tokens', './contracts/tokens.js'); +const miningContractPayload = setupContractPayload('mining', './contracts/mining.js'); +const distributionContractPayload = setupContractPayload('distribution', './contracts/distribution.js'); +const inflationContractPayload = setupContractPayload('inflation', './contracts/inflation.js'); +const witnessContractPayload = setupContractPayload('witnesses', './contracts/witnesses.js'); +const dtfContractPayload = setupContractPayload('witnesses', './contracts/witnesses.js'); +const contractPayload = setupContractPayload('roles', './contracts/roles.js'); + +const fixture = new Fixture(); +const tableAsserts = new TableAsserts(fixture); + +async function assertUserWeight(account, symbol, weight = 0) { + const res = await fixture.database.findOne({ + contract: 'tokenfunds', + table: 'accounts', + query: { + account, + 'weights.symbol': symbol, + } + }); + assert.ok(res, `No weight for ${account}, ${symbol}`); + const wIndex = res.weights.findIndex(x => x.symbol === symbol); + assert.equal(res.weights[wIndex].weight, weight, `${account} has ${symbol} weight ${res.weights[wIndex].weight}, expected ${weight}`); +} + +async function assertUserApproval(account, proposalId, present = true) { + const res = await fixture.database.findOne({ + contract: 'tokenfunds', + table: 'approvals', + query: { + from: account, + to: proposalId + } + }); + + if (!present) { + assert(!res, `proposalId found for ${account}, expected none.`); + return; + } + assert.ok(res, `No proposalId for ${account}, ${proposalId}`); +} + +async function assertContractBalance(account, symbol, balance) { + const res = await fixture.database.findOne({ + contract: 'tokens', + table: 'contractsBalances', + query: { account, symbol } + }); + + if (!balance) { + 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}`); +} + +async function assertTokenBalance(id, symbol, balance) { + let hasBalance = false; + let dist = await fixture.database.findOne({ + contract: 'marketpools', + table: 'batches', + query: { + _id: id + } + }); + if (dist.tokenBalances) { + for (let i = 0; i <= dist.tokenBalances.length; i += 1) { + if (dist.tokenBalances[i].symbol === symbol) { + assert.equal(dist.tokenBalances[i].quantity, balance, `contract ${id} has ${symbol} balance ${dist.tokenBalances[i].quantity}, expected ${balance}`); + hasBalance = true; + break; + } + } + if (balance === undefined) { + assert(!hasBalance, `Balance found for contract ${id}, ${symbol}, expected none.`); + return; + } + } + assert.ok(hasBalance, `No balance for contract ${id}, ${symbol}`); +} + +async function assertWeightConsistency(proposalId, voteSymbol) { + const prop = await fixture.database.findOne({ + contract: 'tokenfunds', + table: 'proposals', + query: { _id: proposalId } + }); + const app = await fixture.database.find({ + contract: 'tokenfunds', + table: 'approvals', + query: { to: proposalId } + }); + let appWeight = 0; + for (let i = 0; i < app.length; i += 1) { + const acct = await fixture.database.findOne({ + contract: 'tokenfunds', + table: 'accounts', + query: { account: app[i].from } + }); + const wIndex = acct.weights.findIndex(x => x.symbol === voteSymbol); + if (wIndex !== -1) { + appWeight = BigNumber(appWeight).plus(acct.weights[wIndex].weight).toNumber(); + } + } + assert.equal(appWeight, prop.approvalWeight.$numberDecimal, `prop.approvalWeight (${prop.approvalWeight.$numberDecimal}) doesn\'t equal total of account weights (${appWeight})`); +} + +async function setUpEnv(configOverride = {}) { + let transactions = []; + let refBlockNumber = fixture.getNextRefBlockNumber(); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(witnessContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(dtfContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-05-31T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + await tableAsserts.assertNoErrorInLastBlock(); + + transactions = []; + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'registerTick', '{ "contractName": "roles", "tickAction": "checkPendingInstances"}')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "5000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "buffet", "quantity": "5000", "isSignedWithActiveKey": true }`)); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-05-31T01:00:00', + transactions, + }; + + await fixture.sendBlock(block); + await tableAsserts.assertNoErrorInLastBlock(); +} + +// distribution test suite +describe('roles tests', function () { + this.timeout(30000); + + before((done) => { + new Promise(async (resolve) => { + client = await MongoClient.connect(conf.databaseURL, { useNewUrlParser: true, useUnifiedTopology: 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('should not create invalid instance', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "PRO", "amount": "1" }, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "ABC", "amount": "1" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "PRO", "amount": "1" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + let txs = res.transactions; + + assertError(txs[1], 'you must use a transaction signed with your active key'); + assertError(txs[2], 'invalid candidateFee object'); + assertError(txs[3], 'invalid candidateFee token or precision'); + assertError(txs[4], 'voteToken must have staking enabled'); + + res = await fixture.database.find({ + contract: 'roles', + table: 'instances', + query: { _id: 1 } + }); + + assert.ok(!res || res.length === 0, 'uncaught errors, invalid instance created'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + + }); + + it('should create valid instance', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + res = await fixture.database.findOne({ + contract: 'roles', + table: 'instances', + query: { + _id: 1 + } + }); + assert.ok(res, 'newly created instance not found'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should not update invalid instance', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "burn", "symbol": "PRO", "amount": "1" }, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "burn", "symbol": "ABC", "amount": "1" }, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + let txs = res.transactions; + + assertError(txs[3], 'you must use a transaction signed with your active key'); + assertError(txs[4], 'invalid candidateFee object'); + assertError(txs[5], 'invalid candidateFee token or precision'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + + }); + + it('should update valid instance', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "issuer", "symbol": "PRO", "amount": "10" }, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + let res = await fixture.database.findOne({ + contract: 'roles', + table: 'instances', + query: { + _id: 1 + } + }); + const expected = { + _id: 1, + voteToken: 'PRO', + candidateFee: { method: 'issuer', symbol: 'PRO', amount: '10' }, + active: false, + creator: 'donchate', + lastTickTime: 1527811200000 + } + assert.deepEqual(res, expected, 'updates not found in instance'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should allow owner to update params', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'roles', 'updateParams', '{ "instanceCreationFee": "1", "instanceUpdateFee": "1", "instanceTickHours": "1", "roleCreationFee": "1", "roleUpdateFee": "1", "maxSlots": "1", "maxInstancePerBlock": "1", "maxTxPerBlock": "1", "maxAccountApprovals": "1", "processQueryLimit": "1", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-01T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + let res = await fixture.database.findOne({ + contract: 'roles', + table: 'params', + query: {}, + }); + const expected = { + _id: 1, + instanceCreationFee: '1', + instanceUpdateFee: '1', + instanceTickHours: '1', + roleCreationFee: '1', + roleUpdateFee: '1', + maxSlots: 1, + maxInstancePerBlock: 1, + maxTxPerBlock: 1, + maxAccountApprovals: 1, + processQueryLimit: 1 + }; + assert.deepEqual(res, expected, 'updates not as expected'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should not create invalid roles', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [], "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 2, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker X", "voteThreshold": "-1", "mainSlots": "0", "backupSlots": "5", "tickHours": "12"}], "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + let txs = res.transactions; + + assertError(txs[3], 'you must use a transaction signed with your active key'); + assertError(txs[4], 'instance not found'); + assertError(txs[5], 'invalid roles object'); + assertError(txs[6], 'must be instance creator'); + assertError(txs[7], 'invalid roles properties'); + + res = await fixture.database.find({ + contract: 'roles', + table: 'roles', + query: {} + }); + + assert.ok(!res || res.length === 0, 'uncaught errors, invalid role created'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + + }); + + it('should create valid roles', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + // let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + let resx = await fixture.database.find({ + contract: 'roles', + table: 'roles', + query: {} + }); + assert.ok(resx.length === 2, 'newly created roles not found'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should not update invalid role', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 2, "roleId": 1, "name": "123", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "mainSlots": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "name": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "voteThreshold": "-1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "mainSlots": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "backupSlots": "36", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "tickHours": "6", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + let txs = res.transactions; + + assertError(txs[4], 'specify at least one field to update'); + assertError(txs[5], 'instance not found'); + assertError(txs[6], 'must be instance creator'); + assertError(txs[7], 'name must be a string less than 50 characters'); + assertError(txs[8], 'voteThreshold must be greater than or equal to 0, precision matching voteToken'); + assertError(txs[9], 'mainSlots must be a integer between 1 - 40'); + assertError(txs[10], 'backupSlots must be an integer between 0 - 35'); + assertError(txs[11], 'tickHours must be an integer greater than or equal to 24'); + + res = await fixture.database.findOne({ + contract: 'roles', + table: 'roles', + query: { _id: 1 } + }); + // console.log(res); + const original = { + _id: 1, + instanceId: 1, + name: 'Worker 1', + voteThreshold: '0', + mainSlots: '5', + backupSlots: '2', + tickHours: '24', + active: true, + lastTickTime: 0, + totalApprovalWeight: 0 + }; + assert.deepEqual(res, original, 'role has changed'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should update valid role', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "name": "Worker 1A", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + // let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + let resx = await fixture.database.findOne({ + contract: 'roles', + table: 'roles', + query: { + _id: 1, + } + }); + const updated = { + _id: 1, + instanceId: 1, + name: 'Worker 1A', + voteThreshold: '0', + mainSlots: '5', + backupSlots: '2', + tickHours: '24', + active: true, + lastTickTime: 0, + totalApprovalWeight: 0 + }; + + assert.deepEqual(resx, updated, 'updates not found in role'); + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should not run inactive proposals', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-16T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-18T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Smaller Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-18T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'disableProposal', '{ "id": "3", "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-17T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:SLV'); + assert.equal(e.data.funded.length, 0); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD'}); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should run proposals and update approvals', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "80000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TST", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "TST", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "TST", "quantity": "100", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + // weight asserts + await assertUserWeight('voter1', 'SLV', '1000.00000000'); + await assertUserWeight('voter2', 'SLV', '10000.00000000'); + await assertUserWeight('voter3', 'SLV', '100000.00000000'); + await assertUserWeight('voter4', 'GLD', '10000.00000000'); + await assertUserWeight('organizer', 'GLD', '0.00000000'); + await assertUserApproval('voter1', 2); + await assertUserApproval('voter2', 2); + await assertUserApproval('voter3', 2); + await assertUserApproval('voter4', 3); + await assertUserApproval('organizer', 3); + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "TST", "quantity": "100", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "100", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:SLV'); + assert.equal(e.data.funded.length, 2); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); + await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); + await assertContractBalance('distribution', 'GLD', '1000.00000000'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should run proposals with modified dtfTickHours', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "dtfTickHours": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "2000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + // weight asserts + await assertUserWeight('voter1', 'SLV', '1000.00000000'); + await assertUserWeight('voter2', 'SLV', '10000.00000000'); + await assertUserWeight('voter3', 'SLV', '100000.00000000'); + await assertUserWeight('voter4', 'GLD', '10000.00000000'); + await assertUserWeight('organizer', 'GLD', '0.00000000'); + await assertUserApproval('voter1', 2); + await assertUserApproval('voter2', 2); + await assertUserApproval('voter3', 2); + await assertUserApproval('voter4', 3); + await assertUserApproval('organizer', 3); + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:SLV'); + assert.equal(e.data.funded.length, 2); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '20.83333333'}); + await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '20.83333333'}); + await assertContractBalance('distribution', 'GLD', '41.87499999'); + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T01:00:00', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:SLV'); + assert.equal(e.data.funded.length, 2); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '41.66666666'}); + await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '41.66666666'}); + await assertContractBalance('distribution', 'GLD', '83.74999998'); + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should cap funds and proposals', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "processQueryLimit": "1", "maxDtfsPerBlock": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "2000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + // weight asserts + await assertUserWeight('voter1', 'SLV', '1000.00000000'); + await assertUserWeight('voter2', 'SLV', '10000.00000000'); + await assertUserWeight('voter3', 'SLV', '100000.00000000'); + await assertUserWeight('voter4', 'GLD', '10000.00000000'); + await assertUserWeight('organizer', 'GLD', '0.00000000'); + await assertUserApproval('voter1', 2); + await assertUserApproval('voter2', 2); + await assertUserApproval('voter3', 2); + await assertUserApproval('voter4', 3); + await assertUserApproval('organizer', 3); + await assertWeightConsistency(1, 'SLV'); + await assertWeightConsistency(2, 'SLV'); + await assertWeightConsistency(3, 'GLD'); + await assertWeightConsistency(4, 'GLD'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:GLD'); + assert.equal(e.data.funded.length, 2); + + // balance asserts + await assertContractBalance('distribution', 'GLD', '1005.00000000'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:03', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, 'GLD:SLV'); + assert.equal(e.data.funded.length, 2); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); + await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); + await assertContractBalance('distribution', 'GLD', '1005.00000000'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:06', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + assert.ok(res.virtualTransactions.length === 0, 'Unexpected virtualTransactions'); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); + await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); + await assertContractBalance('distribution', 'GLD', '1005.00000000'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should update stake weight on stake and delegation', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "10000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "10000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "staker", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "staker2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "delegator", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 1, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 1, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableDelegation', '{ "symbol": "GLD", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableDelegation', '{ "symbol": "SLV", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-30T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + // let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokens', 'stake', '{ "to": "staker2", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T01:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + await assertUserWeight('staker', 'SLV'); + await assertUserWeight('staker2', 'SLV', 500); + await assertWeightConsistency(1, 'SLV'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokens', 'stake', '{ "to": "staker", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'stake', '{ "to": "delegator", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'delegate', '{ "to": "staker", "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T02:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + await assertUserWeight('staker', 'SLV', 600); + await assertUserWeight('staker2', 'SLV', 500); + await assertWeightConsistency(1, 'SLV'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'undelegate', '{ "from": "staker", "symbol": "SLV", "quantity": "50", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T02:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + await assertUserWeight('staker', 'SLV', 550); + await assertUserWeight('staker2', 'SLV', 500); + await assertWeightConsistency(1, 'SLV'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2018-06-02T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokens', 'unstake', '{ "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokens', 'unstake', '{ "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T02:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + await assertUserWeight('staker', 'SLV', 450); + await assertUserWeight('staker2', 'SLV', 400); + await assertWeightConsistency(1, 'SLV'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokenfunds', 'disapproveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokenfunds', 'disapproveProposal', '{ "id": "1" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T02:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + await assertUserWeight('staker', 'SLV', 450); + await assertUserWeight('staker2', 'SLV', 400); + await assertWeightConsistency(1, 'SLV'); + await assertUserApproval('staker', 1, false); + await assertUserApproval('staker2', 1, false); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should limit max account approvals', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "maxAccountApprovals": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + let txs = res.transactions; + // console.log(res); + assertError(txs[27], 'you can only approve 2 active proposals'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + it('should establish utility token fund', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = 56428799; + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(inflationContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(miningContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(witnessContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(distributionContractPayload))); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); + addGovernanceTokenTransactions(fixture, transactions, refBlockNumber); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', `{ "symbol": "${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }`)); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + await tableAsserts.assertNoErrorInLastBlock(); + + // utility DTF deployment block + refBlockNumber = 56977200; + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', `{ "to": "voter1", "symbol": "${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "quantity": "1000", "isSignedWithActiveKey": true }`)); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + await tableAsserts.assertNoErrorInLastBlock(); + res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'createFund'); + assert.ok(e, 'Expected to find createFund event'); + + refBlockNumber = 56977201; + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'createProposal', `{ "fundId": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "title": "Pay Distribution XYZ", "startDate": "2021-03-15T01:00:00.000Z", "endDate": "2022-03-14T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "name": "distribution", "contractPayload": { "distId": "1" } }, "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createProposal', `{ "fundId": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "title": "A Big Community Project", "startDate": "2021-03-15T01:00:00.000Z", "endDate": "2022-03-14T00:00:00.000Z", "amountPerDay": "400", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }`)); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-14T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + await tableAsserts.assertNoErrorInLastBlock(); + + refBlockNumber = 56977202; + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-15T02:00:00', + transactions, + }; + + await fixture.sendBlock(block); + res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + + // await tableAsserts.assertNoErrorInLastBlock(); + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + e = virtualEventLog.events.find(x => x.event === 'fundProposals'); + assert.ok(e, 'Expected to find fundProposals event'); + assert.equal(e.data.fundId, `${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}`); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + + // END TESTS +}); From 3635d602a6690658a691149bf83bcd0aa2056459 Mon Sep 17 00:00:00 2001 From: donchate Date: Wed, 8 Dec 2021 03:30:57 +0000 Subject: [PATCH 2/6] stake weight and tick implementation and test --- contracts/roles.js | 112 +++--- test/roles.js | 898 ++++----------------------------------------- 2 files changed, 149 insertions(+), 861 deletions(-) diff --git a/contracts/roles.js b/contracts/roles.js index fa94b28c..cb4024a4 100644 --- a/contracts/roles.js +++ b/contracts/roles.js @@ -15,8 +15,10 @@ actions.createSSC = async () => { { name: 'byLastTickTime', index: { instanceId: 1, active: 1, lastTickTime: 1 } }, ]); await api.db.createTable('candidates', [ + 'account', + { name: 'byAccountRole', index: { roleId: 1, account: 1 } }, { name: 'byApprovalWeight', index: { roleId: 1, approvalWeight: 1, active: 1 } }, - ], { primaryKey: ['account', 'roleId'] }); + ]); await api.db.createTable('approvals', ['from', 'to']); await api.db.createTable('accounts', [], { primaryKey: ['account'] }); await api.db.createTable('params'); @@ -99,7 +101,8 @@ async function updateCandidateWeight(id, deltaApprovalWeight, deltaToken = null) const candidate = await api.db.findOne('candidates', { _id: id }); if (candidate) { if (deltaToken) { - const inst = await api.db.findOne('instances', { id: candidate.instanceId }); + const role = await api.db.findOne('roles', { _id: candidate.roleId }); + const inst = await api.db.findOne('instances', { _id: role.instanceId }); if (inst.voteToken !== deltaToken.symbol) return true; } candidate.approvalWeight = { @@ -110,7 +113,7 @@ async function updateCandidateWeight(id, deltaApprovalWeight, deltaToken = null) const role = await api.db.findOne('roles', { _id: candidate.roleId }); role.totalApprovalWeight = { - $numberDecimal: api.BigNumber(candidate.approvalWeight.$numberDecimal) + $numberDecimal: api.BigNumber(role.totalApprovalWeight.$numberDecimal) .plus(deltaApprovalWeight), }; await api.db.update('roles', role); @@ -136,13 +139,16 @@ actions.createInstance = async (payload) => { : utilityTokenBalance && api.BigNumber(utilityTokenBalance.balance).gte(instanceCreationFee); if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') - && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') - && api.assert(typeof candidateFee === 'object', 'invalid candidateFee object')) { - if (!api.assert(typeof candidateFee.method === 'string' && FeeMethod.indexOf(candidateFee.method) !== -1 - && typeof candidateFee.symbol === 'string' - && typeof candidateFee.amount === 'string' && api.BigNumber(candidateFee.amount).gte(0), 'invalid candidateFee properties')) return; - const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: candidateFee.symbol }); - if (!api.assert(feeTokenObj && api.BigNumber(candidateFee.amount).dp() <= feeTokenObj.precision, 'invalid candidateFee token or precision')) return; + && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key')) { + if (candidateFee) { + if (!api.assert(typeof candidateFee === 'object' + && typeof candidateFee.method === 'string' + && FeeMethod.indexOf(candidateFee.method) !== -1 + && typeof candidateFee.symbol === 'string' + && typeof candidateFee.amount === 'string' && api.BigNumber(candidateFee.amount).gte(0), 'invalid candidateFee properties')) return; + const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: candidateFee.symbol }); + if (!api.assert(feeTokenObj && api.BigNumber(candidateFee.amount).dp() <= feeTokenObj.precision, 'invalid candidateFee token or precision')) return; + } const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: voteToken }); if (!api.assert(voteTokenObj && voteTokenObj.stakingEnabled, 'voteToken must have staking enabled')) return; @@ -193,7 +199,8 @@ actions.updateInstance = async (payload) => { if (candidateFee) { if (!api.assert(typeof candidateFee === 'object' - && typeof candidateFee.method === 'string' && FeeMethod.indexOf(candidateFee.method) !== -1 + && typeof candidateFee.method === 'string' + && FeeMethod.indexOf(candidateFee.method) !== -1 && typeof candidateFee.symbol === 'string' && typeof candidateFee.amount === 'string' && api.BigNumber(candidateFee.amount).gte(0), 'invalid candidateFee object')) return; const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: candidateFee.symbol }); @@ -256,7 +263,7 @@ actions.createRoles = async (payload) => { ...roles[i], active: true, lastTickTime: 0, - totalApprovalWeight: 0, + totalApprovalWeight: { $numberDecimal: '0' }, }; const insertedRole = await api.db.insert('roles', newRole); insertedRoles.push({ instanceId: insertedRole.instanceId, roleId: insertedRole._id, name: insertedRole.name }); @@ -276,7 +283,7 @@ actions.createRoles = async (payload) => { actions.updateRole = async (payload) => { const { - instanceId, roleId, + roleId, active, name, voteThreshold, mainSlots, backupSlots, tickHours, isSignedWithActiveKey, @@ -294,9 +301,9 @@ actions.updateRole = async (payload) => { if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') - && api.assert(active || name || voteThreshold || mainSlots || backupSlots || tickHours, 'specify at least one field to update')) { + && api.assert(typeof active !== 'undefined' || name || voteThreshold || mainSlots || backupSlots || tickHours, 'specify at least one field to update')) { const existingRole = await api.db.findOne('roles', { _id: roleId }); - const existingInst = await api.db.findOne('instances', { _id: instanceId }); + const existingInst = await api.db.findOne('instances', { _id: existingRole.instanceId }); if (!api.assert(existingRole, 'role not found') || !api.assert(existingInst, 'instance not found') || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; @@ -381,17 +388,42 @@ actions.applyForRole = async (payload) => { return; } const role = await api.db.findOne('roles', { _id: roleId }); + if (!api.assert(role, 'role does not exist')) return; + const inst = await api.db.findOne('instances', { _id: role.instanceId }); + + let authorizedCreation = true; + if (inst.candidateFee) { + const feeTokenBalance = await api.db.findOneInTable('tokens', 'balances', { account: api.sender, symbol: inst.candidateFee.symbol }); + authorizedCreation = api.BigNumber(inst.candidateFee.amount).lte(0) || api.sender === api.owner + ? true + : feeTokenBalance && api.BigNumber(feeTokenBalance.balance).gte(inst.candidateFee.amount); + } + const existingApply = await api.db.findOne('candidates', { roleId, account: api.sender }); - if (api.assert(role, 'role does not exist') + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(!existingApply, 'sender already applied for role')) { const newCandidate = { roleId, account: api.sender, active: true, - approvalWeight: 0, + approvalWeight: { $numberDecimal: '0' }, }; const insertedId = await api.db.insert('candidates', newCandidate); - api.emit('applyForRole', { id: roleId, candidateId: insertedId }); + + if (api.sender !== api.owner && inst.candidateFee) { + if (inst.candidateFee.method === 'burn') { + await api.executeSmartContract('tokens', 'transfer', { + to: 'null', symbol: inst.candidateFee.symbol, quantity: inst.candidateFee.amount, + }); + } else if (inst.candidateFee.method === 'issuer') { + const feeTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.candidateFee.symbol }); + await api.executeSmartContract('tokens', 'transfer', { + to: feeTokenObj.issuer, symbol: inst.candidateFee.symbol, quantity: inst.candidateFee.amount, + }); + } + } + + api.emit('applyForRole', { roleId, candidateId: insertedId._id }); } }; @@ -435,7 +467,7 @@ async function updateTokenBalances(role, token, quantity) { { symbol: token.symbol, quantity }, ]; } - return api.db.update('roles', upRole); + await api.db.update('roles', upRole); } actions.deposit = async (payload) => { @@ -456,12 +488,11 @@ actions.deposit = async (payload) => { const res = await api.executeSmartContract('tokens', 'transferToContract', { symbol, quantity, to: ContractName }); if (res.errors === undefined && res.events && res.events.find(el => el.contract === 'tokens' && el.event === 'transferToContract' && el.data.from === api.sender && el.data.to === ContractName && el.data.quantity === quantity) !== undefined) { - const result = await updateTokenBalances(role, depToken, quantity); + await updateTokenBalances(role, depToken, quantity); api.emit('deposit', { roleId, symbol, quantity, - status: !!result, }); } } @@ -482,12 +513,11 @@ actions.receiveDtfTokens = async (payload) => { const role = await api.db.findOne('roles', { _id: api.BigNumber(data.roleId).toNumber() }); if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); - const result = await updateTokenBalances(role, depToken, quantity); + await updateTokenBalances(role, depToken, quantity); api.emit('receiveDtfTokens', { roleId: data.roleId, symbol, quantity, - status: !!result, }); } }; @@ -507,12 +537,11 @@ actions.receiveDistTokens = async (payload) => { const role = await api.db.findOne('roles', { _id: api.BigNumber(data.roleId).toNumber() }); if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); - const result = await updateTokenBalances(role, depToken, quantity); + await updateTokenBalances(role, depToken, quantity); api.emit('receiveDistTokens', { roleId: data.roleId, symbol, quantity, - status: !!result, }); } }; @@ -528,10 +557,10 @@ actions.approveCandidate = async (payload) => { if (api.assert(candidate, 'candidate does not exist') && api.assert(candidate.active, 'candidate is not active')) { - const role = await api.db.findOne('roles', { id: candidate.roleId }); + const role = await api.db.findOne('roles', { _id: candidate.roleId }); if (!api.assert(role.active, 'role must be active to approve')) return; - const inst = await api.db.findOne('instances', { id: role.instanceId }); + const inst = await api.db.findOne('instances', { _id: role.instanceId }); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.voteToken }); let acct = await api.db.findOne('accounts', { account: api.sender }); if (acct === null) { @@ -726,6 +755,7 @@ async function checkPendingCandidates(inst, params) { { instanceId: inst.id, active: true, + 'tokenBalances.0': { $exists: true }, lastTickTime: { $lte: tickTime, }, @@ -735,8 +765,8 @@ async function checkPendingCandidates(inst, params) { [{ index: 'byLastTickTime', descending: false }, { index: '_id', descending: false }]); for (let i = 0; i < pendingRoles.length; i += 1) { - const funded = []; const role = pendingRoles[i]; + const funded = []; const payTokens = role.tokenBalances.filter(t => api.BigNumber(t.quantity).gt(0)); const totalSlots = api.BigNumber(role.mainSlots).plus(role.backupSlots).toNumber(); @@ -753,18 +783,17 @@ async function checkPendingCandidates(inst, params) { [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); let accWeight = 0; - let backupWeight; + let backupWeight = null; do { for (let j = 0; j < candidates.length; j += 1) { const candidate = candidates[j]; if (funded.length >= role.mainSlots && backupWeight === null) { backupWeight = api.BigNumber(accWeight) .plus(voteTokenMinValue) - .plus(api.BigNumber(role.totalApprovalWeight) + .plus(api.BigNumber(role.totalApprovalWeight.$numberDecimal) .minus(accWeight) - .times(random) - .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP)) - .toFixed(voteTokenObj.precision); + .times(random)) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); } accWeight = api.BigNumber(accWeight) @@ -779,7 +808,6 @@ async function checkPendingCandidates(inst, params) { }); } } - if (funded.length >= totalSlots) break; } @@ -797,14 +825,14 @@ async function checkPendingCandidates(inst, params) { } } while (candidates.length > 0 && funded.length < totalSlots); - for (let k = 0; k < funded.length; k += 1) { - const fund = funded[k]; - for (let l = 0; l < payTokens.length; l += 1) { - const payToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: payTokens[l].symbol }); - const payoutQty = api.BigNumber(payTokens[l].quantity) - .dividedBy(totalSlots) - .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); - if (api.BigNumber(payoutQty).gt(0)) { + for (let l = 0; l < payTokens.length; l += 1) { + const payToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: payTokens[l].symbol }); + const payoutQty = api.BigNumber(payTokens[l].quantity) + .dividedBy(totalSlots) + .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); + if (api.BigNumber(payoutQty).gt(0)) { + for (let k = 0; k < funded.length; k += 1) { + const fund = funded[k]; const payResult = await payRecipient(fund.account, payTokens[l].symbol, payoutQty); if (payResult) { const tbIndex = role.tokenBalances.findIndex(b => b.symbol === payTokens[l].symbol); diff --git a/test/roles.js b/test/roles.js index 0ab4a0be..f6eb1189 100644 --- a/test/roles.js +++ b/test/roles.js @@ -19,7 +19,7 @@ const miningContractPayload = setupContractPayload('mining', './contracts/mining const distributionContractPayload = setupContractPayload('distribution', './contracts/distribution.js'); const inflationContractPayload = setupContractPayload('inflation', './contracts/inflation.js'); const witnessContractPayload = setupContractPayload('witnesses', './contracts/witnesses.js'); -const dtfContractPayload = setupContractPayload('witnesses', './contracts/witnesses.js'); +const dtfContractPayload = setupContractPayload('tokenfunds', './contracts/tokenfunds.js'); const contractPayload = setupContractPayload('roles', './contracts/roles.js'); const fixture = new Fixture(); @@ -27,7 +27,7 @@ const tableAsserts = new TableAsserts(fixture); async function assertUserWeight(account, symbol, weight = 0) { const res = await fixture.database.findOne({ - contract: 'tokenfunds', + contract: 'roles', table: 'accounts', query: { account, @@ -39,21 +39,21 @@ async function assertUserWeight(account, symbol, weight = 0) { assert.equal(res.weights[wIndex].weight, weight, `${account} has ${symbol} weight ${res.weights[wIndex].weight}, expected ${weight}`); } -async function assertUserApproval(account, proposalId, present = true) { +async function assertUserApproval(account, candidateId, present = true) { const res = await fixture.database.findOne({ - contract: 'tokenfunds', + contract: 'roles', table: 'approvals', query: { from: account, - to: proposalId + to: candidateId } }); if (!present) { - assert(!res, `proposalId found for ${account}, expected none.`); + assert(!res, `candidateId found for ${account}, expected none.`); return; } - assert.ok(res, `No proposalId for ${account}, ${proposalId}`); + assert.ok(res, `No candidateId for ${account}, ${candidateId}`); } async function assertContractBalance(account, symbol, balance) { @@ -71,46 +71,21 @@ async function assertContractBalance(account, symbol, balance) { assert.equal(res.balance, balance, `${account} has ${symbol} balance ${res.balance}, expected ${balance}`); } -async function assertTokenBalance(id, symbol, balance) { - let hasBalance = false; - let dist = await fixture.database.findOne({ - contract: 'marketpools', - table: 'batches', - query: { - _id: id - } - }); - if (dist.tokenBalances) { - for (let i = 0; i <= dist.tokenBalances.length; i += 1) { - if (dist.tokenBalances[i].symbol === symbol) { - assert.equal(dist.tokenBalances[i].quantity, balance, `contract ${id} has ${symbol} balance ${dist.tokenBalances[i].quantity}, expected ${balance}`); - hasBalance = true; - break; - } - } - if (balance === undefined) { - assert(!hasBalance, `Balance found for contract ${id}, ${symbol}, expected none.`); - return; - } - } - assert.ok(hasBalance, `No balance for contract ${id}, ${symbol}`); -} - -async function assertWeightConsistency(proposalId, voteSymbol) { +async function assertWeightConsistency(candidateId, voteSymbol) { const prop = await fixture.database.findOne({ - contract: 'tokenfunds', - table: 'proposals', - query: { _id: proposalId } - }); + contract: 'roles', + table: 'candidates', + query: { _id: candidateId } + }); const app = await fixture.database.find({ - contract: 'tokenfunds', + contract: 'roles', table: 'approvals', - query: { to: proposalId } + query: { to: candidateId } }); let appWeight = 0; for (let i = 0; i < app.length; i += 1) { const acct = await fixture.database.findOne({ - contract: 'tokenfunds', + contract: 'roles', table: 'accounts', query: { account: app[i].from } }); @@ -119,7 +94,7 @@ async function assertWeightConsistency(proposalId, voteSymbol) { appWeight = BigNumber(appWeight).plus(acct.weights[wIndex].weight).toNumber(); } } - assert.equal(appWeight, prop.approvalWeight.$numberDecimal, `prop.approvalWeight (${prop.approvalWeight.$numberDecimal}) doesn\'t equal total of account weights (${appWeight})`); + assert.strictEqual(appWeight, BigNumber(prop.approvalWeight.$numberDecimal).toNumber(), `prop.approvalWeight (${prop.approvalWeight.$numberDecimal}) doesn\'t equal total of account weights (${appWeight})`); } async function setUpEnv(configOverride = {}) { @@ -643,280 +618,20 @@ describe('roles tests', function () { }); }); - it('should not run inactive proposals', (done) => { - new Promise(async (resolve) => { - - await fixture.setUp(); await setUpEnv(); - - let refBlockNumber = fixture.getNextRefBlockNumber(); - let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-16T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-18T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Smaller Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-03-18T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'disableProposal', '{ "id": "3", "isSignedWithActiveKey": true }')); - - let block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - let res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - await tableAsserts.assertNoErrorInLastBlock(); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-13T00:00:00', - transactions, - }; - await fixture.sendBlock(block); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-17T00:00:00', - transactions, - }; - await fixture.sendBlock(block); - - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:SLV'); - assert.equal(e.data.funded.length, 0); - - // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD'}); - - resolve(); - }) - .then(() => { - fixture.tearDown(); - done(); - }); - }); - - it('should run proposals and update approvals', (done) => { - new Promise(async (resolve) => { - - await fixture.setUp(); await setUpEnv(); - - let refBlockNumber = fixture.getNextRefBlockNumber(); - let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "80000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "TST", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "TST", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "TST", "quantity": "100", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); - - let block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - let res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - await tableAsserts.assertNoErrorInLastBlock(); - - // weight asserts - await assertUserWeight('voter1', 'SLV', '1000.00000000'); - await assertUserWeight('voter2', 'SLV', '10000.00000000'); - await assertUserWeight('voter3', 'SLV', '100000.00000000'); - await assertUserWeight('voter4', 'GLD', '10000.00000000'); - await assertUserWeight('organizer', 'GLD', '0.00000000'); - await assertUserApproval('voter1', 2); - await assertUserApproval('voter2', 2); - await assertUserApproval('voter3', 2); - await assertUserApproval('voter4', 3); - await assertUserApproval('organizer', 3); - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); - - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "TST", "quantity": "100", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "100", "isSignedWithActiveKey": true }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-13T00:00:00', - transactions, - }; - await fixture.sendBlock(block); - - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T00:00:00', - transactions, - }; - await fixture.sendBlock(block); - - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); - - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:SLV'); - assert.equal(e.data.funded.length, 2); - - // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); - await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); - await assertContractBalance('distribution', 'GLD', '1000.00000000'); - - resolve(); - }) - .then(() => { - fixture.tearDown(); - done(); - }); - }); - - it('should run proposals with modified dtfTickHours', (done) => { + it('should not run inactive roles', (done) => { new Promise(async (resolve) => { await fixture.setUp(); await setUpEnv(); let refBlockNumber = fixture.getNextRefBlockNumber(); let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "dtfTickHours": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "2000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": 1, "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": 2, "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -927,31 +642,10 @@ describe('roles tests', function () { }; await fixture.sendBlock(block); - - let res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - await tableAsserts.assertNoErrorInLastBlock(); - - // weight asserts - await assertUserWeight('voter1', 'SLV', '1000.00000000'); - await assertUserWeight('voter2', 'SLV', '10000.00000000'); - await assertUserWeight('voter3', 'SLV', '100000.00000000'); - await assertUserWeight('voter4', 'GLD', '10000.00000000'); - await assertUserWeight('organizer', 'GLD', '0.00000000'); - await assertUserApproval('voter1', 2); - await assertUserApproval('voter2', 2); - await assertUserApproval('voter3', 2); - await assertUserApproval('voter4', 3); - await assertUserApproval('organizer', 3); - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); - refBlockNumber = fixture.getNextRefBlockNumber(); transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'whatever', 'whatever', '')); block = { refHiveBlockNumber: refBlockNumber, @@ -964,52 +658,10 @@ describe('roles tests', function () { res = (await fixture.database.getLatestBlockInfo()); // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:SLV'); - assert.equal(e.data.funded.length, 2); - - // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '20.83333333'}); - await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '20.83333333'}); - await assertContractBalance('distribution', 'GLD', '41.87499999'); - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T01:00:00', - transactions, - }; - await fixture.sendBlock(block); - - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:SLV'); - assert.equal(e.data.funded.length, 2); + assert.ok(res.virtualTransactions.length === 0, 'Expected to find no virtualTransactions'); // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '41.66666666'}); - await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '41.66666666'}); - await assertContractBalance('distribution', 'GLD', '83.74999998'); - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); + await assertContractBalance('roles', 'BEE', '1'); resolve(); }) @@ -1019,51 +671,39 @@ describe('roles tests', function () { }); }); - it('should cap funds and proposals', (done) => { + it('should run roles and update approvals', (done) => { new Promise(async (resolve) => { await fixture.setUp(); await setUpEnv(); let refBlockNumber = fixture.getNextRefBlockNumber(); let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "SLV", "quantity": "100000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "SLV", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "GLD", "quantity": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "processQueryLimit": "1", "maxDtfsPerBlock": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "2000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Small Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "5", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokenfunds', 'approveProposal', '{ "id": "4" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "10000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000001", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "PRO", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "PRO", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "PRO", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "PRO", "quantity": "1000000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": 1, "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": 2, "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'approveCandidate', '{ "id": "2" }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -1080,24 +720,24 @@ describe('roles tests', function () { await tableAsserts.assertNoErrorInLastBlock(); // weight asserts - await assertUserWeight('voter1', 'SLV', '1000.00000000'); - await assertUserWeight('voter2', 'SLV', '10000.00000000'); - await assertUserWeight('voter3', 'SLV', '100000.00000000'); - await assertUserWeight('voter4', 'GLD', '10000.00000000'); - await assertUserWeight('organizer', 'GLD', '0.00000000'); - await assertUserApproval('voter1', 2); - await assertUserApproval('voter2', 2); - await assertUserApproval('voter3', 2); - await assertUserApproval('voter4', 3); - await assertUserApproval('organizer', 3); - await assertWeightConsistency(1, 'SLV'); - await assertWeightConsistency(2, 'SLV'); - await assertWeightConsistency(3, 'GLD'); - await assertWeightConsistency(4, 'GLD'); + await assertUserWeight('voter1', 'PRO', '1000.00000000'); + await assertUserWeight('voter2', 'PRO', '10000.00000000'); + await assertUserWeight('voter3', 'PRO', '100000.00000000'); + await assertUserWeight('voter4', 'PRO', '1000000.00000000'); + await assertUserApproval('voter1', 1); + await assertUserApproval('voter2', 1); + await assertUserApproval('voter3', 1); + await assertUserApproval('voter4', 1); + await assertWeightConsistency(1, 'PRO'); + await assertWeightConsistency(2, 'PRO'); + await assertWeightConsistency(3, 'PRO'); + await assertWeightConsistency(4, 'PRO'); + refBlockNumber = fixture.getNextRefBlockNumber(); transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to": "voter4", "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'unstake', '{ "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: refBlockNumber, @@ -1108,402 +748,22 @@ describe('roles tests', function () { }; await fixture.sendBlock(block); - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T00:00:00', - transactions, - }; - await fixture.sendBlock(block); + await assertWeightConsistency(1, 'PRO'); + await assertWeightConsistency(2, 'PRO'); + await assertWeightConsistency(3, 'PRO'); + await assertWeightConsistency(4, 'PRO'); res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); + console.log(res); assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - let e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:GLD'); - assert.equal(e.data.funded.length, 2); - - // balance asserts - await assertContractBalance('distribution', 'GLD', '1005.00000000'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T00:00:03', - transactions, - }; - await fixture.sendBlock(block); - - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, 'GLD:SLV'); - assert.equal(e.data.funded.length, 2); - - // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); - await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); - await assertContractBalance('distribution', 'GLD', '1005.00000000'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T00:00:06', - transactions, - }; - await fixture.sendBlock(block); - - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); - assert.ok(res.virtualTransactions.length === 0, 'Unexpected virtualTransactions'); + let e = virtualEventLog.events.find(x => x.event === 'rolePayment'); + assert.ok(e, 'Expected to find rolePayment event'); // balance asserts - await tableAsserts.assertUserBalances({ account: 'rambo', symbol: 'GLD', balance: '500.00000000'}); - await tableAsserts.assertUserBalances({ account: 'silverstein', symbol: 'GLD', balance: '500.00000000'}); - await assertContractBalance('distribution', 'GLD', '1005.00000000'); - - resolve(); - }) - .then(() => { - fixture.tearDown(); - done(); - }); - }); - - it('should update stake weight on stake and delegation', (done) => { - new Promise(async (resolve) => { - - await fixture.setUp(); await setUpEnv(); - - let refBlockNumber = fixture.getNextRefBlockNumber(); - let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "10000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "10000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "staker", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "staker2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "delegator", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 1, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 1, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableDelegation', '{ "symbol": "GLD", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableDelegation', '{ "symbol": "SLV", "undelegationCooldown": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "10000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-30T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); - - let block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - // let res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - await tableAsserts.assertNoErrorInLastBlock(); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokens', 'stake', '{ "to": "staker2", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T01:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - await assertUserWeight('staker', 'SLV'); - await assertUserWeight('staker2', 'SLV', 500); - await assertWeightConsistency(1, 'SLV'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokens', 'stake', '{ "to": "staker", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'stake', '{ "to": "delegator", "symbol": "SLV", "quantity": "500", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'delegate', '{ "to": "staker", "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T02:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - await assertUserWeight('staker', 'SLV', 600); - await assertUserWeight('staker2', 'SLV', 500); - await assertWeightConsistency(1, 'SLV'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'delegator', 'tokens', 'undelegate', '{ "from": "staker", "symbol": "SLV", "quantity": "50", "isSignedWithActiveKey": true }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T02:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - await assertUserWeight('staker', 'SLV', 550); - await assertUserWeight('staker2', 'SLV', 500); - await assertWeightConsistency(1, 'SLV'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'satoshi', 'whatever', 'whatever', '')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2018-06-02T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokens', 'unstake', '{ "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokens', 'unstake', '{ "symbol": "SLV", "quantity": "100", "isSignedWithActiveKey": true }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T02:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - await assertUserWeight('staker', 'SLV', 450); - await assertUserWeight('staker2', 'SLV', 400); - await assertWeightConsistency(1, 'SLV'); - - refBlockNumber = fixture.getNextRefBlockNumber(); - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker', 'tokenfunds', 'disapproveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'staker2', 'tokenfunds', 'disapproveProposal', '{ "id": "1" }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T02:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - await assertUserWeight('staker', 'SLV', 450); - await assertUserWeight('staker2', 'SLV', 400); - await assertWeightConsistency(1, 'SLV'); - await assertUserApproval('staker', 1, false); - await assertUserApproval('staker2', 1, false); - - resolve(); - }) - .then(() => { - fixture.tearDown(); - done(); - }); - }); - - it('should limit max account approvals', (done) => { - new Promise(async (resolve) => { - - await fixture.setUp(); await setUpEnv(); - - let refBlockNumber = fixture.getNextRefBlockNumber(); - let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'deploy', JSON.stringify(contractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'updateParams', '{ "maxAccountApprovals": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "GLD", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "SLV", "precision": 8, "maxSupply": "1000000" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "GLD", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "SLV", "unstakingCooldown": 7, "numberTransactions": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "SLV", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100000", "to": "voter4", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "GLD", "quantity": "100", "to": "organizer", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "SLV", "quantity": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "SLV", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:SLV", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Big Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "800", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'community', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:SLV", "title": "A Small Community Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test2", "payout": { "type": "user", "name": "silverstein" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createFund', '{ "payToken": "GLD", "voteToken": "GLD", "voteThreshold": "1000", "maxDays": "365", "maxAmountPerDay": "1000", "proposalFee": { "method": "burn", "symbol": "GLD", "amount": "100" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'setDtfActive', '{ "fundId": "GLD:GLD", "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'createProposal', '{ "fundId": "GLD:GLD", "title": "A Big Noble Project", "startDate": "2021-03-14T00:00:00.000Z", "endDate": "2021-04-30T00:00:00.000Z", "amountPerDay": "1000", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "contractPayload": { "id": "1" }, "name": "distribution" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "3" }')); - - let block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - let res = await fixture.database.getLatestBlockInfo(); - let txs = res.transactions; - // console.log(res); - assertError(txs[27], 'you can only approve 2 active proposals'); - - resolve(); - }) - .then(() => { - fixture.tearDown(); - done(); - }); - }); - - it('should establish utility token fund', (done) => { - new Promise(async (resolve) => { - - await fixture.setUp(); await setUpEnv(); - - let refBlockNumber = 56428799; - let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(tokensContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(inflationContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(miningContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(witnessContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(distributionContractPayload))); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'contract', 'update', JSON.stringify(contractPayload))); - addGovernanceTokenTransactions(fixture, transactions, refBlockNumber); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'transfer', `{ "symbol": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}", "to": "donchate", "quantity": "50000", "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokens', 'issue', `{ "symbol": "${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }`)); - - let block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-12T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - let res = await fixture.database.getLatestBlockInfo(); - await tableAsserts.assertNoErrorInLastBlock(); - - // utility DTF deployment block - refBlockNumber = 56977200; - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', `{ "to": "voter1", "symbol": "${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "quantity": "1000", "isSignedWithActiveKey": true }`)); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-13T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - - await tableAsserts.assertNoErrorInLastBlock(); - res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - let e = virtualEventLog.events.find(x => x.event === 'createFund'); - assert.ok(e, 'Expected to find createFund event'); - - refBlockNumber = 56977201; - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'tokenfunds', 'createProposal', `{ "fundId": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "title": "Pay Distribution XYZ", "startDate": "2021-03-15T01:00:00.000Z", "endDate": "2022-03-14T00:00:00.000Z", "amountPerDay": "500", "authorPermlink": "@abc123/test", "payout": { "type": "contract", "name": "distribution", "contractPayload": { "distId": "1" } }, "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokenfunds', 'createProposal', `{ "fundId": "${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}", "title": "A Big Community Project", "startDate": "2021-03-15T01:00:00.000Z", "endDate": "2022-03-14T00:00:00.000Z", "amountPerDay": "400", "authorPermlink": "@abc123/test", "payout": { "type": "user", "name": "rambo" }, "isSignedWithActiveKey": true }`)); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "1" }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-14T00:00:00', - transactions, - }; - - await fixture.sendBlock(block); - await tableAsserts.assertNoErrorInLastBlock(); - - refBlockNumber = 56977202; - transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokenfunds', 'approveProposal', '{ "id": "2" }')); - - block = { - refHiveBlockNumber: refBlockNumber, - refHiveBlockId: 'ABCD1', - prevRefHiveBlockId: 'ABCD2', - timestamp: '2021-03-15T02:00:00', - transactions, - }; - - await fixture.sendBlock(block); - res = await fixture.database.getLatestBlockInfo(); - // console.log(res); - - // await tableAsserts.assertNoErrorInLastBlock(); - assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); - virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); - e = virtualEventLog.events.find(x => x.event === 'fundProposals'); - assert.ok(e, 'Expected to find fundProposals event'); - assert.equal(e.data.fundId, `${CONSTANTS.UTILITY_TOKEN_SYMBOL}:${CONSTANTS.GOVERNANCE_TOKEN_SYMBOL}`); + await tableAsserts.assertUserBalances({ account: 'organizer', symbol: 'BEE', balance: '0.50000000'}); + await tableAsserts.assertUserBalances({ account: 'voter1', symbol: 'BEE', balance: '0.50000000'}); + await assertContractBalance('roles', 'BEE', '0.00000000'); resolve(); }) From 218c9c3a6b56027e66017020d0415f913e93cbf6 Mon Sep 17 00:00:00 2001 From: donchate Date: Wed, 8 Dec 2021 04:18:05 +0000 Subject: [PATCH 3/6] cleaned up for testnet --- contracts/roles.js | 151 +++++++++++++++++++++++---------------------- test/roles.js | 36 +++++------ 2 files changed, 93 insertions(+), 94 deletions(-) diff --git a/contracts/roles.js b/contracts/roles.js index cb4024a4..d1b16754 100644 --- a/contracts/roles.js +++ b/contracts/roles.js @@ -749,16 +749,11 @@ async function checkPendingCandidates(inst, params) { .dividedBy(api.BigNumber(10).pow(voteTokenObj.precision)); const random = api.random(); - const tickTime = api.BigNumber(blockDate.getTime()) - .minus(params.instanceTickHours * 3600 * 1000).toNumber(); const pendingRoles = await api.db.find('roles', { instanceId: inst.id, active: true, 'tokenBalances.0': { $exists: true }, - lastTickTime: { - $lte: tickTime, - }, }, params.maxTxPerBlock, 0, @@ -770,88 +765,94 @@ async function checkPendingCandidates(inst, params) { const payTokens = role.tokenBalances.filter(t => api.BigNumber(t.quantity).gt(0)); const totalSlots = api.BigNumber(role.mainSlots).plus(role.backupSlots).toNumber(); - if (payTokens.length > 0) { - let offset = 0; - let candidates = await api.db.find('candidates', - { - roleId: role._id, - active: true, - approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, - }, - params.processQueryLimit, - offset, - [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); - - let accWeight = 0; - let backupWeight = null; - do { - for (let j = 0; j < candidates.length; j += 1) { - const candidate = candidates[j]; - if (funded.length >= role.mainSlots && backupWeight === null) { - backupWeight = api.BigNumber(accWeight) - .plus(voteTokenMinValue) - .plus(api.BigNumber(role.totalApprovalWeight.$numberDecimal) - .minus(accWeight) - .times(random)) - .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); - } + const roleTickTime = api.BigNumber(blockDate.getTime()) + .minus(role.tickHours * 3600 * 1000).toNumber(); + if (role.lastTickTime < roleTickTime) { + if (payTokens.length > 0) { + let offset = 0; + let candidates = await api.db.find('candidates', + { + roleId: role._id, + active: true, + approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, + }, + params.processQueryLimit, + offset, + [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); + + let accWeight = 0; + let backupWeight = null; + do { + for (let j = 0; j < candidates.length; j += 1) { + const candidate = candidates[j]; + if (funded.length >= role.mainSlots && backupWeight === null) { + backupWeight = api.BigNumber(accWeight) + .plus(voteTokenMinValue) + .plus(api.BigNumber(role.totalApprovalWeight.$numberDecimal) + .minus(accWeight) + .times(random)) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); + } - accWeight = api.BigNumber(accWeight) - .plus(candidate.approvalWeight.$numberDecimal) - .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); + accWeight = api.BigNumber(accWeight) + .plus(candidate.approvalWeight.$numberDecimal) + .toFixed(voteTokenObj.precision, api.BigNumber.ROUND_HALF_UP); - if (candidate.active === true) { - if (funded.length < role.mainSlots || api.BigNumber(backupWeight).lte(accWeight)) { - funded.push({ - candidate: candidate._id, - account: candidate.account, - }); + if (candidate.active === true) { + if (funded.length < role.mainSlots || api.BigNumber(backupWeight).lte(accWeight)) { + funded.push({ + candidate: candidate._id, + account: candidate.account, + }); + } } + if (funded.length >= totalSlots) break; } - if (funded.length >= totalSlots) break; - } - if (funded.length < totalSlots) { - offset += params.processQueryLimit; - candidates = await api.db.find('candidates', - { - roleId: role._id, - active: true, - approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, - }, - params.processQueryLimit, - offset, - [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); - } - } while (candidates.length > 0 && funded.length < totalSlots); - - for (let l = 0; l < payTokens.length; l += 1) { - const payToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: payTokens[l].symbol }); - const payoutQty = api.BigNumber(payTokens[l].quantity) - .dividedBy(totalSlots) - .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); - if (api.BigNumber(payoutQty).gt(0)) { - for (let k = 0; k < funded.length; k += 1) { - const fund = funded[k]; - const payResult = await payRecipient(fund.account, payTokens[l].symbol, payoutQty); - if (payResult) { - const tbIndex = role.tokenBalances.findIndex(b => b.symbol === payTokens[l].symbol); - role.tokenBalances[tbIndex].quantity = api.BigNumber(role.tokenBalances[tbIndex].quantity) - .minus(payoutQty) - .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); - api.emit('rolePayment', { + if (funded.length < totalSlots) { + offset += params.processQueryLimit; + candidates = await api.db.find('candidates', + { roleId: role._id, - account: fund.account, - symbol: payTokens[l].symbol, - quantity: payoutQty, - }); + active: true, + approvalWeight: { $gt: { $numberDecimal: api.BigNumber(role.voteThreshold) } }, + }, + params.processQueryLimit, + offset, + [{ index: 'byApprovalWeight', descending: true }, { index: '_id', descending: false }]); + } + } while (candidates.length > 0 && funded.length < totalSlots); + + for (let l = 0; l < payTokens.length; l += 1) { + const payToken = await api.db.findOneInTable('tokens', 'tokens', { symbol: payTokens[l].symbol }); + const payoutQty = api.BigNumber(payTokens[l].quantity) + .dividedBy(totalSlots) + .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); + if (api.BigNumber(payoutQty).gt(0)) { + for (let k = 0; k < funded.length; k += 1) { + const fund = funded[k]; + const payResult = await payRecipient(fund.account, payTokens[l].symbol, payoutQty); + if (payResult) { + const tbIndex = role.tokenBalances.findIndex(b => b.symbol === payTokens[l].symbol); + role.tokenBalances[tbIndex].quantity = api.BigNumber(role.tokenBalances[tbIndex].quantity) + .minus(payoutQty) + .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); + api.emit('rolePayment', { + roleId: role._id, + account: fund.account, + symbol: payTokens[l].symbol, + quantity: payoutQty, + }); + } } } } } + const upRole = JSON.parse(JSON.stringify(role)); + upRole.lastTickTime = blockDate.getTime(); + await api.db.update('roles', upRole); } } - upInst.lastTickTime = blockDate.getTime(); await api.db.update('instances', upInst); } diff --git a/test/roles.js b/test/roles.js index f6eb1189..b3dffc83 100644 --- a/test/roles.js +++ b/test/roles.js @@ -210,7 +210,7 @@ describe('roles tests', function () { let txs = res.transactions; assertError(txs[1], 'you must use a transaction signed with your active key'); - assertError(txs[2], 'invalid candidateFee object'); + assertError(txs[2], 'invalid candidateFee properties'); assertError(txs[3], 'invalid candidateFee token or precision'); assertError(txs[4], 'voteToken must have staking enabled'); @@ -504,14 +504,13 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 2, "roleId": 1, "name": "123", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "mainSlots": "5", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "name": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "voteThreshold": "-1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "mainSlots": "0", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "backupSlots": "36", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "tickHours": "6", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'updateRole', '{ "roleId": 1, "mainSlots": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "name": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "voteThreshold": "-1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "mainSlots": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "backupSlots": "36", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "tickHours": "6", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -528,13 +527,12 @@ describe('roles tests', function () { let txs = res.transactions; assertError(txs[4], 'specify at least one field to update'); - assertError(txs[5], 'instance not found'); - assertError(txs[6], 'must be instance creator'); - assertError(txs[7], 'name must be a string less than 50 characters'); - assertError(txs[8], 'voteThreshold must be greater than or equal to 0, precision matching voteToken'); - assertError(txs[9], 'mainSlots must be a integer between 1 - 40'); - assertError(txs[10], 'backupSlots must be an integer between 0 - 35'); - assertError(txs[11], 'tickHours must be an integer greater than or equal to 24'); + assertError(txs[5], 'must be instance creator'); + assertError(txs[6], 'name must be a string less than 50 characters'); + assertError(txs[7], 'voteThreshold must be greater than or equal to 0, precision matching voteToken'); + assertError(txs[8], 'mainSlots must be a integer between 1 - 40'); + assertError(txs[9], 'backupSlots must be an integer between 0 - 35'); + assertError(txs[10], 'tickHours must be an integer greater than or equal to 24'); res = await fixture.database.findOne({ contract: 'roles', @@ -552,7 +550,7 @@ describe('roles tests', function () { tickHours: '24', active: true, lastTickTime: 0, - totalApprovalWeight: 0 + totalApprovalWeight: { $numberDecimal: '0' } }; assert.deepEqual(res, original, 'role has changed'); resolve(); @@ -606,7 +604,7 @@ describe('roles tests', function () { tickHours: '24', active: true, lastTickTime: 0, - totalApprovalWeight: 0 + totalApprovalWeight: { $numberDecimal: '0' } }; assert.deepEqual(resx, updated, 'updates not found in role'); @@ -754,7 +752,7 @@ describe('roles tests', function () { await assertWeightConsistency(4, 'PRO'); res = (await fixture.database.getLatestBlockInfo()); - console.log(res); + // console.log(res); assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let e = virtualEventLog.events.find(x => x.event === 'rolePayment'); From 99856351b24c4d0ec0e99e28f4e607d5b0e3addc Mon Sep 17 00:00:00 2001 From: donchate Date: Sun, 2 Jan 2022 23:37:33 +0000 Subject: [PATCH 4/6] throttling implementation --- contracts/roles.js | 75 ++++++++++++++++++++++++++-------------------- test/roles.js | 29 ++++++++++-------- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/contracts/roles.js b/contracts/roles.js index d1b16754..8428324a 100644 --- a/contracts/roles.js +++ b/contracts/roles.js @@ -29,9 +29,9 @@ actions.createSSC = async () => { params.instanceTickHours = '24'; params.roleCreationFee = '50'; params.roleUpdateFee = '25'; - params.maxSlots = 40; - params.maxInstancePerBlock = 1; - params.maxTxPerBlock = 40; + params.maxSlots = 10; + params.maxInstancesPerBlock = 1; + params.maxRolesPerBlock = 4; params.maxAccountApprovals = 50; params.processQueryLimit = 1000; await api.db.insert('params', params); @@ -46,8 +46,8 @@ actions.updateParams = async (payload) => { roleCreationFee, roleUpdateFee, maxSlots, - maxInstancePerBlock, - maxTxPerBlock, + maxInstancesPerBlock, + maxRolesPerBlock, maxAccountApprovals, processQueryLimit, } = payload; @@ -78,16 +78,16 @@ actions.updateParams = async (payload) => { if (!api.assert(typeof maxSlots === 'string' && api.BigNumber(maxSlots).isInteger() && api.BigNumber(maxSlots).gte(1), 'invalid maxSlots')) return; params.maxSlots = api.BigNumber(maxSlots).toNumber(); } - if (maxInstancePerBlock) { - if (!api.assert(typeof maxInstancePerBlock === 'string' && api.BigNumber(maxInstancePerBlock).isInteger() && api.BigNumber(maxInstancePerBlock).gte(1), 'invalid maxInstancePerBlock')) return; - params.maxInstancePerBlock = api.BigNumber(maxInstancePerBlock).toNumber(); + if (maxInstancesPerBlock) { + if (!api.assert(typeof maxInstancesPerBlock === 'string' && api.BigNumber(maxInstancesPerBlock).isInteger() && api.BigNumber(maxInstancesPerBlock).gte(1), 'invalid maxInstancesPerBlock')) return; + params.maxInstancesPerBlock = api.BigNumber(maxInstancesPerBlock).toNumber(); } - if (maxTxPerBlock) { - if (!api.assert(typeof maxTxPerBlock === 'string' && api.BigNumber(maxTxPerBlock).isInteger() && api.BigNumber(maxTxPerBlock).gte(1), 'invalid maxTxPerBlock')) return; - params.maxTxPerBlock = api.BigNumber(maxTxPerBlock).toNumber(); + if (maxRolesPerBlock) { + if (!api.assert(typeof maxRolesPerBlock === 'string' && api.BigNumber(maxRolesPerBlock).isInteger() && api.BigNumber(maxRolesPerBlock).gte(1), 'invalid maxRolesPerBlock')) return; + params.maxRolesPerBlock = api.BigNumber(maxRolesPerBlock).toNumber(); } if (maxAccountApprovals) { - if (!api.assert(typeof maxAccountApprovals === 'string' && api.BigNumber(maxAccountApprovals).isInteger() && api.BigNumber(maxAccountApprovals).gte(1), 'invalid maxTxPerBlock')) return; + if (!api.assert(typeof maxAccountApprovals === 'string' && api.BigNumber(maxAccountApprovals).isInteger() && api.BigNumber(maxAccountApprovals).gte(1), 'invalid maxAccountApprovals')) return; params.maxAccountApprovals = api.BigNumber(maxAccountApprovals).toNumber(); } if (processQueryLimit) { @@ -253,7 +253,7 @@ actions.createRoles = async (payload) => { && typeof role.voteThreshold === 'string' && api.BigNumber(role.voteThreshold).gte(0) && api.BigNumber(role.voteThreshold).dp() <= voteTokenObj.precision && typeof role.mainSlots === 'string' && api.BigNumber(role.mainSlots).isInteger() && api.BigNumber(role.mainSlots).gt(0) && api.BigNumber(role.mainSlots).lte(params.maxSlots) && typeof role.backupSlots === 'string' && api.BigNumber(role.backupSlots).isInteger() && api.BigNumber(role.backupSlots).gte(0) && api.BigNumber(role.backupSlots).lte(api.BigNumber(params.maxSlots).minus(role.mainSlots)) - && typeof role.tickHours === 'string' && api.BigNumber(role.tickHours).isInteger() && api.BigNumber(role.tickHours).gte(params.instanceTickHours), 'invalid roles properties')) return; + && typeof role.tickHours === 'string' && api.BigNumber(role.tickHours).isInteger() && api.BigNumber(role.tickHours).gte(params.instanceTickHours) && api.BigNumber(role.tickHours).mod(params.instanceTickHours).eq(0), 'invalid roles properties')) return; } const insertedRoles = []; @@ -325,7 +325,7 @@ actions.updateRole = async (payload) => { if (!api.assert(typeof mainSlots === 'string' && api.BigNumber(mainSlots).isInteger() && api.BigNumber(mainSlots).gt(0) - && api.BigNumber(mainSlots).lte(params.maxTxPerBlock), `mainSlots must be a integer between 1 - ${params.maxSlots}`)) return; + && api.BigNumber(mainSlots).lte(params.maxSlots), 'mainSlots must be a integer between 1 - params.maxSlots')) return; existingRole.mainSlots = mainSlots; } if (backupSlots) { @@ -333,13 +333,14 @@ actions.updateRole = async (payload) => { if (!api.assert(typeof backupSlots === 'string' && api.BigNumber(backupSlots).isInteger() && api.BigNumber(backupSlots).gte(0) - && api.BigNumber(backupSlots).lte(remainingSlots), `backupSlots must be an integer between 0 - ${remainingSlots}`)) return; + && api.BigNumber(backupSlots).lte(remainingSlots), 'backupSlots must be an integer between 0 - remainingSlots')) return; existingRole.backupSlots = backupSlots; } if (tickHours) { if (!api.assert(typeof tickHours === 'string' && api.BigNumber(tickHours).isInteger() - && api.BigNumber(tickHours).gte(params.instanceTickHours), `tickHours must be an integer greater than or equal to ${params.instanceTickHours}`)) return; + && api.BigNumber(tickHours).gte(params.instanceTickHours) + && api.BigNumber(tickHours).mod(params.instanceTickHours).eq(0), 'tickHours must be an integer greater than or equal to, and a multiple of params.instanceTickHours')) return; existingRole.tickHours = tickHours; } @@ -387,7 +388,7 @@ actions.applyForRole = async (payload) => { && !api.assert(typeof roleId === 'string', 'invalid roleId')) { return; } - const role = await api.db.findOne('roles', { _id: roleId }); + const role = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); if (!api.assert(role, 'role does not exist')) return; const inst = await api.db.findOne('instances', { _id: role.instanceId }); @@ -399,11 +400,11 @@ actions.applyForRole = async (payload) => { : feeTokenBalance && api.BigNumber(feeTokenBalance.balance).gte(inst.candidateFee.amount); } - const existingApply = await api.db.findOne('candidates', { roleId, account: api.sender }); - if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') + const existingApply = await api.db.findOne('candidates', { roleId: role._id, account: api.sender }); + if (api.assert(authorizedCreation, 'you must have enough tokens to cover the application fee') && api.assert(!existingApply, 'sender already applied for role')) { const newCandidate = { - roleId, + roleId: role._id, account: api.sender, active: true, approvalWeight: { $numberDecimal: '0' }, @@ -423,7 +424,7 @@ actions.applyForRole = async (payload) => { } } - api.emit('applyForRole', { roleId, candidateId: insertedId._id }); + api.emit('applyForRole', { roleId: role._id, candidateId: insertedId._id }); } }; @@ -439,17 +440,17 @@ actions.toggleApplyForRole = async (payload) => { && !api.assert(typeof active === 'string', 'invalid active')) { return; } - const role = await api.db.findOne('roles', { _id: roleId }); - const existingApply = await api.db.findOne('candidates', { roleId, account: api.sender }); + const role = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); + const existingApply = await api.db.findOne('candidates', { roleId: role._id, account: api.sender }); if (api.assert(role, 'role does not exist') - && api.assert(existingApply, 'application does not exist for sender')) { + && api.assert(existingApply, 'candidate does not exist for sender')) { existingApply.active = !!active; await api.db.update('candidates', existingApply); - api.emit('toggleApplyForRole', { id: roleId, account: existingApply.account, active }); + api.emit('toggleApplyForRole', { roleId: role._id, account: existingApply.account, active }); } }; -// deposit actions +// deposit async function updateTokenBalances(role, token, quantity) { const upRole = role; @@ -742,20 +743,24 @@ async function payRecipient(account, symbol, quantity, type = 'user', contractPa } async function checkPendingCandidates(inst, params) { + const random = api.random(); const blockDate = new Date(`${api.hiveBlockTimestamp}.000Z`); const upInst = JSON.parse(JSON.stringify(inst)); const voteTokenObj = await api.db.findOneInTable('tokens', 'tokens', { symbol: inst.voteToken }); const voteTokenMinValue = api.BigNumber(1) .dividedBy(api.BigNumber(10).pow(voteTokenObj.precision)); - const random = api.random(); + const instTickTime = api.BigNumber(blockDate.getTime()) + .minus(params.instanceTickHours * 3600 * 1000).toNumber(); + let rolesProcessed = 0; const pendingRoles = await api.db.find('roles', { instanceId: inst.id, active: true, 'tokenBalances.0': { $exists: true }, + lastTickTime: { $lte: instTickTime }, }, - params.maxTxPerBlock, + params.maxRolesPerBlock, 0, [{ index: 'byLastTickTime', descending: false }, { index: '_id', descending: false }]); @@ -764,10 +769,10 @@ async function checkPendingCandidates(inst, params) { const funded = []; const payTokens = role.tokenBalances.filter(t => api.BigNumber(t.quantity).gt(0)); const totalSlots = api.BigNumber(role.mainSlots).plus(role.backupSlots).toNumber(); - const roleTickTime = api.BigNumber(blockDate.getTime()) .minus(role.tickHours * 3600 * 1000).toNumber(); - if (role.lastTickTime < roleTickTime) { + + if (role.lastTickTime <= roleTickTime) { if (payTokens.length > 0) { let offset = 0; let candidates = await api.db.find('candidates', @@ -848,13 +853,17 @@ async function checkPendingCandidates(inst, params) { } } } + rolesProcessed += 1; const upRole = JSON.parse(JSON.stringify(role)); upRole.lastTickTime = blockDate.getTime(); await api.db.update('roles', upRole); } } - upInst.lastTickTime = blockDate.getTime(); - await api.db.update('instances', upInst); + + if (rolesProcessed === 0) { + upInst.lastTickTime = blockDate.getTime(); + await api.db.update('instances', upInst); + } } actions.checkPendingInstances = async () => { @@ -871,7 +880,7 @@ actions.checkPendingInstances = async () => { $lte: tickTime, }, }, - params.maxInstancePerBlock, + params.maxInstancesPerBlock, 0, [{ index: 'lastTickTime', descending: false }, { index: '_id', descending: false }]); diff --git a/test/roles.js b/test/roles.js index b3dffc83..8b3dc0c4 100644 --- a/test/roles.js +++ b/test/roles.js @@ -361,7 +361,7 @@ describe('roles tests', function () { let refBlockNumber = fixture.getNextRefBlockNumber(); let transactions = []; - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'roles', 'updateParams', '{ "instanceCreationFee": "1", "instanceUpdateFee": "1", "instanceTickHours": "1", "roleCreationFee": "1", "roleUpdateFee": "1", "maxSlots": "1", "maxInstancePerBlock": "1", "maxTxPerBlock": "1", "maxAccountApprovals": "1", "processQueryLimit": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'roles', 'updateParams', '{ "instanceCreationFee": "1", "instanceUpdateFee": "1", "instanceTickHours": "1", "roleCreationFee": "1", "roleUpdateFee": "1", "maxSlots": "1", "maxInstancesPerBlock": "1", "maxRolesPerBlock": "1", "maxAccountApprovals": "1", "processQueryLimit": "1", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -387,8 +387,8 @@ describe('roles tests', function () { roleCreationFee: '1', roleUpdateFee: '1', maxSlots: 1, - maxInstancePerBlock: 1, - maxTxPerBlock: 1, + maxInstancesPerBlock: 1, + maxRolesPerBlock: 1, maxAccountApprovals: 1, processQueryLimit: 1 }; @@ -530,9 +530,9 @@ describe('roles tests', function () { assertError(txs[5], 'must be instance creator'); assertError(txs[6], 'name must be a string less than 50 characters'); assertError(txs[7], 'voteThreshold must be greater than or equal to 0, precision matching voteToken'); - assertError(txs[8], 'mainSlots must be a integer between 1 - 40'); - assertError(txs[9], 'backupSlots must be an integer between 0 - 35'); - assertError(txs[10], 'tickHours must be an integer greater than or equal to 24'); + assertError(txs[8], 'mainSlots must be a integer between 1 - params.maxSlots'); + assertError(txs[9], 'backupSlots must be an integer between 0 - remainingSlots'); + assertError(txs[10], 'tickHours must be an integer greater than or equal to, and a multiple of params.instanceTickHours'); res = await fixture.database.findOne({ contract: 'roles', @@ -692,11 +692,11 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": 2, "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'applyForRole', '{ "roleId": 2, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'approveCandidate', '{ "id": "1" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "1" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'approveCandidate', '{ "id": "1" }')); @@ -736,6 +736,7 @@ describe('roles tests', function () { transactions = []; transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to": "voter4", "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'unstake', '{ "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'toggleApplyForRole', '{ "roleId": "2", "active": false, "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: refBlockNumber, @@ -746,13 +747,15 @@ describe('roles tests', function () { }; await fixture.sendBlock(block); + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + await assertWeightConsistency(1, 'PRO'); await assertWeightConsistency(2, 'PRO'); await assertWeightConsistency(3, 'PRO'); await assertWeightConsistency(4, 'PRO'); - res = (await fixture.database.getLatestBlockInfo()); - // console.log(res); assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); let e = virtualEventLog.events.find(x => x.event === 'rolePayment'); From 4c3e5e507b916d4781b6bd454e643c7311906acf Mon Sep 17 00:00:00 2001 From: donchate Date: Mon, 3 Jan 2022 02:47:15 +0000 Subject: [PATCH 5/6] throttling test, general cleanup --- contracts/roles.js | 45 ++++++++-- test/roles.js | 220 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 228 insertions(+), 37 deletions(-) diff --git a/contracts/roles.js b/contracts/roles.js index 8428324a..ea17b9d5 100644 --- a/contracts/roles.js +++ b/contracts/roles.js @@ -192,8 +192,9 @@ actions.updateInstance = async (payload) => { if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(typeof instanceId === 'string' && api.BigNumber(instanceId).isInteger(), 'invalid instanceId') && api.assert(candidateFee, 'specify at least one field to update')) { - const existingInst = await api.db.findOne('instances', { _id: instanceId }); + const existingInst = await api.db.findOne('instances', { _id: api.BigNumber(instanceId).toNumber() }); if (!api.assert(existingInst, 'instance not found') || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; @@ -240,8 +241,9 @@ actions.createRoles = async (payload) => { if (api.assert(authorizedCreation, 'you must have enough tokens to cover the creation fee') && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(typeof instanceId === 'string' && api.BigNumber(instanceId).isInteger(), 'invalid instanceId') && api.assert(typeof roles === 'object' && Array.isArray(roles) && roles.length > 0 && roles.length <= 50, 'invalid roles object')) { - const existingInst = await api.db.findOne('instances', { _id: instanceId }); + const existingInst = await api.db.findOne('instances', { _id: api.BigNumber(instanceId).toNumber() }); if (!api.assert(existingInst, 'instance not found') || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; @@ -301,13 +303,14 @@ actions.updateRole = async (payload) => { if (api.assert(authorizedUpdate, 'you must have enough tokens to cover the update fee') && api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + && api.assert(typeof roleId === 'string' && api.BigNumber(roleId).isInteger(), 'invalid roleId') && api.assert(typeof active !== 'undefined' || name || voteThreshold || mainSlots || backupSlots || tickHours, 'specify at least one field to update')) { - const existingRole = await api.db.findOne('roles', { _id: roleId }); + const existingRole = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); const existingInst = await api.db.findOne('instances', { _id: existingRole.instanceId }); if (!api.assert(existingRole, 'role not found') || !api.assert(existingInst, 'instance not found') || !api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) return; - if (active) { + if (typeof active !== 'undefined') { existingRole.active = !!active; } if (name) { @@ -355,7 +358,7 @@ actions.updateRole = async (payload) => { to: 'null', symbol: "'${CONSTANTS.UTILITY_TOKEN_SYMBOL}$'", quantity: roleUpdateFee, isSignedWithActiveKey, }); } - api.emit('updateRole', { id: existingRole._id }); + api.emit('updateRole', { roleId: existingRole._id }); } }; @@ -366,15 +369,37 @@ actions.setInstanceActive = async (payload) => { isSignedWithActiveKey, } = payload; - if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key')) { + if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + || !api.assert(typeof instanceId === 'string' && api.BigNumber(instanceId).isInteger(), 'invalid instanceId')) { return; } - const inst = await api.db.findOne('instances', { _id: instanceId }); + const inst = await api.db.findOne('instances', { _id: api.BigNumber(instanceId).toNumber() }); if (api.assert(inst, 'instance does not exist') && api.assert(inst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) { inst.active = !!active; await api.db.update('instances', inst); - api.emit('setInstanceActive', { id: inst.id, active: inst.active }); + api.emit('setInstanceActive', { instanceId: inst._id, active: inst.active }); + } +}; + +actions.setRoleActive = async (payload) => { + const { + roleId, + active, + isSignedWithActiveKey, + } = payload; + + if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') + || !api.assert(typeof roleId === 'string' && api.BigNumber(roleId).isInteger(), 'invalid roleId')) { + return; + } + const existingRole = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); + const existingInst = await api.db.findOne('instances', { _id: existingRole.instanceId }); + if (api.assert(existingRole, 'role does not exist') + && api.assert(existingInst.creator === api.sender || api.owner === api.sender, 'must be instance creator')) { + existingRole.active = !!active; + await api.db.update('roles', existingRole); + api.emit('setRoleActive', { roleId: existingRole._id, active: existingRole.active }); } }; @@ -479,12 +504,13 @@ actions.deposit = async (payload) => { const depToken = await api.db.findOneInTable('tokens', 'tokens', { symbol }); if (!api.assert(isSignedWithActiveKey === true, 'you must use a custom_json signed with your active key') + || !api.assert(typeof roleId === 'string' && api.BigNumber(roleId).isInteger(), 'invalid roleId') || !api.assert(typeof quantity === 'string' && api.BigNumber(quantity).gt(0), 'invalid quantity') || !api.assert(api.BigNumber(quantity).dp() <= depToken.precision, 'quantity precision mismatch')) { return; } - const role = await api.db.findOne('roles', { _id: roleId }); + const role = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); if (api.assert(role, 'role not found') && api.assert(role.active, 'role must be active to deposit')) { const res = await api.executeSmartContract('tokens', 'transferToContract', { symbol, quantity, to: ContractName }); if (res.errors === undefined @@ -843,6 +869,7 @@ async function checkPendingCandidates(inst, params) { .minus(payoutQty) .toFixed(payToken.precision, api.BigNumber.ROUND_DOWN); api.emit('rolePayment', { + instanceId: inst.id, roleId: role._id, account: fund.account, symbol: payTokens[l].symbol, diff --git a/test/roles.js b/test/roles.js index 8b3dc0c4..1432d9b1 100644 --- a/test/roles.js +++ b/test/roles.js @@ -278,9 +278,9 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "burn", "symbol": "PRO", "amount": "1" }, "isSignedWithActiveKey": false }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": "1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "burn", "symbol": "ABC", "amount": "1" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": "1", "candidateFee": { "method": "burn", "symbol": "PRO", "amount": "1" }, "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": "1", "candidateFee": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": "1", "candidateFee": { "method": "burn", "symbol": "ABC", "amount": "1" }, "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -317,7 +317,7 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": 1, "candidateFee": { "method": "issuer", "symbol": "PRO", "amount": "10" }, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateInstance', '{ "instanceId": "1", "candidateFee": { "method": "issuer", "symbol": "PRO", "amount": "10" }, "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -412,11 +412,11 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [], "isSignedWithActiveKey": false }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 2, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker X", "voteThreshold": "-1", "mainSlots": "0", "backupSlots": "5", "tickHours": "12"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [], "isSignedWithActiveKey": false }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "2", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker X", "voteThreshold": "-1", "mainSlots": "0", "backupSlots": "5", "tickHours": "12"}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -464,7 +464,7 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -503,14 +503,14 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'updateRole', '{ "roleId": 1, "mainSlots": "5", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "name": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "voteThreshold": "-1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "mainSlots": "0", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "backupSlots": "36", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": 1, "tickHours": "6", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'buffet', 'roles', 'updateRole', '{ "roleId": "1", "mainSlots": "5", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "name": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "voteThreshold": "-1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "mainSlots": "0", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "backupSlots": "36", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "tickHours": "6", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -571,8 +571,8 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "name": "Worker 1A", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "roleId": "1", "name": "Worker 1A", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -626,10 +626,10 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "1000" }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "candidateFee": { "method": "burn", "symbol": "BEE", "amount": "0" }, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": 1, "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": 2, "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": "1", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": "1", "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": "2", "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); let block = { refHiveBlockNumber: refBlockNumber, @@ -688,10 +688,10 @@ describe('roles tests', function () { transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "PRO", "quantity": "100000", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "PRO", "quantity": "1000000", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": 1, "active": true, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": 1, "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'updateRole', '{ "instanceId": 1, "roleId": 1, "active": false, "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": 2, "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": "1", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "5", "backupSlots": "2", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "1", "backupSlots": "1", "tickHours": "168"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setRoleActive', '{ "roleId": "1", "active": false, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": "2", "symbol": "BEE", "quantity": "1", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); @@ -774,5 +774,169 @@ describe('roles tests', function () { }); }); + it('should run roles over several blocks', (done) => { + new Promise(async (resolve) => { + + await fixture.setUp(); await setUpEnv(); + + let refBlockNumber = fixture.getNextRefBlockNumber(); + let transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), CONSTANTS.HIVE_ENGINE_ACCOUNT, 'roles', 'updateParams', '{ "maxInstancesPerBlock": "1", "maxRolesPerBlock": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'create', '{ "isSignedWithActiveKey": true, "name": "token", "symbol": "PRO", "precision": 8, "maxSupply": "10000000" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'enableStaking', '{ "symbol": "PRO", "unstakingCooldown": 3, "numberTransactions": 1, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000", "to": "organizer", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000", "to": "voter1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "10000", "to": "voter2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "100000", "to": "voter3", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'tokens', 'issue', '{ "symbol": "PRO", "quantity": "1000001", "to": "voter4", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'tokens', 'stake', '{ "to":"voter1", "symbol": "PRO", "quantity": "1000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'tokens', 'stake', '{ "to":"voter2", "symbol": "PRO", "quantity": "10000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'stake', '{ "to":"voter3", "symbol": "PRO", "quantity": "100000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to":"voter4", "symbol": "PRO", "quantity": "1000000", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createInstance', '{ "voteToken": "PRO", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'setInstanceActive', '{ "instanceId": "1", "active": true, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'createRoles', '{ "instanceId": "1", "roles": [{ "name": "Worker 1", "voteThreshold": "0", "mainSlots": "3", "backupSlots": "1", "tickHours": "24"},{ "name": "Worker 2", "voteThreshold": "0", "mainSlots": "4", "backupSlots": "0", "tickHours": "24"}], "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": "1", "symbol": "BEE", "quantity": "100", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'donchate', 'roles', 'deposit', '{ "roleId": "2", "symbol": "BEE", "quantity": "100", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'organizer', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'applyForRole', '{ "roleId": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'applyForRole', '{ "roleId": "1", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'applyForRole', '{ "roleId": "2", "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'approveCandidate', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'approveCandidate', '{ "id": "1" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'approveCandidate', '{ "id": "2" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter1', 'roles', 'approveCandidate', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'approveCandidate', '{ "id": "3" }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'roles', 'approveCandidate', '{ "id": "3" }')); + + let block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-12T00:00:00', + transactions, + }; + + await fixture.sendBlock(block); + + let res = await fixture.database.getLatestBlockInfo(); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + // weight asserts + await assertUserWeight('voter1', 'PRO', '1000.00000000'); + await assertUserWeight('voter2', 'PRO', '10000.00000000'); + await assertUserWeight('voter3', 'PRO', '100000.00000000'); + await assertUserWeight('voter4', 'PRO', '1000000.00000000'); + await assertUserApproval('voter1', 1); + await assertUserApproval('voter2', 1); + await assertUserApproval('voter3', 1); + await assertUserApproval('voter4', 1); + await assertWeightConsistency(1, 'PRO'); + await assertWeightConsistency(2, 'PRO'); + await assertWeightConsistency(3, 'PRO'); + await assertWeightConsistency(4, 'PRO'); + + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "2" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:00', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + await assertWeightConsistency(1, 'PRO'); + await assertWeightConsistency(2, 'PRO'); + await assertWeightConsistency(3, 'PRO'); + await assertWeightConsistency(4, 'PRO'); + + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + let virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + let e = virtualEventLog.events.find(x => x.event === 'rolePayment'); + assert.ok(e, 'Expected to find rolePayment event'); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'disapproveCandidate', '{ "id": "2" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:03', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + + await assertWeightConsistency(1, 'PRO'); + await assertWeightConsistency(2, 'PRO'); + await assertWeightConsistency(3, 'PRO'); + await assertWeightConsistency(4, 'PRO'); + + assert.ok(res.virtualTransactions.length > 0, 'Expected to find virtualTransactions'); + virtualEventLog = JSON.parse(res.virtualTransactions[0].logs); + e = virtualEventLog.events.find(x => x.event === 'rolePayment'); + assert.ok(e, 'Expected to find rolePayment event'); + + // balance asserts + await tableAsserts.assertUserBalances({ account: 'organizer', symbol: 'BEE', balance: '25.00000000'}); + await tableAsserts.assertUserBalances({ account: 'voter1', symbol: 'BEE', balance: '25.00000000'}); + await tableAsserts.assertUserBalances({ account: 'voter2', symbol: 'BEE', balance: '25.00000000'}); + await tableAsserts.assertUserBalances({ account: 'voter3', symbol: 'BEE'}); + await tableAsserts.assertUserBalances({ account: 'voter4', symbol: 'BEE'}); + await assertContractBalance('roles', 'BEE', '125.00000000'); + + // let roleState = await fixture.database.find({ + // contract: 'roles', + // table: 'roles', + // query: {} + // }); + // console.log(JSON.stringify(roleState, null, 2)); + + refBlockNumber = fixture.getNextRefBlockNumber(); + transactions = []; + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter2', 'roles', 'approveCandidate', '{ "id": "4" }')); + + block = { + refHiveBlockNumber: refBlockNumber, + refHiveBlockId: 'ABCD1', + prevRefHiveBlockId: 'ABCD2', + timestamp: '2021-03-13T00:00:06', + transactions, + }; + await fixture.sendBlock(block); + + res = (await fixture.database.getLatestBlockInfo()); + // console.log(res); + await tableAsserts.assertNoErrorInLastBlock(); + assert.ok(res.virtualTransactions.length == 0, 'Expected to not find virtualTransactions'); + + resolve(); + }) + .then(() => { + fixture.tearDown(); + done(); + }); + }); + // END TESTS }); From 29d76eb51658467037296926f92eed380c27f1e1 Mon Sep 17 00:00:00 2001 From: donchate Date: Mon, 3 Jan 2022 03:59:40 +0000 Subject: [PATCH 6/6] pre testnet cleanup --- contracts/roles.js | 7 +++---- test/roles.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/roles.js b/contracts/roles.js index ea17b9d5..56d5978f 100644 --- a/contracts/roles.js +++ b/contracts/roles.js @@ -453,7 +453,7 @@ actions.applyForRole = async (payload) => { } }; -actions.toggleApplyForRole = async (payload) => { +actions.setApplyActive = async (payload) => { const { roleId, active, @@ -461,8 +461,7 @@ actions.toggleApplyForRole = async (payload) => { } = payload; if (!api.assert(isSignedWithActiveKey === true, 'you must use a transaction signed with your active key') - && !api.assert(typeof roleId === 'string', 'invalid roleId') - && !api.assert(typeof active === 'string', 'invalid active')) { + && !api.assert(typeof roleId === 'string', 'invalid roleId')) { return; } const role = await api.db.findOne('roles', { _id: api.BigNumber(roleId).toNumber() }); @@ -471,7 +470,7 @@ actions.toggleApplyForRole = async (payload) => { && api.assert(existingApply, 'candidate does not exist for sender')) { existingApply.active = !!active; await api.db.update('candidates', existingApply); - api.emit('toggleApplyForRole', { roleId: role._id, account: existingApply.account, active }); + api.emit('setApplyActive', { roleId: role._id, account: existingApply.account, active }); } }; diff --git a/test/roles.js b/test/roles.js index 1432d9b1..1400c634 100644 --- a/test/roles.js +++ b/test/roles.js @@ -736,7 +736,7 @@ describe('roles tests', function () { transactions = []; transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter4', 'tokens', 'stake', '{ "to": "voter4", "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'tokens', 'unstake', '{ "symbol": "PRO", "quantity": "1", "isSignedWithActiveKey": true }')); - transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'toggleApplyForRole', '{ "roleId": "2", "active": false, "isSignedWithActiveKey": true }')); + transactions.push(new Transaction(refBlockNumber, fixture.getNextTxId(), 'voter3', 'roles', 'setApplyActive', '{ "roleId": "2", "active": false, "isSignedWithActiveKey": true }')); block = { refHiveBlockNumber: refBlockNumber,