diff --git a/contracts/Core/BlockManager.sol b/contracts/Core/BlockManager.sol index 7fdbda91..40bd0aae 100644 --- a/contracts/Core/BlockManager.sol +++ b/contracts/Core/BlockManager.sol @@ -100,7 +100,6 @@ contract BlockManager is Initializable, ACL, BlockStorage { require(sorted[i] > lastVisited, "sorted[i] is not greater than lastVisited"); lastVisited = sorted[i]; accWeight = accWeight + (voteManager.getVoteWeight(epoch, assetId, sorted[i])); - if (disputes[epoch][msg.sender].median == 0 && accWeight > medianWeight) { disputes[epoch][msg.sender].median = sorted[i]; } @@ -116,7 +115,8 @@ contract BlockManager is Initializable, ACL, BlockStorage { disputes[epoch][msg.sender] = Structs.Dispute(0, 0, 0, 0); } - function finalizeDispute(uint256 epoch, uint256 blockId) external initialized checkEpoch(epoch) checkState(parameters.dispute()) { + function finalizeDispute (uint256 epoch, uint256 blockId, uint256 assetPosInBlock) + public initialized checkEpoch(epoch) checkState(parameters.dispute()) { uint256 assetId = disputes[epoch][msg.sender].assetId; require( disputes[epoch][msg.sender].accWeight == voteManager.getTotalInfluenceRevealed(epoch, assetId), @@ -124,9 +124,8 @@ contract BlockManager is Initializable, ACL, BlockStorage { ); uint256 median = disputes[epoch][msg.sender].median; uint256 proposerId = proposedBlocks[epoch][blockId].proposerId; - // require(median > 0, "median can not be zero"); - if (proposedBlocks[epoch][blockId].medians[assetId] != median) { + if (proposedBlocks[epoch][blockId].medians[assetPosInBlock] != median) { proposedBlocks[epoch][blockId].valid = false; stakeManager.slash(proposerId, msg.sender, epoch); } else { diff --git a/contracts/Core/Parameters.sol b/contracts/Core/Parameters.sol index c4709ca5..4887af27 100644 --- a/contracts/Core/Parameters.sol +++ b/contracts/Core/Parameters.sol @@ -6,7 +6,6 @@ import "./ACL.sol"; contract Parameters is ACL { // constant type can be readjusted to some smaller type than uint256 for saving gas (storage variable packing). // penalty not reveal = 0.01% per epch - uint256 public penaltyNotRevealNum = 1; uint256 public penaltyNotRevealDenom = 10000; uint256 public slashPenaltyNum = 10000; @@ -23,7 +22,8 @@ contract Parameters is ACL { uint256 public withdrawReleasePeriod = 5; uint256 public resetLockPenalty = 1; uint256 public maxAge = 100 * 10000; - + // Note : maxAssetsPerStaker should be less than total no of jobs + uint256 public maxAssetsPerStaker = 2; bool public escapeHatchEnabled = true; uint32 private constant _COMMIT = 0; @@ -107,6 +107,11 @@ contract Parameters is ACL { aggregationRange = _aggregationRange; } + + function setmaxAssetsPerStaker(uint256 _maxAssetsPerStaker) external onlyRole(DEFAULT_ADMIN_ROLE) { + maxAssetsPerStaker = _maxAssetsPerStaker; + } + function setMaxAge(uint256 _maxAge) external onlyRole(DEFAULT_ADMIN_ROLE) { maxAge = _maxAge; } diff --git a/contracts/Core/RewardManager.sol b/contracts/Core/RewardManager.sol index 7a7d0b7a..ac06fdd4 100644 --- a/contracts/Core/RewardManager.sol +++ b/contracts/Core/RewardManager.sol @@ -129,13 +129,24 @@ contract RewardManager is Initializable, ACL, RewardStorage { if (mediansLastEpoch.length > 0) { uint256 penalty = 0; for (uint256 i = 0; i < mediansLastEpoch.length; i++) { - uint256 voteLastEpoch = voteManager.getVote(epochLastRevealed, thisStaker.id, i).value; + Structs.Vote memory voteLastEpoch = voteManager.getVote( + epochLastRevealed, + thisStaker.id, + _block.ids[i] - 1 + ); uint256 medianLastEpoch = mediansLastEpoch[i]; - - if (voteLastEpoch > medianLastEpoch) { - penalty = penalty + (previousAge * (voteLastEpoch - medianLastEpoch)**2) / medianLastEpoch**2; - } else { - penalty = penalty + (previousAge * (medianLastEpoch - voteLastEpoch)**2) / medianLastEpoch**2; + + if (voteLastEpoch.weight > 0) { + if (voteLastEpoch.value > medianLastEpoch) { + penalty = penalty + + (previousAge * (voteLastEpoch.value - medianLastEpoch)**2) + /medianLastEpoch**2; + } else { + penalty = penalty + + (previousAge*(medianLastEpoch - voteLastEpoch.value)**2) + /medianLastEpoch**2; + + } } } diff --git a/contracts/Core/VoteManager.sol b/contracts/Core/VoteManager.sol index b41fc4fd..db8d7101 100644 --- a/contracts/Core/VoteManager.sol +++ b/contracts/Core/VoteManager.sol @@ -6,18 +6,21 @@ import "./interface/IParameters.sol"; import "./interface/IStakeManager.sol"; import "./interface/IRewardManager.sol"; import "./interface/IBlockManager.sol"; +import "./interface/IAssetManager.sol"; import "./storage/VoteStorage.sol"; import "../Initializable.sol"; import "./ACL.sol"; +import "../lib/Random.sol"; contract VoteManager is Initializable, ACL, VoteStorage { IParameters public parameters; IStakeManager public stakeManager; IRewardManager public rewardManager; IBlockManager public blockManager; + IAssetManager public assetManager; event Committed(uint256 epoch, uint256 stakerId, bytes32 commitment, uint256 timestamp); - event Revealed(uint256 epoch, uint256 stakerId, uint256 stake, uint256[] values, uint256 timestamp); + event Revealed(uint256 epoch, uint256 stakerId, uint256 stake, Structs.AssignedAsset[] values, uint256 timestamp); modifier checkEpoch(uint256 epoch) { require(epoch == parameters.getEpoch(), "incorrect epoch"); @@ -33,12 +36,14 @@ contract VoteManager is Initializable, ACL, VoteStorage { address stakeManagerAddress, address rewardManagerAddress, address blockManagerAddress, - address parametersAddress + address parametersAddress, + address assetManagerAddress ) external initializer onlyRole(DEFAULT_ADMIN_ROLE) { stakeManager = IStakeManager(stakeManagerAddress); rewardManager = IRewardManager(rewardManagerAddress); blockManager = IBlockManager(blockManagerAddress); parameters = IParameters(parametersAddress); + assetManager = IAssetManager(assetManagerAddress); } function commit(uint256 epoch, bytes32 commitment) external initialized checkEpoch(epoch) checkState(parameters.commit()) { @@ -64,7 +69,7 @@ contract VoteManager is Initializable, ACL, VoteStorage { function reveal( uint256 epoch, bytes32 root, - uint256[] memory values, + Structs.AssignedAsset [] memory values, bytes32[][] memory proofs, bytes32 secret, address stakerAddress @@ -74,17 +79,21 @@ contract VoteManager is Initializable, ACL, VoteStorage { Structs.Staker memory thisStaker = stakeManager.getStaker(thisStakerId); require(commitments[epoch][thisStakerId] != 0x0, "not commited or already revealed"); require(keccak256(abi.encodePacked(epoch, root, secret)) == commitments[epoch][thisStakerId], "incorrect secret/value"); - + require(values.length == parameters.maxAssetsPerStaker(), "Revealed assets not equal to required assets per staker"); + //if revealing self if (msg.sender == stakerAddress) { require(parameters.getState() == parameters.reveal(), "Not reveal state"); require(thisStaker.stake > 0, "nonpositive stake"); for (uint256 i = 0; i < values.length; i++) { - require(MerkleProof.verify(proofs[i], root, keccak256(abi.encodePacked(values[i]))), "invalid merkle proof"); - uint256 influence = stakeManager.getInfluence(thisStakerId); - votes[epoch][thisStakerId][i] = Structs.Vote(values[i], thisStaker.stake); - voteWeights[epoch][i][values[i]] = voteWeights[epoch][i][values[i]] + influence; - totalInfluenceRevealed[epoch][i] = totalInfluenceRevealed[epoch][i] + influence; + if (votes[epoch][thisStakerId][values[i].id - 1].weight == 0) { // If Job Not Revealed before + require(isAssetAllotedToStaker(thisStakerId, i, values[i].id), "Revealed asset not alloted"); + require(MerkleProof.verify(proofs[i], root, keccak256(abi.encodePacked(values[i].value))), "invalid merkle proof"); + uint256 influence = stakeManager.getInfluence(thisStakerId); + votes[epoch][thisStakerId][values[i].id-1] = Structs.Vote(values[i].value, thisStaker.stake); + voteWeights[epoch][values[i].id-1][values[i].value] = voteWeights[epoch][values[i].id-1][values[i].value] + influence; + totalInfluenceRevealed[epoch][values[i].id-1] = totalInfluenceRevealed[epoch][values[i].id-1] + influence; + } } commitments[epoch][thisStakerId] = 0x0; @@ -99,6 +108,13 @@ contract VoteManager is Initializable, ACL, VoteStorage { } } + function isAssetAllotedToStaker(uint256 stakerId, uint256 iteration, uint256 assetId) public view initialized returns (bool) + { + // numBlocks = 10, max= numAssets, seed = iteration+stakerId, epochLength + if ((Random.prng(10, assetManager.getNumAssets(), keccak256(abi.encode( iteration + stakerId)), parameters.epochLength())+(1)) == assetId) return true; + return false; + } + function getCommitment(uint256 epoch, uint256 stakerId) public view returns (bytes32) { //epoch -> stakerid -> commitment return (commitments[epoch][stakerId]); diff --git a/contracts/Core/interface/IAssetManager.sol b/contracts/Core/interface/IAssetManager.sol index 0b0daa02..2923ec32 100644 --- a/contracts/Core/interface/IAssetManager.sol +++ b/contracts/Core/interface/IAssetManager.sol @@ -34,4 +34,5 @@ interface IAssetManager { uint256[] memory jobIDs, uint256 result ); + function getNumAssets() external view returns(uint256); } diff --git a/contracts/Core/interface/IParameters.sol b/contracts/Core/interface/IParameters.sol index 517451c9..28372f5e 100644 --- a/contracts/Core/interface/IParameters.sol +++ b/contracts/Core/interface/IParameters.sol @@ -39,6 +39,8 @@ interface IParameters { function slashPenaltyNum() external view returns (uint256); function slashPenaltyDenom() external view returns (uint256); + + function maxAssetsPerStaker() external view returns (uint256); function getEpoch() external view returns (uint256); diff --git a/contracts/Core/interface/IVoteManager.sol b/contracts/Core/interface/IVoteManager.sol index 705f44f6..0ce5d72e 100644 --- a/contracts/Core/interface/IVoteManager.sol +++ b/contracts/Core/interface/IVoteManager.sol @@ -9,6 +9,7 @@ interface IVoteManager { function reveal( uint256 epoch, bytes32 root, + uint256[] calldata assetIds, uint256[] calldata values, bytes32[][] calldata proofs, bytes32 secret, diff --git a/contracts/lib/Structs.sol b/contracts/lib/Structs.sol index cc9e727e..6fdd12c2 100644 --- a/contracts/lib/Structs.sol +++ b/contracts/lib/Structs.sol @@ -66,4 +66,9 @@ library Structs { uint256 result; uint256 assetType; } -} + + struct AssignedAsset { + uint256 id; + uint256 value; + } +} \ No newline at end of file diff --git a/migrations/src/7_deploy_vote_manager.js b/migrations/src/7_deploy_vote_manager.js index 6f21d653..5fadb13a 100644 --- a/migrations/src/7_deploy_vote_manager.js +++ b/migrations/src/7_deploy_vote_manager.js @@ -1,7 +1,7 @@ const { deployContract } = require('../migrationHelpers'); const deployVoteManager = async () => { - await deployContract('VoteManager'); + await deployContract('VoteManager', ['Random']); }; module.exports = async () => { diff --git a/migrations/src/postDeploymentSetup.js b/migrations/src/postDeploymentSetup.js index c6dfad8e..3e43d61b 100644 --- a/migrations/src/postDeploymentSetup.js +++ b/migrations/src/postDeploymentSetup.js @@ -57,7 +57,8 @@ module.exports = async () => { pendingTransactions.push(await blockManager.initialize(stakeManagerAddress, rewardManagerAddress, voteManagerAddress, assetManagerAddress, parametersAddress)); - pendingTransactions.push(await voteManager.initialize(stakeManagerAddress, rewardManagerAddress, blockManagerAddress, parametersAddress)); + pendingTransactions.push(await voteManager.initialize(stakeManagerAddress, rewardManagerAddress, blockManagerAddress, + parametersAddress, assetManagerAddress)); pendingTransactions.push(await stakeManager.initialize(RAZORAddress, rewardManagerAddress, voteManagerAddress, parametersAddress)); pendingTransactions.push(await rewardManager.initialize(stakeManagerAddress, voteManagerAddress, blockManagerAddress, parametersAddress)); diff --git a/test/BlockManager.js b/test/BlockManager.js index 647f99e3..be374ecb 100644 --- a/test/BlockManager.js +++ b/test/BlockManager.js @@ -18,6 +18,7 @@ const { getIteration, toBigNumber, tokenAmount, + getAssignedAssets, } = require('./helpers/utils'); const { utils } = ethers; @@ -33,7 +34,14 @@ describe('BlockManager', function () { let rewardManager; let parameters; let initializeContracts; - + let maxAssetsPerStaker; + let numAssets; + let weightsPerRevealedAssets = {}; + let blockThisEpoch = { + ids: [], medians: [], + }; + let toBeDisputedAssetId = 0; + let disputedAssetIdIndexInBlock = 0; before(async () => { ({ blockManager, @@ -88,6 +96,19 @@ describe('BlockManager', function () { it('should be able to initialize', async () => { await Promise.all(await initializeContracts()); + // Before Staker could commit even if there were no jobs, now as we are moving to assgined jobs, we need to create them first, and then only commit + await assetManager.grantRole(await parameters.getAssetModifierHash(), signers[0].address); + const url = 'http://testurl.com'; + const selector = 'selector'; + const name = 'test'; + const repeat = true; + let i = 0; + while (i < 9) { await assetManager.createJob(url, selector, name, repeat); i++; } + // By default its 2 setting it 5 + await parameters.setmaxAssetsPerStaker(5); + maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + numAssets = Number(await assetManager.getNumAssets()); + await mineToNextEpoch(); await razor.transfer(signers[5].address, tokenAmount('423000')); await razor.transfer(signers[6].address, tokenAmount('19000')); @@ -138,29 +159,106 @@ describe('BlockManager', function () { await mineToNextState(); + // Staker 5 const proof = []; for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[5]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[5].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[5]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[5].address); + // Staker 6 const proof2 = []; for (let i = 0; i < votes2.length; i++) { proof2.push(tree2.getProofPath(i, true, true)); } - await voteManager.connect(signers[6]).reveal(epoch, tree2.root(), votes2, proof2, + + const assignedAssets2 = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[6].address), votes2, proof2, maxAssetsPerStaker, random); + const assigneedAssetsVotes2 = assignedAssets2[0]; + const assigneedAssetsProofs2 = assignedAssets2[1]; + + await voteManager.connect(signers[6]).reveal(epoch, tree2.root(), assigneedAssetsVotes2, assigneedAssetsProofs2, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[6].address); + // Staker 8 const proof3 = []; for (let i = 0; i < votes3.length; i++) { proof3.push(tree3.getProofPath(i, true, true)); } - await voteManager.connect(signers[8]).reveal(epoch, tree3.root(), votes3, proof3, + + const assignedAssets3 = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[8].address), votes3, proof3, maxAssetsPerStaker, random); + const assigneedAssetsVotes3 = assignedAssets3[0]; + const assigneedAssetsProofs3 = assignedAssets3[1]; + + await voteManager.connect(signers[8]).reveal(epoch, tree3.root(), assigneedAssetsVotes3, assigneedAssetsProofs3, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[8].address); + + // To Form Block Proposal On basis of revealed assets this epoch + const assetRevealedByStaker = {}; + const assetRevealedByStaker2 = {}; + const assetRevealedByStaker3 = {}; + + const stakerIdAccount1 = await stakeManager.stakerIds(signers[5].address); + const stakerIdAccount2 = await stakeManager.stakerIds(signers[6].address); + const stakerIdAccount3 = await stakeManager.stakerIds(signers[8].address); + + const influence1 = await stakeManager.getInfluence(stakerIdAccount1); + const influence2 = await stakeManager.getInfluence(stakerIdAccount2); + const influence3 = await stakeManager.getInfluence(stakerIdAccount3); + + for (let i = 0; i < maxAssetsPerStaker; i++) { + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes[i].id] = []; + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] = []; + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes3[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes3[i].id] = []; + + // To have figure how much stake was revealed for that asset + + // Staker 5 + if (!assetRevealedByStaker[assigneedAssetsVotes[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes[i].id].push(influence1); + assetRevealedByStaker[assigneedAssetsVotes[i].id] = true; + } + + // Staker 6 + if (!assetRevealedByStaker2[assigneedAssetsVotes2[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes2[i].id].push(influence2); + assetRevealedByStaker2[assigneedAssetsVotes2[i].id] = true; + } + + // Staker 8 + if (!assetRevealedByStaker3[assigneedAssetsVotes3[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes3[i].id].push(influence3); + assetRevealedByStaker3[assigneedAssetsVotes3[i].id] = true; + } + } + + // To find a asset id revealed by max no of stakers for better test coverage + let maxRevealsForAsset = 0; + for (const assetId of Object.keys(weightsPerRevealedAssets)) { + if (maxRevealsForAsset < weightsPerRevealedAssets[assetId].length) { + maxRevealsForAsset = weightsPerRevealedAssets[assetId].length; + toBeDisputedAssetId = assetId; + } + } + // Forming block + // Purposefully proposing malicious value for assetTobeDisputed + for (let i = 1; i <= numAssets; i++) { + if (typeof weightsPerRevealedAssets[i] !== 'undefined') { + blockThisEpoch.ids.push(i); + if (i === parseInt(toBeDisputedAssetId, 10)) { + disputedAssetIdIndexInBlock = blockThisEpoch.medians.length; + blockThisEpoch.medians.push(i * 100 + 1); + } else blockThisEpoch.medians.push(i * 100); + } + } }); it('should be able to propose', async function () { @@ -174,8 +272,8 @@ describe('BlockManager', function () { const iteration = await getIteration(stakeManager, random, staker); await blockManager.connect(signers[5]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [100, 201, 300, 400, 500, 600, 700, 800, 900], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); const proposedBlock = await blockManager.proposedBlocks(epoch, 0); @@ -197,9 +295,13 @@ describe('BlockManager', function () { const { biggestInfluencerId } = await getBiggestInfluenceAndId(stakeManager); const firstProposedBlock = await blockManager.proposedBlocks(epoch, 0); const iteration = await getIteration(stakeManager, random, staker); + + // Correcting Invalid Value + blockThisEpoch.medians[disputedAssetIdIndexInBlock]--; + await blockManager.connect(signers[6]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [100, 200, 300, 400, 500, 600, 700, 800, 900], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); @@ -221,10 +323,11 @@ describe('BlockManager', function () { await mineToNextState(); const epoch = await getEpoch(); - const sortedVotes = [toBigNumber('200')]; - const stakerIdAccount1 = await stakeManager.stakerIds(signers[5].address); - const stakerIdAccount2 = await stakeManager.stakerIds(signers[6].address); - const stakerIdAccount3 = await stakeManager.stakerIds(signers[8].address); + const sortedVotes = [toBigNumber(toBeDisputedAssetId * 100)]; + let weights = tokenAmount('0'); + for (let i = 0; i < weightsPerRevealedAssets[toBeDisputedAssetId].length; i++) { + weights = weights.add(weightsPerRevealedAssets[toBeDisputedAssetId][i]); + } const { median, totalInfluenceRevealed, @@ -232,16 +335,13 @@ describe('BlockManager', function () { voteManager, epoch, sortedVotes, - [await stakeManager.getInfluence(stakerIdAccount1), - await stakeManager.getInfluence(stakerIdAccount2), - await stakeManager.getInfluence(stakerIdAccount3)] // initial weights + [weights], // initial weights, + toBeDisputedAssetId - 1 ); - - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes); - + await blockManager.connect(signers[19]).giveSorted(epoch, toBeDisputedAssetId - 1, sortedVotes); const dispute = await blockManager.disputes(epoch, signers[19].address); - assertBNEqual(dispute.assetId, toBigNumber('1'), 'assetId should match'); + assertBNEqual(dispute.assetId, toBigNumber(toBeDisputedAssetId - 1), 'assetId should match'); assertBNEqual(dispute.accWeight, totalInfluenceRevealed, 'totalInfluenceRevealed should match'); assertBNEqual(dispute.median, median, 'median should match'); assertBNEqual(dispute.lastVisited, sortedVotes[sortedVotes.length - 1], 'lastVisited should match'); @@ -260,7 +360,7 @@ describe('BlockManager', function () { const balanceBeforeAcc19 = await razor.balanceOf(signers[19].address); const balanceBeforeBurn = await razor.balanceOf(parameters.burnAddress()); - await blockManager.connect(signers[19]).finalizeDispute(epoch, firstProposedBlockIndex); + await blockManager.connect(signers[19]).finalizeDispute(epoch, firstProposedBlockIndex, disputedAssetIdIndexInBlock); const proposedBlock = await blockManager.proposedBlocks(epoch, firstProposedBlockIndex); assert((await proposedBlock.valid) === false); @@ -310,21 +410,84 @@ describe('BlockManager', function () { await mineToNextState(); + // Staker 6 const proof = []; for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[6]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[6].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + await voteManager.connect(signers[6]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[6].address); + // Staker 7 const proof2 = []; for (let i = 0; i < votes2.length; i++) { proof2.push(tree2.getProofPath(i, true, true)); } - await voteManager.connect(signers[7]).reveal(epoch, tree2.root(), votes2, proof2, + const assignedAssets2 = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[7].address), votes2, proof2, maxAssetsPerStaker, random); + const assigneedAssetsVotes2 = assignedAssets2[0]; + const assigneedAssetsProofs2 = assignedAssets2[1]; + await voteManager.connect(signers[7]).reveal(epoch, tree2.root(), assigneedAssetsVotes2, assigneedAssetsProofs2, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[7].address); + + blockThisEpoch = { + ids: [], medians: [], + }; + weightsPerRevealedAssets = {}; + + // To Form Block Proposal On basis of revealed assets this epoch + const assetRevealedByStaker = {}; + const assetRevealedByStaker2 = {}; + + const stakerIdAccount1 = await stakeManager.stakerIds(signers[6].address); + const stakerIdAccount2 = await stakeManager.stakerIds(signers[7].address); + + const influence1 = await stakeManager.getInfluence(stakerIdAccount1); + const influence2 = await stakeManager.getInfluence(stakerIdAccount2); + + for (let i = 0; i < maxAssetsPerStaker; i++) { + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes[i].id] = []; + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] = []; + + // To have figure how much stake was revealed for that asset + + // Staker 6 + if (!assetRevealedByStaker[assigneedAssetsVotes[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes[i].id].push(influence1); + assetRevealedByStaker[assigneedAssetsVotes[i].id] = true; + } + + // Staker 7 + if (!assetRevealedByStaker2[assigneedAssetsVotes2[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes2[i].id].push(influence2); + assetRevealedByStaker2[assigneedAssetsVotes2[i].id] = true; + } + } + + // To find a asset id revealed by max no of stakers for better test coverage + let maxRevealsForAsset = 0; + for (const assetId of Object.keys(weightsPerRevealedAssets)) { + if (maxRevealsForAsset < weightsPerRevealedAssets[assetId].length) { + maxRevealsForAsset = weightsPerRevealedAssets[assetId].length; + toBeDisputedAssetId = assetId; + } + } + + // Forming block + // Purposefully proposing malicious value for assetTobeDisputed + for (let i = 1; i <= numAssets; i++) { + if (typeof weightsPerRevealedAssets[i] !== 'undefined') { + blockThisEpoch.ids.push(i); + if (i === parseInt(toBeDisputedAssetId, 10)) { + disputedAssetIdIndexInBlock = blockThisEpoch.medians.length; + blockThisEpoch.medians.push(i * 1000 + 1); + } else blockThisEpoch.medians.push(i * 1000); + } + } }); it('all blocks being disputed', async function () { @@ -344,20 +507,39 @@ describe('BlockManager', function () { await mineToNextState(); await blockManager.connect(signers[6]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [1000, 2100, 3100, 4000, 5000, 6000, 7000, 8000, 9000], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration6, biggestInfluencerId); await blockManager.connect(signers[7]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [1000, 2200, 3300, 4000, 5000, 6000, 7000, 8000, 9000], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration7, biggestInfluencerId); await mineToNextState(); - const sortedVotes1 = [toBigNumber('2000'), toBigNumber('2010')]; + let sortedVotes1; + let weights; + + const stakerIdAccount1 = await stakeManager.stakerIds(signers[6].address); + const stakerIdAccount2 = await stakeManager.stakerIds(signers[7].address); + + const influence1 = await stakeManager.getInfluence(stakerIdAccount1); + const influence2 = await stakeManager.getInfluence(stakerIdAccount2); + + if (weightsPerRevealedAssets[toBeDisputedAssetId].length === 2) { // If toBeDisputedAssetId is revealed by both stakers + sortedVotes1 = [toBigNumber(toBeDisputedAssetId * 1000), toBigNumber(toBeDisputedAssetId * 1000 + 10)]; + weights = [influence1, influence2]; + } else if (weightsPerRevealedAssets[toBeDisputedAssetId][0] === influence1) { + sortedVotes1 = [toBigNumber(toBeDisputedAssetId * 1000)]; + weights = [influence1]; + } else { + sortedVotes1 = [toBigNumber(toBeDisputedAssetId * 1000 + 10)]; + weights = [influence2]; + } + const { median: median1, totalInfluenceRevealed: totalInfluenceRevealed1, @@ -365,42 +547,43 @@ describe('BlockManager', function () { voteManager, epoch, sortedVotes1, - [await voteManager.getVoteWeight(epoch, 1, sortedVotes1[0]), - await voteManager.getVoteWeight(epoch, 1, sortedVotes1[1])] // initial weights + weights, // initial weights + toBeDisputedAssetId - 1 ); - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes1); + + await blockManager.connect(signers[19]).giveSorted(epoch, toBeDisputedAssetId - 1, sortedVotes1); + const firstDispute = await blockManager.disputes(epoch, signers[19].address); - assertBNEqual(firstDispute.assetId, toBigNumber('1'), 'assetId should match'); + assertBNEqual(firstDispute.assetId, toBigNumber(toBeDisputedAssetId - 1), 'assetId should match'); assertBNEqual(firstDispute.accWeight, totalInfluenceRevealed1, 'totalInfluenceRevealed should match'); assertBNEqual(firstDispute.median, median1, 'median should match'); assertBNEqual(firstDispute.lastVisited, sortedVotes1[sortedVotes1.length - 1], 'lastVisited should match'); - await blockManager.connect(signers[19]).finalizeDispute(epoch, 0); + await blockManager.connect(signers[19]).finalizeDispute(epoch, 0, disputedAssetIdIndexInBlock); let proposedBlock = await blockManager.proposedBlocks(epoch, 0); assert((await proposedBlock.valid) === false); - const sortedVotes2 = [toBigNumber('3000'), toBigNumber('3010')]; const { median: median2, totalInfluenceRevealed: totalInfluenceRevealed2, } = await calculateDisputesData( voteManager, epoch, - sortedVotes2, - [await voteManager.getVoteWeight(epoch, 2, sortedVotes2[0]), - await voteManager.getVoteWeight(epoch, 2, sortedVotes2[1])] // initial weights + sortedVotes1, + weights, // initial weights + toBeDisputedAssetId - 1 ); - await blockManager.connect(signers[15]).giveSorted(epoch, 2, sortedVotes2); + await blockManager.connect(signers[15]).giveSorted(epoch, toBeDisputedAssetId - 1, sortedVotes1); const secondDispute = await blockManager.disputes(epoch, signers[15].address); - assertBNEqual(secondDispute.assetId, toBigNumber('2'), 'assetId should match'); + assertBNEqual(secondDispute.assetId, toBigNumber(toBeDisputedAssetId - 1), 'assetId should match'); assertBNEqual(secondDispute.accWeight, totalInfluenceRevealed2, 'totalInfluenceRevealed should match'); assertBNEqual(secondDispute.median, median2, 'median should match'); - assertBNEqual(secondDispute.lastVisited, sortedVotes2[sortedVotes2.length - 1], 'lastVisited should match'); + assertBNEqual(secondDispute.lastVisited, sortedVotes1[sortedVotes1.length - 1], 'lastVisited should match'); - await blockManager.connect(signers[15]).finalizeDispute(epoch, 1); + await blockManager.connect(signers[15]).finalizeDispute(epoch, 1, disputedAssetIdIndexInBlock); proposedBlock = await blockManager.proposedBlocks(epoch, 1); assert((await proposedBlock.valid) === false); }); @@ -435,7 +618,10 @@ describe('BlockManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[8]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[8].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + await voteManager.connect(signers[8]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[8].address); @@ -466,7 +652,10 @@ describe('BlockManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[19]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[19].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + await voteManager.connect(signers[19]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[19].address); @@ -477,9 +666,30 @@ describe('BlockManager', function () { const { biggestInfluencerId } = await getBiggestInfluenceAndId(stakeManager); const iteration = await getIteration(stakeManager, random, staker); + + // To Form Block Proposal On basis of revealed assets this epoch + const revealedAssetsThisEpoch = {}; + for (let i = 0; i < maxAssetsPerStaker; i++) { + revealedAssetsThisEpoch[assigneedAssetsVotes[i].id] = true; + } + blockThisEpoch = { + ids: [], medians: [], + }; + // Purposefully Proposing invalid value for 1st asset of block + let firstAsset = true; + for (let i = 1; i <= numAssets; i++) { + if (revealedAssetsThisEpoch[i]) { + blockThisEpoch.ids.push(i); + firstAsset && (toBeDisputedAssetId = i); + // Purposefully Proposing invalid value for 1st asset of block + firstAsset ? blockThisEpoch.medians.push(i * 1000 + 1) : blockThisEpoch.medians.push(i * 1000); + firstAsset = false; + } + } + await blockManager.connect(signers[19]).propose(epoch, - [10, 12, 13, 14, 15, 16, 17, 18, 19], - [1000, 2001, 3000, 4000, 5000, 6000, 7000, 8000, 9000], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); const proposedBlock = await blockManager.proposedBlocks(epoch, 0); @@ -487,12 +697,13 @@ describe('BlockManager', function () { await mineToNextState(); - const sortedVotes = [toBigNumber('20000')]; + // By Mistake Raising Wrong Dispute + const sortedVotes = [toBigNumber(toBeDisputedAssetId * 10000)]; - await blockManager.connect(signers[15]).giveSorted(epoch, 1, sortedVotes); + await blockManager.connect(signers[15]).giveSorted(epoch, toBeDisputedAssetId - 1, sortedVotes); const beforeDisputeReset = await blockManager.disputes(epoch, signers[15].address); - assertBNEqual(beforeDisputeReset.assetId, toBigNumber('1'), 'assetId should match'); + assertBNEqual(beforeDisputeReset.assetId, toBigNumber(toBeDisputedAssetId - 1), 'assetId should match'); await blockManager.connect(signers[15]).resetDispute(epoch); const afterDisputeReset = await blockManager.disputes(epoch, signers[15].address); @@ -526,9 +737,8 @@ describe('BlockManager', function () { await voteManager.connect(signers[2]).commit(epoch, commitment1); - const votes2 = [100, 200, 300, 400, 500, 600, 700, 800, 900]; + const votes2 = [105, 205, 305, 405, 505, 605, 705, 805, 905]; const tree2 = merkle('keccak256').sync(votes2); - const root2 = tree2.root(); const commitment2 = utils.solidityKeccak256( ['uint256', 'uint256', 'bytes32'], @@ -543,19 +753,78 @@ describe('BlockManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[2]).reveal(epoch, tree.root(), votes, proof, + // Staker 2 + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[2].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[2]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[2].address); + // Staker 3 const proof2 = []; for (let i = 0; i < votes2.length; i++) { proof2.push(tree2.getProofPath(i, true, true)); } - await voteManager.connect(signers[3]).reveal(epoch, tree2.root(), votes2, proof2, + const assignedAssets2 = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[3].address), votes2, proof2, maxAssetsPerStaker, random); + const assigneedAssetsVotes2 = assignedAssets2[0]; + const assigneedAssetsProofs2 = assignedAssets2[1]; + await voteManager.connect(signers[3]).reveal(epoch, tree2.root(), assigneedAssetsVotes2, assigneedAssetsProofs2, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); - // Propose + blockThisEpoch = { + ids: [], medians: [], + }; + weightsPerRevealedAssets = {}; + + // To Form Block Proposal On basis of revealed assets this epoch + const assetRevealedByStaker = {}; + const assetRevealedByStaker2 = {}; + const stakerIdAccount1 = await stakeManager.stakerIds(signers[2].address); + const stakerIdAccount2 = await stakeManager.stakerIds(signers[3].address); + + const influence1 = await stakeManager.getInfluence(stakerIdAccount1); + const influence2 = await stakeManager.getInfluence(stakerIdAccount2); + for (let i = 0; i < maxAssetsPerStaker; i++) { + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes[i].id] = []; + if (typeof weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] === 'undefined') weightsPerRevealedAssets[assigneedAssetsVotes2[i].id] = []; + + // To have figure how much stake was revealed for that asset + + // Staker 2 + if (!assetRevealedByStaker[assigneedAssetsVotes[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes[i].id].push(influence1); + assetRevealedByStaker[assigneedAssetsVotes[i].id] = true; + } + + // Staker 3 + if (!assetRevealedByStaker2[assigneedAssetsVotes2[i].id]) { + weightsPerRevealedAssets[assigneedAssetsVotes2[i].id].push(influence2); + assetRevealedByStaker2[assigneedAssetsVotes2[i].id] = true; + } + } + + // To find a asset id revealed by max no of stakers for better test coverage + let maxRevealsForAsset = 0; + for (const assetId of Object.keys(weightsPerRevealedAssets)) { + if (maxRevealsForAsset < weightsPerRevealedAssets[assetId].length) { + maxRevealsForAsset = weightsPerRevealedAssets[assetId].length; + toBeDisputedAssetId = assetId; + } + } + // Forming block + // Purposefully proposing malicious value for assetTobeDisputed + for (let i = 1; i <= numAssets; i++) { + if (typeof weightsPerRevealedAssets[i] !== 'undefined') { + blockThisEpoch.ids.push(i); + if (i === parseInt(toBeDisputedAssetId, 10)) { + disputedAssetIdIndexInBlock = blockThisEpoch.medians.length; + blockThisEpoch.medians.push(i * 1000 + 1); + } else blockThisEpoch.medians.push(i * 100); + } + } await mineToNextState(); const stakerIdAcc2 = await stakeManager.stakerIds(signers[2].address); const staker = await stakeManager.getStaker(stakerIdAcc2); @@ -564,8 +833,8 @@ describe('BlockManager', function () { const iteration = await getIteration(stakeManager, random, staker); await blockManager.connect(signers[2]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [100, 201, 300, 400, 500, 600, 700, 800, 900], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); const proposedBlock = await blockManager.proposedBlocks(epoch, 0); @@ -574,25 +843,36 @@ describe('BlockManager', function () { // Calculate Dispute data await mineToNextState(); epoch = await getEpoch(); - const sortedVotes = [toBigNumber('200')]; + let sortedVotes; + let weights; + if (weightsPerRevealedAssets[toBeDisputedAssetId].length === 2) { // If toBeDisputedAssetId is revealed by both stakers + sortedVotes = [toBigNumber(votes[toBeDisputedAssetId - 1]), toBigNumber(votes2[toBeDisputedAssetId - 1])]; + weights = [influence1, influence2]; + } else if (weightsPerRevealedAssets[toBeDisputedAssetId][0] === influence1) { + sortedVotes = [toBigNumber(votes[toBeDisputedAssetId - 1])]; + weights = [influence1]; + } else { + sortedVotes = [toBigNumber(votes2[toBeDisputedAssetId - 1])]; + weights = [influence2]; + } const { median, totalInfluenceRevealed, } = await calculateDisputesData( voteManager, epoch, sortedVotes, - [await voteManager.getVoteWeight(epoch, 1, sortedVotes[0])] // initial weights + weights, // initial weights + toBeDisputedAssetId - 1 ); - // Dispute in batches - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes.slice(0, 51)); - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes.slice(51, 101)); - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes.slice(101, 151)); - await blockManager.connect(signers[19]).giveSorted(epoch, 1, sortedVotes.slice(151, 201)); + if (sortedVotes.length === 2) { + await blockManager.connect(signers[19]).giveSorted(epoch, toBeDisputedAssetId - 1, [sortedVotes[0]]); + await blockManager.connect(signers[19]).giveSorted(epoch, toBeDisputedAssetId - 1, [sortedVotes[1]]); + } else { await blockManager.connect(signers[19]).giveSorted(epoch, toBeDisputedAssetId - 1, [sortedVotes[0]]); } const dispute = await blockManager.disputes(epoch, signers[19].address); - assertBNEqual(dispute.assetId, toBigNumber('1'), 'assetId should match'); + assertBNEqual(dispute.assetId, toBigNumber(toBeDisputedAssetId - 1), 'assetId should match'); assertBNEqual(dispute.accWeight, totalInfluenceRevealed, 'totalInfluenceRevealed should match'); assertBNEqual(dispute.median, median, 'median should match'); assertBNEqual(dispute.lastVisited, sortedVotes[sortedVotes.length - 1], 'lastVisited should match'); diff --git a/test/StakeManager.js b/test/StakeManager.js index 3a70fbe8..7b9511c7 100644 --- a/test/StakeManager.js +++ b/test/StakeManager.js @@ -20,6 +20,7 @@ const { tokenAmount, getBiggestInfluenceAndId, getIteration, + getAssignedAssets, maturity, } = require('./helpers/utils'); const { setupContracts } = require('./helpers/testSetup'); @@ -33,9 +34,16 @@ describe('StakeManager', function () { let stakeManager; let rewardManager; let voteManager; + let assetManager; let initializeContracts; let stakedToken; let random; + let maxAssetsPerStaker; + let numAssets; + let revealedAssetsThisEpoch = {}; + let blockThisEpoch = { + ids: [], medians: [], + }; before(async () => { ({ @@ -45,6 +53,7 @@ describe('StakeManager', function () { rewardManager, parameters, voteManager, + assetManager, initializeContracts, stakedToken, random, @@ -75,6 +84,19 @@ describe('StakeManager', function () { it('should be able to initialize', async function () { await Promise.all(await initializeContracts()); + // Before Staker could commit even if there were no jobs, now as we are moving to assgined jobs, we need to create them first, and then only commit + await assetManager.grantRole(await parameters.getAssetModifierHash(), signers[0].address); + const url = 'http://testurl.com'; + const selector = 'selector'; + const name = 'test'; + const repeat = true; + let i = 0; + while (i < 9) { await assetManager.createJob(url, selector, name, repeat); i++; } + // By default its 2 setting it 5 + await parameters.setmaxAssetsPerStaker(5); + maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + numAssets = Number(await assetManager.getNumAssets()); + await mineToNextEpoch(); const stake1 = tokenAmount('443000'); await razor.transfer(signers[1].address, stake1); @@ -257,7 +279,12 @@ describe('StakeManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[2]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[2].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[2]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[2].address); @@ -298,7 +325,12 @@ describe('StakeManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[3]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[3].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[3]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); // Staker 3 is penalised because no of inactive epochs (9) > max allowed inactive epochs i.e grace_period (8) @@ -335,7 +367,12 @@ describe('StakeManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[3]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[3].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[3]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); // Staker is not penalised because no. of inactive epochs (8) <= max allowed inactive epochs i.e grace_period (8) @@ -517,18 +554,38 @@ describe('StakeManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[4]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[4].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[4]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[4].address); // propose + // To Form Block Proposal On basis of revealed assets this epoch + revealedAssetsThisEpoch = {}; + for (let i = 0; i < maxAssetsPerStaker; i++) { + revealedAssetsThisEpoch[assigneedAssetsVotes[i].id] = true; + } + blockThisEpoch = { + ids: [], medians: [], + }; + for (let i = 1; i <= numAssets; i++) { + if (revealedAssetsThisEpoch[i]) { + blockThisEpoch.ids.push(i); + blockThisEpoch.medians.push(i * 100); + } + } + await mineToNextState(); const { biggestInfluencerId } = await getBiggestInfluenceAndId(stakeManager); const iteration = await getIteration(stakeManager, random, staker); await blockManager.connect(signers[4]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [100, 200, 300, 400, 500, 600, 700, 800, 900], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); const proposedBlock = await blockManager.proposedBlocks(epoch, 0); @@ -617,7 +674,12 @@ describe('StakeManager', function () { for (let i = 0; i < votes.length; i++) { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[4]).reveal(epoch, tree.root(), votes, proof, + + const assignedAssets = await getAssignedAssets(numAssets, await stakeManager.stakerIds(signers[4].address), votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[4]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[4].address); diff --git a/test/VoteManager.js b/test/VoteManager.js index 72633774..79ad0493 100644 --- a/test/VoteManager.js +++ b/test/VoteManager.js @@ -19,6 +19,8 @@ const { getBiggestInfluenceAndId, toBigNumber, tokenAmount, + getAssignedAssets, + findAssetNotAlloted, } = require('./helpers/utils'); describe('VoteManager', function () { @@ -31,11 +33,16 @@ describe('VoteManager', function () { let stakeManager; let rewardManager; let voteManager; + let assetManager; let initializeContracts; - + let revealedAssetsThisEpoch = {}; + let blockThisEpoch = { + ids: [], medians: [], + }; + let revealedVotesForStaker = []; before(async () => { ({ - blockManager, parameters, random, razor, stakeManager, rewardManager, voteManager, initializeContracts, + blockManager, parameters, random, razor, stakeManager, rewardManager, voteManager, assetManager, initializeContracts, } = await setupContracts()); signers = await ethers.getSigners(); }); @@ -62,7 +69,8 @@ describe('VoteManager', function () { }); it('should not be able to initiliaze VoteManager contract without admin role', async () => { - const tx = voteManager.connect(signers[1]).initialize(stakeManager.address, rewardManager.address, blockManager.address, parameters.address); + const tx = voteManager.connect(signers[1]).initialize(stakeManager.address, rewardManager.address, blockManager.address, + parameters.address, assetManager.address); await assertRevert(tx, 'AccessControl'); }); @@ -81,6 +89,17 @@ describe('VoteManager', function () { const epoch = await getEpoch(); await stakeManager.connect(signers[3]).stake(epoch, tokenAmount('420000')); await stakeManager.connect(signers[4]).stake(epoch, tokenAmount('19000')); + + // Before Staker could commit even if there were no jobs, now as we are moving to assgined jobs, we need to create them first, and then only commit + await assetManager.grantRole(await parameters.getAssetModifierHash(), signers[0].address); + const url = 'http://testurl.com'; + const selector = 'selector'; + const name = 'test'; + const repeat = true; + let i = 0; + while (i < 9) { await assetManager.createJob(url, selector, name, repeat); i++; } + // By default its 2 setting it 5 + await parameters.setmaxAssetsPerStaker(5); }); it('should be able to commit', async function () { @@ -110,11 +129,13 @@ describe('VoteManager', function () { await voteManager.connect(signers[4]).commit(epoch, commitment3); }); - it('should be able to reveal', async function () { + it('should be able to reveal assigned assets', async function () { const epoch = await getEpoch(); const stakerIdAcc3 = await stakeManager.stakerIds(signers[3].address); - + const stakerIdAcc4 = await stakeManager.stakerIds(signers[4].address); const stakeBefore = (await stakeManager.stakers(stakerIdAcc3)).stake; + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); const votes = [100, 200, 300, 400, 500, 600, 700, 800, 900]; const tree = merkle('keccak256').sync(votes); @@ -126,11 +147,34 @@ describe('VoteManager', function () { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[3]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc3, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + // Revealed assets not equal to required assets per staker + const assignedAssetsVotesCopy = JSON.parse(JSON.stringify(assigneedAssetsVotes)); // Deep Clone + assignedAssetsVotesCopy.push({ id: 5, value: 500 }); + let tx = voteManager.connect(signers[3]).reveal(epoch, tree.root(), assignedAssetsVotesCopy, proof, + '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', + signers[3].address); + await assertRevert(tx, 'Revealed assets not equal to required assets per staker'); + + // Incorrect Reveal + assignedAssetsVotesCopy.splice(0, 1); + const assetId = await findAssetNotAlloted(assignedAssetsVotesCopy, votes.length); + assignedAssetsVotesCopy[0].id = assetId; + tx = voteManager.connect(signers[3]).reveal(epoch, tree.root(), assignedAssetsVotesCopy, proof, + '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', + signers[3].address); + await assertRevert(tx, 'Revealed asset not alloted'); + + // Correct Reveal + await voteManager.connect(signers[3]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); // arguments getvVote => epoch, stakerId, assetId - assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, 0)).value, toBigNumber('100'), 'Vote not equal to 100'); + assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, assigneedAssetsVotes[0].id - 1)).value, toBigNumber(assigneedAssetsVotes[0].value), + 'Vote Stored not equal to submitted one'); const votes2 = [104, 204, 304, 404, 504, 604, 704, 804, 904]; const tree2 = merkle('keccak256').sync(votes2); @@ -139,13 +183,27 @@ describe('VoteManager', function () { for (let i = 0; i < votes2.length; i++) { proof2.push(tree2.getProofPath(i, true, true)); } - - await voteManager.connect(signers[4]).reveal(epoch, root2, votes2, proof2, + const assignedAssets2 = await getAssignedAssets(numAssets, stakerIdAcc4, votes2, proof2, maxAssetsPerStaker, random); + const assigneedAssetsVotes2 = assignedAssets2[0]; + const assigneedAssetsProofs2 = assignedAssets2[1]; + await voteManager.connect(signers[4]).reveal(epoch, root2, assigneedAssetsVotes2, assigneedAssetsProofs2, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[4].address); - const stakeAfter = (await stakeManager.stakers(stakerIdAcc3)).stake; assertBNEqual(stakeBefore, stakeAfter); + + // To Form Block Proposal On basis of revealed assets this epoch + for (let i = 0; i < maxAssetsPerStaker; i++) { + revealedAssetsThisEpoch[assigneedAssetsVotes[i].id] = true; + revealedAssetsThisEpoch[assigneedAssetsVotes2[i].id] = true; + } + + for (let i = 1; i <= numAssets; i++) { + if (revealedAssetsThisEpoch[i]) { + blockThisEpoch.ids.push(i); + blockThisEpoch.medians.push(i * 100); + } + } }); it('should be able to commit again with correct influence', async function () { @@ -158,8 +216,8 @@ describe('VoteManager', function () { const iteration = await getIteration(stakeManager, random, staker); await mineToNextState(); // propose await blockManager.connect(signers[3]).propose(epoch, - [1, 2, 3, 4, 5, 6, 7, 8, 9], - [100, 200, 300, 400, 500, 600, 700, 800, 900], + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); @@ -205,6 +263,8 @@ describe('VoteManager', function () { const epoch = await getEpoch(); const stakerIdAcc3 = await stakeManager.stakerIds(signers[3].address); const stakerIdAcc4 = await stakeManager.stakerIds(signers[4].address); + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); const stakeBefore = (await stakeManager.stakers(stakerIdAcc3)).stake; const stakeBefore2 = (await stakeManager.stakers(stakerIdAcc4)).stake; @@ -227,13 +287,23 @@ describe('VoteManager', function () { await mineToNextState(); // reveal - await voteManager.connect(signers[3]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc3, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[3]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); // arguments getvVote => epoch, stakerId, assetId - assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, 0)).value, toBigNumber('100'), 'Vote not equal to 100'); + assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, assigneedAssetsVotes[0].id - 1)).value, toBigNumber(assigneedAssetsVotes[0].value), + 'Vote Stored not equal to submitted one'); - await voteManager.connect(signers[4]).reveal(epoch, tree2.root(), votes2, proof2, + const assignedAssets2 = await getAssignedAssets(numAssets, stakerIdAcc4, votes2, proof2, maxAssetsPerStaker, random); + const assigneedAssetsVotes2 = assignedAssets2[0]; + revealedVotesForStaker = assigneedAssetsVotes2; + const assigneedAssetsProofs2 = assignedAssets2[1]; + + await voteManager.connect(signers[4]).reveal(epoch, tree2.root(), assigneedAssetsVotes2, assigneedAssetsProofs2, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[4].address); @@ -241,6 +311,22 @@ describe('VoteManager', function () { const stakeAfter2 = (await stakeManager.stakers(stakerIdAcc4)).stake; assertBNEqual(stakeBefore, stakeAfter); assertBNEqual(stakeBefore2, stakeAfter2); + + // To Form Block Proposal On basis of revealed assets this epoch + revealedAssetsThisEpoch = {}; + for (let i = 0; i < maxAssetsPerStaker; i++) { + revealedAssetsThisEpoch[assigneedAssetsVotes[i].id] = true; + revealedAssetsThisEpoch[assigneedAssetsVotes2[i].id] = true; + } + blockThisEpoch = { + ids: [], medians: [], + }; + for (let i = 1; i <= numAssets; i++) { + if (revealedAssetsThisEpoch[i]) { + blockThisEpoch.ids.push(i); + blockThisEpoch.medians.push(i * 100); + } + } }); it('account 4 should be penalised for trying to make fraudulent predictions in the previous epoch', async function () { @@ -253,10 +339,9 @@ describe('VoteManager', function () { const iteration = await getIteration(stakeManager, random, staker); await mineToNextState(); // propose - const medians = [100, 200, 300, 400, 500, 600, 700, 800, 900]; await blockManager.connect(signers[3]).propose(epoch, - [10, 11, 12, 13, 14, 15, 16, 17, 18], - medians, + blockThisEpoch.ids, + blockThisEpoch.medians, iteration, biggestInfluencerId); @@ -288,17 +373,19 @@ describe('VoteManager', function () { let toAdd = toBigNumber(0); let num = toBigNumber(0); let denom = toBigNumber(0); - const votes2 = [104, 204, 304, 404, 504, 604, 704, 804, 904]; - for (let i = 0; i < votes2.length; i++) { - num = (toBigNumber(votes2[i]).sub(medians[i])).pow(2); - denom = toBigNumber(medians[i]).pow(2); - toAdd = (ageBefore2.mul(num).div(denom)); - penalty = penalty.add(toAdd); + const map = {}; + for (let i = 0; i < revealedVotesForStaker.length; i++) { + if (typeof map[revealedVotesForStaker[i].id] === 'undefined') { + num = (toBigNumber(revealedVotesForStaker[i].value).sub(revealedVotesForStaker[i].value - revealedVotesForStaker[i].value % 100)).pow(2); + denom = toBigNumber(revealedVotesForStaker[i].value - revealedVotesForStaker[i].value % 100).pow(2); + toAdd = (ageBefore2.mul(num).div(denom)); + penalty = penalty.add(toAdd); + map[revealedVotesForStaker[i].id] = true; + } } const expectedAgeAfter2 = ageBefore2.add(10000).sub(penalty); const ageAfter = (await stakeManager.stakers(stakerIdAcc3)).age; const ageAfter2 = (await stakeManager.stakers(stakerIdAcc4)).age; - assertBNLessThan(ageBefore, ageAfter, 'Not rewarded'); assertBNEqual(expectedAgeAfter2, ageAfter2, 'Age Penalty should be applied'); }); @@ -308,6 +395,9 @@ describe('VoteManager', function () { const stakerIdAcc4 = await stakeManager.stakerIds(signers[4].address); const stakeBeforeAcc4 = (await stakeManager.stakers(stakerIdAcc4)).stake; + const stakerIdAcc10 = await stakeManager.stakerIds(signers[10].address); + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); const votes = [100, 200, 300, 400, 500, 600, 700, 800, 900]; const tree = merkle('keccak256').sync(votes); @@ -316,7 +406,10 @@ describe('VoteManager', function () { proof.push(tree.getProofPath(i, true, true)); } - await voteManager.connect(signers[10]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc10, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + await voteManager.connect(signers[10]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[4].address); @@ -329,8 +422,8 @@ describe('VoteManager', function () { it('Account 3 should be able to reveal again', async function () { const epoch = await getEpoch(); const stakerIdAcc3 = await stakeManager.stakerIds(signers[3].address); - - // const ageBefore = (await stakeManager.stakers(stakerIdAcc3)).age; + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); const votes = [100, 200, 300, 400, 500, 600, 700, 800, 900]; const tree = merkle('keccak256').sync(votes); @@ -341,19 +434,23 @@ describe('VoteManager', function () { } await mineToNextState(); // reveal - await voteManager.connect(signers[3]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc3, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[3]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[3].address); // arguments getvVote => epoch, stakerId, assetId - assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, 0)).value, toBigNumber('100'), 'Vote not equal to 100'); - - // const ageAfter = (await stakeManager.stakers(stakerIdAcc3)).age; - // assertBNEqual(ageBefore.add(10000), ageAfter); + assertBNEqual((await voteManager.getVote(epoch, stakerIdAcc3, assigneedAssetsVotes[0].id - 1)).value, toBigNumber(assigneedAssetsVotes[0].value), + 'Vote Stored not equal to submitted one'); }); it('Should be able to slash if stake is zero', async function () { await mineToNextEpoch(); const epoch = await getEpoch(); + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); await parameters.setMinStake(0); await stakeManager.connect(signers[6]).stake(epoch, tokenAmount('0')); @@ -378,7 +475,11 @@ describe('VoteManager', function () { } const balanceBeforeAcc10 = await razor.balanceOf(signers[10].address); - await voteManager.connect(signers[10]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc6, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[10]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[6].address); @@ -392,6 +493,9 @@ describe('VoteManager', function () { it('Should be able to slash if stake is one', async function () { const epoch = await getEpoch(); + const maxAssetsPerStaker = Number(await parameters.maxAssetsPerStaker()); + const numAssets = Number(await assetManager.getNumAssets()); + await stakeManager.connect(signers[5]).stake(epoch, tokenAmount('1')); const votes2 = [100, 200, 300, 400, 500, 600, 700, 800, 900]; @@ -414,7 +518,11 @@ describe('VoteManager', function () { } const balanceBeforeAcc10 = await razor.balanceOf(signers[10].address); - await voteManager.connect(signers[10]).reveal(epoch, tree.root(), votes, proof, + const assignedAssets = await getAssignedAssets(numAssets, stakerIdAcc5, votes, proof, maxAssetsPerStaker, random); + const assigneedAssetsVotes = assignedAssets[0]; + const assigneedAssetsProofs = assignedAssets[1]; + + await voteManager.connect(signers[10]).reveal(epoch, tree.root(), assigneedAssetsVotes, assigneedAssetsProofs, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd', signers[5].address); diff --git a/test/helpers/testSetup.js b/test/helpers/testSetup.js index a806008f..1aa4d910 100644 --- a/test/helpers/testSetup.js +++ b/test/helpers/testSetup.js @@ -24,7 +24,12 @@ const setupContracts = async () => { const RAZOR = await ethers.getContractFactory('RAZOR'); const StakeManager = await ethers.getContractFactory('StakeManager'); const RewardManager = await ethers.getContractFactory('RewardManager'); - const VoteManager = await ethers.getContractFactory('VoteManager'); + + const VoteManager = await ethers.getContractFactory('VoteManager', { + libraries: { + Random: random.address, + }, + }); const parameters = await Parameters.deploy(); const blockManager = await BlockManager.deploy(); @@ -49,7 +54,7 @@ const setupContracts = async () => { const initializeContracts = async () => [ blockManager.initialize(stakeManager.address, rewardManager.address, voteManager.address, assetManager.address, parameters.address), - voteManager.initialize(stakeManager.address, rewardManager.address, blockManager.address, parameters.address), + voteManager.initialize(stakeManager.address, rewardManager.address, blockManager.address, parameters.address, assetManager.address), stakeManager.initialize(razor.address, rewardManager.address, voteManager.address, parameters.address), rewardManager.initialize(stakeManager.address, voteManager.address, blockManager.address, parameters.address), diff --git a/test/helpers/utils.js b/test/helpers/utils.js index 350230dc..bf773277 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -6,15 +6,15 @@ const { const toBigNumber = (value) => BigNumber.from(value); const tokenAmount = (value) => toBigNumber(value).mul(ONE_ETHER); -const calculateDisputesData = async (voteManager, epoch, sortedVotes, weights) => { +const calculateDisputesData = async (voteManager, epoch, sortedVotes, weights, assetId) => { // See issue https://github.com/ethers-io/ethers.js/issues/407#issuecomment-458360013 // We should rethink about overloading functions. - const totalInfluenceRevealed = await voteManager['getTotalInfluenceRevealed(uint256,uint256)'](epoch, 1); + const totalInfluenceRevealed = await voteManager['getTotalInfluenceRevealed(uint256,uint256)'](epoch, assetId); const medianWeight = totalInfluenceRevealed.div(2); + let median = toBigNumber('0'); let weight = toBigNumber('0'); - for (let i = 0; i < sortedVotes.length; i++) { weight = weight.add(weights[i]); if (weight.gt(medianWeight) && median.eq('0')) median = sortedVotes[i]; @@ -97,6 +97,45 @@ const getState = async () => { return state.mod(NUM_STATES).toNumber(); }; +const getAssignedAssets = async (numAssets, stakerId, votes, proofs, maxAssetsPerStaker, random) => { + const assignedAssetsVotes = []; + const assignedAssetsProofs = []; + + const blockHashes = await random.blockHashes(NUM_BLOCKS, EPOCH_LENGTH); + let assetId; + let seed; + for (let i = 0; i < maxAssetsPerStaker; i++) { + seed = await web3.utils.soliditySha3(+stakerId + i); + assetId = +(await prng(seed, numAssets, blockHashes)) + 1; + assignedAssetsVotes.push({ id: assetId, value: votes[assetId - 1] }); + assignedAssetsProofs.push(proofs[assetId - 1]); + } + return [assignedAssetsVotes, assignedAssetsProofs]; +}; + +const getNumRevealedAssets = async (assignedAssetsVotes) => { + const isExist = {}; + let numRevealedAssetsForStaker = 0; + for (let i = 0; i < assignedAssetsVotes.length; i++) { + if (typeof isExist[assignedAssetsVotes[i].id] === 'undefined') { + isExist[assignedAssetsVotes[i].id] = true; + numRevealedAssetsForStaker++; + } + } + return numRevealedAssetsForStaker; +}; + +const findAssetNotAlloted = async (assignedAssetsVotes, numAssets) => { + const map = {}; + for (let i = 0; i < assignedAssetsVotes.length; i++) { + map[assignedAssetsVotes[i].id] = true; + } + for (let i = 1; i <= numAssets; i++) { + if (!map[i]) return i; + } + return 1000; +}; + module.exports = { calculateDisputesData, isElectedProposer, @@ -109,5 +148,8 @@ module.exports = { prngHash, toBigNumber, tokenAmount, + getAssignedAssets, + getNumRevealedAssets, + findAssetNotAlloted, maturity, };