From 924b3063a0dac75f353556933f4293010005794d Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Thu, 26 Aug 2021 16:56:45 +0700 Subject: [PATCH 1/7] new: apply cooldown on bidding --- contracts/staking/stakeManager/StakeManager.sol | 4 ++++ contracts/staking/stakeManager/StakeManagerExtension.sol | 5 +++++ contracts/staking/stakeManager/StakeManagerStorage.sol | 1 + .../staking/stakeManager/StakeManagerStorageExtension.sol | 2 ++ 4 files changed, 12 insertions(+) diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index dd4382f2f..b70c9bf58 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -190,6 +190,10 @@ contract StakeManager is Governance Methods */ + function setBidCooldown(uint256 cooldown) public onlyGovernance { + bidCooldown = cooldown; + } + function setDelegationEnabled(bool enabled) public onlyGovernance { delegationEnabled = enabled; } diff --git a/contracts/staking/stakeManager/StakeManagerExtension.sol b/contracts/staking/stakeManager/StakeManagerExtension.sol index c74dbcca4..0df46a5e8 100644 --- a/contracts/staking/stakeManager/StakeManagerExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerExtension.sol @@ -57,6 +57,10 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag perceivedStake = perceivedStake.add(validators[validatorId].delegatedAmount); Auction storage auction = validatorAuction[validatorId]; + + // do not allow bidding too often + require(auction.lastBidTimestamp == 0 || auction.lastBidTimestamp < block.timestamp + bidCooldown, "bid too often"); + uint256 currentAuctionAmount = auction.amount; perceivedStake = Math.max(perceivedStake, currentAuctionAmount); @@ -74,6 +78,7 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag auction.user = msg.sender; auction.acceptDelegation = _acceptDelegation; auction.signerPubkey = _signerPubkey; + auction.lastBidTimestamp = block.timestamp; logger.logStartAuction(validatorId, currentValidatorAmount, amount); } diff --git a/contracts/staking/stakeManager/StakeManagerStorage.sol b/contracts/staking/stakeManager/StakeManagerStorage.sol index 6b448a838..854abe5dd 100644 --- a/contracts/staking/stakeManager/StakeManagerStorage.sol +++ b/contracts/staking/stakeManager/StakeManagerStorage.sol @@ -18,6 +18,7 @@ contract StakeManagerStorage is GovernanceLockable, RootChainable { address user; bool acceptDelegation; bytes signerPubkey; + uint256 lastBidTimestamp; } struct State { diff --git a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol index dca5cd233..bb13ae32e 100644 --- a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol @@ -14,4 +14,6 @@ contract StakeManagerStorageExtension { uint256 public maxRewardedCheckpoints; // increase / decrease value for faster or slower checkpoints, 0 - 100% uint256 public checkpointRewardDelta; + // do not prevent bidding for some time to incentivize early bidding + uint256 public bidCooldown; } From a85def74a7f5214011b955f8373ff21fcbcfa98c Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Thu, 26 Aug 2021 18:13:41 +0700 Subject: [PATCH 2/7] new: set a minimum bid amount to % of total stake --- contracts/staking/stakeManager/StakeManager.sol | 4 ++++ contracts/staking/stakeManager/StakeManagerExtension.sol | 9 ++++++--- contracts/staking/stakeManager/StakeManagerStorage.sol | 2 +- .../stakeManager/StakeManagerStorageExtension.sol | 7 ++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index b70c9bf58..7edcf973f 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -190,6 +190,10 @@ contract StakeManager is Governance Methods */ + function setMinBidStakeFraction(uint256 fraction) public onlyGovernance { + minBidStakeFraction = fraction; + } + function setBidCooldown(uint256 cooldown) public onlyGovernance { bidCooldown = cooldown; } diff --git a/contracts/staking/stakeManager/StakeManagerExtension.sol b/contracts/staking/stakeManager/StakeManagerExtension.sol index 0df46a5e8..dbbc8caba 100644 --- a/contracts/staking/stakeManager/StakeManagerExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerExtension.sol @@ -59,11 +59,14 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag Auction storage auction = validatorAuction[validatorId]; // do not allow bidding too often - require(auction.lastBidTimestamp == 0 || auction.lastBidTimestamp < block.timestamp + bidCooldown, "bid too often"); + require(lastBidTimestamp[msg.sender] == 0 || lastBidTimestamp[msg.sender] < block.timestamp, "bid too often"); uint256 currentAuctionAmount = auction.amount; - perceivedStake = Math.max(perceivedStake, currentAuctionAmount); + perceivedStake = Math.max( + validatorState.amount.mul(minBidStakeFraction).div(MIN_BID_PRECISION), + Math.max(perceivedStake, currentAuctionAmount) + ); require(perceivedStake < amount, "Must bid higher"); require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed"); @@ -78,7 +81,7 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag auction.user = msg.sender; auction.acceptDelegation = _acceptDelegation; auction.signerPubkey = _signerPubkey; - auction.lastBidTimestamp = block.timestamp; + lastBidTimestamp[msg.sender] = block.timestamp + bidCooldown; logger.logStartAuction(validatorId, currentValidatorAmount, amount); } diff --git a/contracts/staking/stakeManager/StakeManagerStorage.sol b/contracts/staking/stakeManager/StakeManagerStorage.sol index 854abe5dd..1cec59363 100644 --- a/contracts/staking/stakeManager/StakeManagerStorage.sol +++ b/contracts/staking/stakeManager/StakeManagerStorage.sol @@ -18,7 +18,6 @@ contract StakeManagerStorage is GovernanceLockable, RootChainable { address user; bool acceptDelegation; bytes signerPubkey; - uint256 lastBidTimestamp; } struct State { @@ -52,6 +51,7 @@ contract StakeManagerStorage is GovernanceLockable, RootChainable { uint256 constant REWARD_PRECISION = 10**25; uint256 internal constant INCORRECT_VALIDATOR_ID = 2**256 - 1; uint256 internal constant INITIALIZED_AMOUNT = 1; + uint256 constant CHK_REWARD_PRECISION = 100; IERC20 public token; address public registry; diff --git a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol index bb13ae32e..c615f7756 100644 --- a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol @@ -1,12 +1,13 @@ pragma solidity 0.5.17; contract StakeManagerStorageExtension { + uint256 public constant MIN_BID_PRECISION = 10000; // down to 0.0001% fractions + address public eventsHub; uint256 public rewardPerStake; address public extensionCode; address[] public signers; - uint256 constant CHK_REWARD_PRECISION = 100; uint256 public prevBlockInterval; // how much less reward per skipped checkpoint, 0 - 100% uint256 public rewardDecreasePerCheckpoint; @@ -16,4 +17,8 @@ contract StakeManagerStorageExtension { uint256 public checkpointRewardDelta; // do not prevent bidding for some time to incentivize early bidding uint256 public bidCooldown; + // fraction of the total stake acting as a minimum for auction bidding, + uint256 public minBidStakeFraction; + // tracks when auction bid happened for the user + mapping(address => uint256) public lastBidTimestamp; } From 2fc9ce26a5b9712e84a9df1cd00f47246a288fac Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Thu, 26 Aug 2021 18:13:53 +0700 Subject: [PATCH 3/7] new: tests WIP --- test/helpers/deployer.js | 14 + .../staking/stakeManager/StakeManager.test.js | 321 ++++++++++-------- 2 files changed, 202 insertions(+), 133 deletions(-) diff --git a/test/helpers/deployer.js b/test/helpers/deployer.js index a3bab0702..8b8c1a02f 100644 --- a/test/helpers/deployer.js +++ b/test/helpers/deployer.js @@ -171,6 +171,20 @@ class Deployer { stakeManager.contract.methods.updateCheckpointRewardParams(val1, val2, val3).encodeABI() ) } + + stakeManager.setBidCooldown = (cooldown) => { + return governance.update( + stakeManager.address, + stakeManager.contract.methods.setBidCooldown(cooldown).encodeABI() + ) + } + + stakeManager.setMinBidStakeFraction = (fraction) => { + return governance.update( + stakeManager.address, + stakeManager.contract.methods.setMinBidStakeFraction(fraction).encodeABI() + ) + } } async deployStakeManager(wallets) { diff --git a/test/units/staking/stakeManager/StakeManager.test.js b/test/units/staking/stakeManager/StakeManager.test.js index 53e54801f..d2ee1e2dc 100644 --- a/test/units/staking/stakeManager/StakeManager.test.js +++ b/test/units/staking/stakeManager/StakeManager.test.js @@ -186,11 +186,11 @@ contract('StakeManager', async function(accounts) { this.stakeManager.contract.methods.setStakingToken(this.stakeToken.address).encodeABI() ) - await this.stakeToken.mint(this.stakeManager.address, web3.utils.toWei('10000000')) + await this.stakeToken.mint(this.stakeManager.address, toWei('10000000')) this.validatorId = '1' this.validatorUser = wallets[0] - this.stakeAmount = new BN(web3.utils.toWei('100')) + this.stakeAmount = new BN(toWei('100')) await approveAndStake.call(this, { wallet: this.validatorUser, stakeAmount: this.stakeAmount, acceptDelegation: true }) @@ -199,7 +199,7 @@ contract('StakeManager', async function(accounts) { this.user = wallets[2].getChecksumAddressString() - const approveAmount = web3.utils.toWei('20000') + const approveAmount = toWei('20000') await this.stakeToken.mint( this.user, approveAmount @@ -252,7 +252,7 @@ contract('StakeManager', async function(accounts) { describe('after commision rate changed', function() { it('Alice must purchase voucher', async function() { - await buyVoucher(this.validatorContract, web3.utils.toWei('100'), this.user) + await buyVoucher(this.validatorContract, toWei('100'), this.user) }) it('1 checkpoint must be commited', async function() { @@ -260,7 +260,7 @@ contract('StakeManager', async function(accounts) { }) it('liquid rewards must be correct', async function() { - assertBigNumberEquality(await this.validatorContract.getLiquidRewards(this.user), web3.utils.toWei('2250')) + assertBigNumberEquality(await this.validatorContract.getLiquidRewards(this.user), toWei('2250')) }) }) }) @@ -306,21 +306,21 @@ contract('StakeManager', async function(accounts) { await buyVoucher(this.validatorContract, this.stakeAmount, this.user) }) // get 25% of checkpoint rewards - testAfterComissionChange(web3.utils.toWei('2250'), '100') + testAfterComissionChange(toWei('2250'), '100') }) testCommisionRate('50', '100') describe('after commision rate changed', function() { // get 0% of checkpoint rewards - testAfterComissionChange(web3.utils.toWei('9000'), '100') + testAfterComissionChange(toWei('9000'), '100') }) testCommisionRate('100', '0') describe('after commision rate changed', function() { // get only 50% of checkpoint rewards - testAfterComissionChange(web3.utils.toWei('13500'), '100') + testAfterComissionChange(toWei('13500'), '100') }) }) @@ -352,7 +352,7 @@ contract('StakeManager', async function(accounts) { describe('updateValidatorDelegation', function() { let staker = wallets[1] - let stakeAmount = web3.utils.toWei('100') + let stakeAmount = toWei('100') function doDeploy(acceptDelegation) { before('Fresh deploy', freshDeploy) @@ -454,9 +454,9 @@ contract('StakeManager', async function(accounts) { describe('proposer bonus must be rewarded to the proposer without distribution to the delegators', function() { const delegator = wallets[1].getChecksumAddressString() const stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('100')) } + { wallet: wallets[2], stake: new BN(toWei('100')) }, + { wallet: wallets[4], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('100')) } ] const signers = stakers.map(x => x.wallet) @@ -491,9 +491,9 @@ contract('StakeManager', async function(accounts) { const validatorWallet = wallets[2] const validatorId = '1' const stakers = [ - { wallet: validatorWallet, stake: new BN(web3.utils.toWei('200')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('200')) } + { wallet: validatorWallet, stake: new BN(toWei('200')) }, + { wallet: wallets[4], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('200')) } ] const signers = stakers.map(x => x.wallet) @@ -518,9 +518,9 @@ contract('StakeManager', async function(accounts) { const validatorWallet = wallets[4] const validatorId = '2' const stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('200')) }, - { wallet: validatorWallet, stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('200')) } + { wallet: wallets[2], stake: new BN(toWei('200')) }, + { wallet: validatorWallet, stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('200')) } ] const signers = stakers.map(x => x.wallet) @@ -543,9 +543,9 @@ contract('StakeManager', async function(accounts) { describe('when validator signs twice and sends his 2nd signature out of order', function() { let stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('1000')) } + { wallet: wallets[2], stake: new BN(toWei('100')) }, + { wallet: wallets[4], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('1000')) } ] const signers = stakers.map(x => x.wallet) @@ -566,8 +566,8 @@ contract('StakeManager', async function(accounts) { describe('when validators sign several times', function() { const stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('200')) } + { wallet: wallets[2], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('200')) } ] const signers = stakers.map(x => x.wallet) @@ -595,7 +595,7 @@ contract('StakeManager', async function(accounts) { for (let i = 0; i < 100; ++i) { stakers.push({ wallet: w[i], - stake: new BN(web3.utils.toWei('1')) + stake: new BN(toWei('1')) }) } @@ -607,8 +607,8 @@ contract('StakeManager', async function(accounts) { describe('when 2 validators stakes, block interval 1, 1 epoch', function() { const stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('200')) } + { wallet: wallets[2], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('200')) } ] prepareToTest(stakers) @@ -638,9 +638,9 @@ contract('StakeManager', async function(accounts) { describe('when 3 validators stake', function() { let stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('200')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('300')) } + { wallet: wallets[2], stake: new BN(toWei('100')) }, + { wallet: wallets[3], stake: new BN(toWei('200')) }, + { wallet: wallets[4], stake: new BN(toWei('300')) } ] function runTests(checkpointBlockInterval, blockInterval, epochs, expectedRewards) { @@ -780,14 +780,14 @@ contract('StakeManager', async function(accounts) { describe('when 3 validators stake but only 1 signs', function() { let stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('1000')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('100')) } + { wallet: wallets[2], stake: new BN(toWei('1000')) }, + { wallet: wallets[3], stake: new BN(toWei('100')) }, + { wallet: wallets[4], stake: new BN(toWei('100')) } ] prepareToTest(stakers, 1) testCheckpointing(stakers, [stakers[0].wallet], 1, 1, { - [stakers[0].wallet.getAddressString()]: web3.utils.toWei('7500'), + [stakers[0].wallet.getAddressString()]: toWei('7500'), [stakers[1].wallet.getAddressString()]: '0', [stakers[2].wallet.getAddressString()]: '0' }) @@ -795,13 +795,13 @@ contract('StakeManager', async function(accounts) { describe('when 7 validators stake but only 1 signs', function() { let stakers = [ - { wallet: wallets[2], stake: new BN(web3.utils.toWei('10000')) }, - { wallet: wallets[3], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[4], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[5], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[6], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[7], stake: new BN(web3.utils.toWei('100')) }, - { wallet: wallets[8], stake: new BN(web3.utils.toWei('100')) } + { wallet: wallets[2], stake: new BN(toWei('10000')) }, + { wallet: wallets[3], stake: new BN(toWei('100')) }, + { wallet: wallets[4], stake: new BN(toWei('100')) }, + { wallet: wallets[5], stake: new BN(toWei('100')) }, + { wallet: wallets[6], stake: new BN(toWei('100')) }, + { wallet: wallets[7], stake: new BN(toWei('100')) }, + { wallet: wallets[8], stake: new BN(toWei('100')) } ] prepareToTest(stakers, 1) @@ -819,7 +819,7 @@ contract('StakeManager', async function(accounts) { describe('when payload is invalid', function() { beforeEach(freshDeploy) beforeEach('Prepare to test', async function() { - this.amount = new BN(web3.utils.toWei('200')) + this.amount = new BN(toWei('200')) this.wallets = [wallets[2]] this.voteData = 'dummyData' this.stateRoot = utils.bufferToHex(utils.keccak256('stateRoot')) @@ -877,7 +877,7 @@ contract('StakeManager', async function(accounts) { }) describe('with votes', function() { - const amount = new BN(web3.utils.toWei('200')) + const amount = new BN(toWei('200')) async function feeCheckpointWithVotes(validatorId, start, end, votes, _sigPrefix, proposer) { let tree = await buildTreeFee(this.validators, this.accumulatedFees, this.checkpointIndex) @@ -929,7 +929,7 @@ contract('StakeManager', async function(accounts) { describe('Deploying and staking with 4 validators...', async function() { const AliceValidatorId = 1 - const firstFeeToClaim = new BN(web3.utils.toWei('25')) + const firstFeeToClaim = new BN(toWei('25')) beforeEach(function() { this.trees = [] @@ -1042,7 +1042,7 @@ contract('StakeManager', async function(accounts) { async function doDeploy() { await freshDeploy.call(this) - const amount = web3.utils.toWei('200') + const amount = toWei('200') for (const wallet of w) { await approveAndStake.call(this, { wallet, stakeAmount: amount }) } @@ -1086,8 +1086,8 @@ contract('StakeManager', async function(accounts) { { wallet: wallets[5] }, { wallet: wallets[3] } ], [wallets[5], wallets[0]], 1, 1, { - [wallets[5].getAddressString()]: web3.utils.toWei('4500'), - [wallets[3].getAddressString()]: web3.utils.toWei('4500') + [wallets[5].getAddressString()]: toWei('4500'), + [wallets[3].getAddressString()]: toWei('4500') }) }) @@ -1338,7 +1338,7 @@ contract('StakeManager', async function(accounts) { async function doDeploy() { await freshDeploy.call(this) - this.amount = new BN(web3.utils.toWei('200')) + this.amount = new BN(toWei('200')) this.totalStaked = new BN(0) for (const wallet of _wallets) { @@ -1435,8 +1435,8 @@ contract('StakeManager', async function(accounts) { describe('topUpForFee', function() { const wallet = wallets[1] const validatorUser = wallet.getChecksumAddressString() - const amount = web3.utils.toWei('200') - const fee = new BN(web3.utils.toWei('50')) + const amount = toWei('200') + const fee = new BN(toWei('50')) async function doDeploy() { await freshDeploy.call(this) @@ -1507,7 +1507,7 @@ contract('StakeManager', async function(accounts) { runTopUpTests(async function() { await doDeploy.call(this) - const mintAmount = web3.utils.toWei('10000') + const mintAmount = toWei('10000') await this.stakeToken.mint(user, mintAmount) await this.stakeToken.approve(this.stakeManager.address, new BN(mintAmount), { from: user @@ -1558,7 +1558,7 @@ contract('StakeManager', async function(accounts) { }) describe('claimFee', function() { - const amount = new BN(web3.utils.toWei('200')) + const amount = new BN(toWei('200')) async function feeCheckpoint(validatorId, start, end, proposer) { let tree = await buildTreeFee(this.validators, this.accumulatedFees, this.checkpointIndex) @@ -1674,9 +1674,9 @@ contract('StakeManager', async function(accounts) { describe('when Alice topups once and claims 2 times', async function() { const AliceValidatorId = 1 - const totalFee = new BN(web3.utils.toWei('100')) - const firstFeeToClaim = new BN(web3.utils.toWei('25')) - const secondFeeToClaim = new BN(web3.utils.toWei('100')) + const totalFee = new BN(toWei('100')) + const firstFeeToClaim = new BN(toWei('25')) + const secondFeeToClaim = new BN(toWei('100')) before(function() { this.trees = [] @@ -1737,7 +1737,7 @@ contract('StakeManager', async function(accounts) { before(function() { this.user = this.validatorsWallets[AliceValidatorId].getChecksumAddressString() this.validatorsCount = 2 - this.fee = new BN(web3.utils.toWei('50')) + this.fee = new BN(toWei('50')) this.claimedFee = this.fee this.topUpFeeFor = { [this.user]: this.fee @@ -1768,8 +1768,8 @@ contract('StakeManager', async function(accounts) { // If i want to be able to withdraw fee from previous checkpoint - should i commit previous tree root? describe.skip('when Alice top ups 2 times with different values', function() { const AliceValidatorId = 1 - const firstFee = new BN(web3.utils.toWei('50')) - const secondFee = new BN(web3.utils.toWei('30')) + const firstFee = new BN(toWei('50')) + const secondFee = new BN(toWei('30')) describe('when topup', function() { before(function() { @@ -1826,7 +1826,7 @@ contract('StakeManager', async function(accounts) { describe('reverts', function() { beforeEach(function() { this.validatorsCount = 2 - this.fee = new BN(web3.utils.toWei('50')) + this.fee = new BN(toWei('50')) this.validatorId = 1 }) @@ -1876,9 +1876,17 @@ contract('StakeManager', async function(accounts) { }) }) - describe('startAuction', function() { + describe.only('startAuction', function() { const _initialStakers = [wallets[1], wallets[2]] - const initialStakeAmount = web3.utils.toWei('200') + const initialStakeAmount = toWei('200') + const AliceAndBobBalance = toWei('100000') + + const Alice = wallets[3] + const Bob = wallets[4] + + let aliceBidAmount = toWei('1200') + let bobBidAmount = toWei('1250') + const BidCooldown = 600 // 10 minutes async function doDeploy() { await prepareForTest(8, 10).call(this) @@ -1893,88 +1901,135 @@ contract('StakeManager', async function(accounts) { for (let i = currentEpoch; i <= auctionPeriod + (await this.stakeManager.dynasty()).toNumber(); i++) { await checkPoint(_initialStakers, this.rootChainOwner, this.stakeManager) } - this.amount = web3.utils.toWei('500') - await this.stakeToken.approve(this.stakeManager.address, this.amount, { - from: wallets[3].getAddressString() + // prepare Alice nad Bob to bidding + await this.stakeToken.mint(Alice.getAddressString(), AliceAndBobBalance) + await this.stakeToken.approve(this.stakeManager.address, AliceAndBobBalance, { + from: Alice.getAddressString() }) - } - - describe('Alice and Bob bid', function() { - const Alice = wallets[3] - const Bob = wallets[4] - let aliceBidAmount = web3.utils.toWei('1200') - let bobBidAmount = web3.utils.toWei('1250') + await this.stakeToken.mint(Bob.getAddressString(), AliceAndBobBalance) + await this.stakeToken.approve(this.stakeManager.address, AliceAndBobBalance, { + from: Bob.getAddressString() + }) - before('deploy', doDeploy) - before(async function() { - await this.stakeToken.mint(Alice.getAddressString(), aliceBidAmount) - await this.stakeToken.approve(this.stakeManager.address, aliceBidAmount, { - from: Alice.getAddressString() - }) + this.aliceOldBalance = await this.stakeToken.balanceOf(Alice.getAddressString()) + this.bobOldBalance = await this.stakeToken.balanceOf(Bob.getAddressString()) - await this.stakeToken.mint(Bob.getAddressString(), bobBidAmount) - await this.stakeToken.approve(this.stakeManager.address, bobBidAmount, { - from: Bob.getAddressString() - }) + this.validatorId = '1' + this.initialStakeAmount = initialStakeAmount - this.userOldBalance = await this.stakeToken.balanceOf(Alice.getAddressString()) - this.bobOldBalance = await this.stakeToken.balanceOf(Bob.getAddressString()) + await this.stakeManager.setBidCooldown(BidCooldown) + } - this.validatorId = '1' - this.initialStakeAmount = initialStakeAmount - }) + describe('Alice and Bob bid', function() { + before('deploy', doDeploy) describe('when Alice bids', function() { + before(function() { + this.userOldBalance = this.aliceOldBalance + }) testStartAuction(Alice.getChecksumAddressString(), Alice.getPrivateKeyString(), aliceBidAmount) }) describe('when Bob bids', function() { + before(function() { + this.userOldBalance = this.bobOldBalance + }) + testStartAuction(Bob.getChecksumAddressString(), Bob.getPublicKeyString(), bobBidAmount) it('Alice must get her bid back', async function() { const currentBalance = await this.stakeToken.balanceOf(Alice.getAddressString()) - assertBigNumberEquality(this.userOldBalance, currentBalance) + assertBigNumberEquality(this.aliceOldBalance, currentBalance) }) }) }) - describe('reverts', function() { - beforeEach('deploy', doDeploy) + describe('when Alice and Bob participate in the active bidding', function() { + describe('when Alice start the auction', function() { + before('deploy', doDeploy) + before('initial bids', async function() { + await this.stakeManager.startAuction(this.validatorId, aliceBidAmount, false, Alice.getPrivateKeyString(), { + from: Alice.getChecksumAddressString() + }) + await this.stakeManager.startAuction(this.validatorId, bobBidAmount, false, Bob.getPrivateKeyString(), { + from: Bob.getChecksumAddressString() + }) + await this.stakeManager.setMinBidStakeFraction('1000') // 10% of total stake + }) - it('when bid during non-auction period', async function() { - let auctionPeriod = await this.stakeManager.auctionPeriod() - await this.stakeManager.advanceEpoch((auctionPeriod).toNumber()) - await expectRevert(this.stakeManager.startAuction(1, this.amount, false, wallets[3].getPrivateKeyString(), { - from: wallets[3].getAddressString() - }), 'Invalid auction period') + describe('when Alice outbids Bob immediately', function() { + it('reverts', async function() { + await expectRevert( + this.stakeManager.startAuction(this.validatorId, new BN(bobBidAmount).add(new BN('1')), false, Alice.getPrivateKeyString(), { + from: Alice.getChecksumAddressString() + }), + 'bid too often' + ) + }) + }) }) + }) - it('when trying to start and confirm in last epoch', async function() { - this.validatorId = 1 + describe('when Alice tries to start and confirm at the last epoch of the auction', async function() { + const amount = toWei('300') + const validatorId = 1 + + before(doDeploy) + before(async function() { await this.stakeManager.advanceEpoch(1) - await this.stakeManager.startAuction(this.validatorId, this.amount, false, wallets[3].getPublicKeyString(), { + await this.stakeManager.startAuction(validatorId, amount, false, wallets[3].getPublicKeyString(), { from: wallets[3].getAddressString() }) - await this.stakeToken.approve(this.stakeManager.address, web3.utils.toWei('1'), { + await this.stakeToken.approve(this.stakeManager.address, toWei('1'), { from: wallets[3].getAddressString() }) - await expectRevert(this.stakeManager.confirmAuctionBid( - this.validatorId, - web3.utils.toWei('1'), - { - from: wallets[3].getAddressString() - } - ), 'Not allowed before auctionPeriod') - await this.stakeManager.advanceEpoch(1) - await this.stakeManager.confirmAuctionBid( - this.validatorId, - web3.utils.toWei('1'), - { - from: wallets[3].getAddressString() - } - ) - assert.ok(!(await this.stakeManager.isValidator(this.validatorId))) + }) + + describe('when confirming auction too soon', function() { + it('reverts', async function() { + await expectRevert(this.stakeManager.confirmAuctionBid( + this.validatorId, + toWei('1'), + { + from: wallets[3].getAddressString() + } + ), 'Not allowed before auctionPeriod') + }) + }) + + describe('when confirming auction on time', function() { + before(async function() { + await this.stakeManager.advanceEpoch(1) + }) + + it('should confirm auction', async function() { + await this.stakeManager.confirmAuctionBid( + this.validatorId, + toWei('1'), + { + from: wallets[3].getAddressString() + } + ) + }) + + it('should become validator', async function() { + assert.ok(!(await this.stakeManager.isValidator(this.validatorId))) + }) + }) + }) + + describe('reverts', function() { + beforeEach('deploy', doDeploy) + + const amount = toWei('300') + + it('when bid during non-auction period', async function() { + let auctionPeriod = await this.stakeManager.auctionPeriod() + await this.stakeManager.advanceEpoch((auctionPeriod).toNumber()) + await expectRevert(this.stakeManager.startAuction(1, amount, false, wallets[3].getPrivateKeyString(), { + from: wallets[3].getAddressString() + }), 'Invalid auction period') }) it('when bid during replacement cooldown', async function() { @@ -1983,21 +2038,21 @@ contract('StakeManager', async function(accounts) { this.stakeManager.contract.methods.updateDynastyValue('7').encodeABI() ) - await expectRevert(this.stakeManager.startAuction(1, this.amount, false, wallets[3].getPrivateKeyString(), { + await expectRevert(this.stakeManager.startAuction(1, amount, false, wallets[3].getPrivateKeyString(), { from: wallets[3].getAddressString() }), 'Cooldown period') }) it('when bid on unstaking validator', async function() { await this.stakeManager.unstake(1, { from: _initialStakers[0].getAddressString() }) - await expectRevert(this.stakeManager.startAuction(1, this.amount, false, wallets[3].getPrivateKeyString(), { + await expectRevert(this.stakeManager.startAuction(1, amount, false, wallets[3].getPrivateKeyString(), { from: wallets[3].getAddressString() }), 'Invalid validator for an auction') }) it('when restake on unstaking validator', async function() { await this.stakeManager.unstake(1, { from: _initialStakers[0].getAddressString() }) - await expectRevert(this.stakeManager.restake(1, this.amount, false, { + await expectRevert(this.stakeManager.restake(1, amount, false, { from: _initialStakers[0].getAddressString() }), 'No restaking') }) @@ -2014,13 +2069,13 @@ contract('StakeManager', async function(accounts) { }) it('when validatorId is invalid', async function() { - await expectRevert.unspecified(this.stakeManager.startAuction(0, this.amount, false, wallets[3].getPrivateKeyString(), { + await expectRevert.unspecified(this.stakeManager.startAuction(0, amount, false, wallets[3].getPrivateKeyString(), { from: wallets[3].getAddressString() })) }) it('when bid is too low', async function() { - await expectRevert(this.stakeManager.startAuction(1, web3.utils.toWei('100'), false, wallets[3].getPrivateKeyString(), { + await expectRevert(this.stakeManager.startAuction(1, toWei('100'), false, wallets[3].getPrivateKeyString(), { from: wallets[3].getAddressString() }), 'Must bid higher') }) @@ -2029,8 +2084,8 @@ contract('StakeManager', async function(accounts) { describe('confirmAuctionBid', function() { const initialStakers = [wallets[1], wallets[2]] - const bidAmount = new BN(web3.utils.toWei('1200')) - const initialStakeAmount = web3.utils.toWei('200') + const bidAmount = new BN(toWei('1200')) + const initialStakeAmount = toWei('200') function doDeploy(skipAuctionPeriod = true) { return async function() { @@ -2044,7 +2099,7 @@ contract('StakeManager', async function(accounts) { await approveAndStake.call(this, { wallet, stakeAmount: initialStakeAmount }) } - this.amount = web3.utils.toWei('500') + this.amount = toWei('500') await this.stakeToken.approve(this.stakeManager.address, this.amount, { from: wallets[3].getAddressString() }) @@ -2077,7 +2132,7 @@ contract('StakeManager', async function(accounts) { } describe('when last auctioner is not validator', function() { - const heimdallFee = web3.utils.toWei('100') + const heimdallFee = toWei('100') function prepareToTest() { before(async function() { @@ -2116,7 +2171,7 @@ contract('StakeManager', async function(accounts) { describe('when validator has more stake then last bid', function() { prepareToTest() before(async function() { - let restakeAmount = web3.utils.toWei('10000') + let restakeAmount = toWei('10000') await this.stakeToken.mint(this.prevValidatorAddr, restakeAmount) await this.stakeToken.approve(this.stakeManager.address, restakeAmount, { from: this.prevValidatorAddr @@ -2202,8 +2257,8 @@ contract('StakeManager', async function(accounts) { const validatorUserAddr = wallets[4].getChecksumAddressString() const auctionValidatorAddr = wallets[5].getChecksumAddressString() const auctionValidatorPubKey = wallets[5].getPublicKeyString() - const stakeAmount = web3.utils.toWei('1250') - const bidAmount = web3.utils.toWei('2555') + const stakeAmount = toWei('1250') + const bidAmount = toWei('2555') function doDeploy() { return async function() { @@ -2217,7 +2272,7 @@ contract('StakeManager', async function(accounts) { before('fresh deploy', doDeploy()) before(async function() { - await this.stakeToken.mint(this.stakeManager.address, web3.utils.toWei('1000000'))// rewards amount + await this.stakeToken.mint(this.stakeManager.address, toWei('1000000'))// rewards amount await approveAndStake.call(this, { wallet: validatorUser, stakeAmount, acceptDelegation: true }) @@ -2287,8 +2342,8 @@ contract('StakeManager', async function(accounts) { describe('stopAuctions', function() { const initialStakers = [wallets[1], wallets[2]] - const stakeAmount = web3.utils.toWei('1250') - const bidAmount = web3.utils.toWei('1350') + const stakeAmount = toWei('1250') + const bidAmount = toWei('1350') const bidder = wallets[3].getChecksumAddressString() const bidderPubKey = wallets[3].getPublicKeyString() @@ -2296,7 +2351,7 @@ contract('StakeManager', async function(accounts) { await prepareForTest(8, 10).call(this) for (const wallet of initialStakers) { - await approveAndStake.call(this, { wallet, stakeAmount, approveAmount: web3.utils.toWei('12500') }) + await approveAndStake.call(this, { wallet, stakeAmount, approveAmount: toWei('12500') }) } } @@ -2337,11 +2392,11 @@ contract('StakeManager', async function(accounts) { describe('stake migration', function() { const initialStakers = [wallets[1], wallets[2], wallets[3], wallets[4], wallets[5], wallets[6], wallets[7], wallets[8], wallets[9]] - const stakeAmount = web3.utils.toWei('1250') + const stakeAmount = toWei('1250') const stakeAmountBN = new BN(stakeAmount) - const delegationAmount = web3.utils.toWei('150') + const delegationAmount = toWei('150') const delegationAmountBN = new BN(delegationAmount) - const migrationAmount = web3.utils.toWei('100') + const migrationAmount = toWei('100') const migrationAmountBN = new BN(migrationAmount) async function prepareForTest() { From 4f9a2d5568c40602d2035ce09d16bee729dfa79e Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Fri, 27 Aug 2021 11:58:20 +0700 Subject: [PATCH 4/7] refactor: re-use a variable --- contracts/staking/stakeManager/StakeManagerExtension.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/staking/stakeManager/StakeManagerExtension.sol b/contracts/staking/stakeManager/StakeManagerExtension.sol index dbbc8caba..373f65a67 100644 --- a/contracts/staking/stakeManager/StakeManagerExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerExtension.sol @@ -38,9 +38,9 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag ); uint256 _currentEpoch = currentEpoch; - uint256 _replacementCoolDown = replacementCoolDown; + uint256 cooldown = replacementCoolDown; // when dynasty period is updated validators are in cooldown period - require(_replacementCoolDown == 0 || _replacementCoolDown <= _currentEpoch, "Cooldown period"); + require(cooldown == 0 || cooldown <= _currentEpoch, "Cooldown period"); // (auctionPeriod--dynasty)--(auctionPeriod--dynasty)--(auctionPeriod--dynasty) // if it's auctionPeriod then will get residue smaller then auctionPeriod // from (CurrentPeriod of validator )%(auctionPeriod--dynasty) @@ -59,7 +59,8 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag Auction storage auction = validatorAuction[validatorId]; // do not allow bidding too often - require(lastBidTimestamp[msg.sender] == 0 || lastBidTimestamp[msg.sender] < block.timestamp, "bid too often"); + cooldown = lastBidTimestamp[msg.sender]; + require(cooldown == 0 || cooldown < block.timestamp, "bid too often"); uint256 currentAuctionAmount = auction.amount; From fb34f59570488cbed529f0230bb247cbd2136d15 Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Fri, 27 Aug 2021 11:58:39 +0700 Subject: [PATCH 5/7] new: more tests --- .../staking/stakeManager/StakeManager.test.js | 80 ++++++++++++++----- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/test/units/staking/stakeManager/StakeManager.test.js b/test/units/staking/stakeManager/StakeManager.test.js index d2ee1e2dc..e0c5b542b 100644 --- a/test/units/staking/stakeManager/StakeManager.test.js +++ b/test/units/staking/stakeManager/StakeManager.test.js @@ -1876,30 +1876,30 @@ contract('StakeManager', async function(accounts) { }) }) - describe.only('startAuction', function() { - const _initialStakers = [wallets[1], wallets[2]] - const initialStakeAmount = toWei('200') + describe('startAuction', function() { + const InitialStakers = [wallets[1], wallets[2]] + const InitialStakeAmount = toWei('200') const AliceAndBobBalance = toWei('100000') const Alice = wallets[3] const Bob = wallets[4] - let aliceBidAmount = toWei('1200') - let bobBidAmount = toWei('1250') + const AliceBidAmount = toWei('1200') + const BobBidAmount = toWei('1250') const BidCooldown = 600 // 10 minutes async function doDeploy() { await prepareForTest(8, 10).call(this) - for (const wallet of _initialStakers) { - await approveAndStake.call(this, { wallet, stakeAmount: initialStakeAmount }) + for (const wallet of InitialStakers) { + await approveAndStake.call(this, { wallet, stakeAmount: InitialStakeAmount }) } // cooldown period let auctionPeriod = (await this.stakeManager.auctionPeriod()).toNumber() let currentEpoch = (await this.stakeManager.currentEpoch()).toNumber() for (let i = currentEpoch; i <= auctionPeriod + (await this.stakeManager.dynasty()).toNumber(); i++) { - await checkPoint(_initialStakers, this.rootChainOwner, this.stakeManager) + await checkPoint(InitialStakers, this.rootChainOwner, this.stakeManager) } // prepare Alice nad Bob to bidding await this.stakeToken.mint(Alice.getAddressString(), AliceAndBobBalance) @@ -1916,7 +1916,7 @@ contract('StakeManager', async function(accounts) { this.bobOldBalance = await this.stakeToken.balanceOf(Bob.getAddressString()) this.validatorId = '1' - this.initialStakeAmount = initialStakeAmount + this.initialStakeAmount = InitialStakeAmount await this.stakeManager.setBidCooldown(BidCooldown) } @@ -1928,7 +1928,7 @@ contract('StakeManager', async function(accounts) { before(function() { this.userOldBalance = this.aliceOldBalance }) - testStartAuction(Alice.getChecksumAddressString(), Alice.getPrivateKeyString(), aliceBidAmount) + testStartAuction(Alice.getChecksumAddressString(), Alice.getPrivateKeyString(), AliceBidAmount) }) describe('when Bob bids', function() { @@ -1936,7 +1936,7 @@ contract('StakeManager', async function(accounts) { this.userOldBalance = this.bobOldBalance }) - testStartAuction(Bob.getChecksumAddressString(), Bob.getPublicKeyString(), bobBidAmount) + testStartAuction(Bob.getChecksumAddressString(), Bob.getPublicKeyString(), BobBidAmount) it('Alice must get her bid back', async function() { const currentBalance = await this.stakeToken.balanceOf(Alice.getAddressString()) @@ -1949,19 +1949,18 @@ contract('StakeManager', async function(accounts) { describe('when Alice start the auction', function() { before('deploy', doDeploy) before('initial bids', async function() { - await this.stakeManager.startAuction(this.validatorId, aliceBidAmount, false, Alice.getPrivateKeyString(), { + await this.stakeManager.startAuction(this.validatorId, AliceBidAmount, false, Alice.getPrivateKeyString(), { from: Alice.getChecksumAddressString() }) - await this.stakeManager.startAuction(this.validatorId, bobBidAmount, false, Bob.getPrivateKeyString(), { + await this.stakeManager.startAuction(this.validatorId, BobBidAmount, false, Bob.getPrivateKeyString(), { from: Bob.getChecksumAddressString() }) - await this.stakeManager.setMinBidStakeFraction('1000') // 10% of total stake }) describe('when Alice outbids Bob immediately', function() { it('reverts', async function() { await expectRevert( - this.stakeManager.startAuction(this.validatorId, new BN(bobBidAmount).add(new BN('1')), false, Alice.getPrivateKeyString(), { + this.stakeManager.startAuction(this.validatorId, new BN(BobBidAmount).add(new BN('1')), false, Alice.getPrivateKeyString(), { from: Alice.getChecksumAddressString() }), 'bid too often' @@ -2012,13 +2011,52 @@ contract('StakeManager', async function(accounts) { } ) }) - + it('should become validator', async function() { assert.ok(!(await this.stakeManager.isValidator(this.validatorId))) }) }) }) + describe('Bob uses a minimum bid', function() { + const TargetValidatorId = '3' + const MinBidFraction = '1000' // 10% + + before('deploy', doDeploy) + before(async function() { + // add 1 small validator + await approveAndStake.call(this, { wallet: wallets[3], stakeAmount: toWei('1') }) + + let auctionPeriod = (await this.stakeManager.auctionPeriod()).toNumber() + let currentEpoch = (await this.stakeManager.currentEpoch()).toNumber() + for (let i = currentEpoch; i <= auctionPeriod + (await this.stakeManager.dynasty()).toNumber(); i++) { + await checkPoint(InitialStakers, this.rootChainOwner, this.stakeManager) + } + + await this.stakeManager.setMinBidStakeFraction(MinBidFraction) + }) + + describe('when bid hasn\'t reached a minimum total stake fraction', function() { + it('revert', async function() { + await expectRevert(this.stakeManager.startAuction(TargetValidatorId, toWei('2'), false, wallets[4].getPrivateKeyString(), { + from: wallets[4].getAddressString() + }), 'Must bid higher') + }) + }) + + describe('when bid is a minimum fraction of total stake', function() { + it('should bid', async function() { + const { amount: totalStake } = await this.stakeManager.validatorState() + const minBidFractionPrecision = await this.stakeManager.MIN_BID_PRECISION() + const bid = new BN(totalStake).mul(new BN(MinBidFraction)).div(new BN(minBidFractionPrecision)) + + await this.stakeManager.startAuction(TargetValidatorId, bid.add(new BN('1')), false, wallets[4].getPrivateKeyString(), { + from: wallets[4].getAddressString() + }) + }) + }) + }) + describe('reverts', function() { beforeEach('deploy', doDeploy) @@ -2044,23 +2082,23 @@ contract('StakeManager', async function(accounts) { }) it('when bid on unstaking validator', async function() { - await this.stakeManager.unstake(1, { from: _initialStakers[0].getAddressString() }) + await this.stakeManager.unstake(1, { from: InitialStakers[0].getAddressString() }) await expectRevert(this.stakeManager.startAuction(1, amount, false, wallets[3].getPrivateKeyString(), { from: wallets[3].getAddressString() }), 'Invalid validator for an auction') }) it('when restake on unstaking validator', async function() { - await this.stakeManager.unstake(1, { from: _initialStakers[0].getAddressString() }) + await this.stakeManager.unstake(1, { from: InitialStakers[0].getAddressString() }) await expectRevert(this.stakeManager.restake(1, amount, false, { - from: _initialStakers[0].getAddressString() + from: InitialStakers[0].getAddressString() }), 'No restaking') }) // since all the rewards are given in unstake already it('Must not transfer any rewards after unstaking', async function() { - await this.stakeManager.unstake(1, { from: _initialStakers[0].getAddressString() }) - const receipt = await this.stakeManager.withdrawRewards(1, { from: _initialStakers[0].getAddressString() }) + await this.stakeManager.unstake(1, { from: InitialStakers[0].getAddressString() }) + const receipt = await this.stakeManager.withdrawRewards(1, { from: InitialStakers[0].getAddressString() }) await expectEvent.inTransaction(receipt.tx, StakingInfo, 'ClaimRewards', { validatorId: '1', amount: '0', From 31055c11c480d83c7a503010426418c4a44c3a1f Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Fri, 27 Aug 2021 15:36:46 +0700 Subject: [PATCH 6/7] chg: apply min deposit as fraction of the total supply too --- contracts/staking/stakeManager/StakeManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index 7edcf973f..80b1057cd 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -459,7 +459,7 @@ contract StakeManager is bytes memory signerPubkey ) public onlyWhenUnlocked { require(currentValidatorSetSize() < validatorThreshold, "no more slots"); - require(amount >= minDeposit, "not enough deposit"); + require(amount >= validatorState.amount.mul(minBidStakeFraction).div(MIN_BID_PRECISION), "not enough deposit"); _transferAndTopUp(user, msg.sender, heimdallFee, amount); _stakeFor(user, amount, acceptDelegation, signerPubkey); } From fcfa698055454feb2be4c598a295b63b2b9b8ddd Mon Sep 17 00:00:00 2001 From: Denis Ermolin Date: Fri, 27 Aug 2021 15:36:53 +0700 Subject: [PATCH 7/7] new: tests --- .../stakeManager/StakeManager.Staking.js | 114 +++++++++++------- .../staking/stakeManager/StakeManager.test.js | 2 +- 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/test/units/staking/stakeManager/StakeManager.Staking.js b/test/units/staking/stakeManager/StakeManager.Staking.js index a753b86b0..aa943f024 100644 --- a/test/units/staking/stakeManager/StakeManager.Staking.js +++ b/test/units/staking/stakeManager/StakeManager.Staking.js @@ -6,6 +6,7 @@ import { } from '../../../helpers/utils.js' import { expectEvent, expectRevert, BN } from '@openzeppelin/test-helpers' import { wallets, walletAmounts, freshDeploy, approveAndStake } from '../deployment' +const { toWei } = web3.utils module.exports = function(accounts) { let owner = accounts[0] @@ -17,7 +18,7 @@ module.exports = function(accounts) { let _aproveAmount = aproveAmount || walletAmounts[user].amount let _stakeAmount = stakeAmount || walletAmounts[user].stakeAmount - await approveAndStake.call(this, { wallet, stakeAmount: _stakeAmount, approveAmount: _aproveAmount, noMinting, signer }) + return approveAndStake.call(this, { wallet, stakeAmount: _stakeAmount, approveAmount: _aproveAmount, noMinting, signer }) } } @@ -76,7 +77,7 @@ module.exports = function(accounts) { }) } - function testStake(user, userPubkey, amount, stakeAmount, validatorId, fee) { + function testStake({ user, userPubkey, amount, stakeAmount, validatorId, fee, totalStake }) { before('Approve', async function() { this.user = user this.fee = new BN(fee || this.defaultHeimdallFee) @@ -116,7 +117,7 @@ module.exports = function(accounts) { it('must have correct total staked balance', async function() { const stake = await this.stakeManager.currentValidatorSetTotalStake() - assertBigNumberEquality(stake, stakeAmount) + assertBigNumberEquality(stake, totalStake || stakeAmount) }) it(`must have validatorId == ${validatorId}`, async function() { @@ -174,21 +175,21 @@ module.exports = function(accounts) { describe('when stakes first time', function() { const amounts = walletAmounts[wallets[1].getAddressString()] - testStake( - wallets[1].getChecksumAddressString(), - wallets[1].getPublicKeyString(), - amounts.amount, - amounts.stakeAmount, - 1 - ) + testStake({ + user: wallets[1].getChecksumAddressString(), + userPubkey: wallets[1].getPublicKeyString(), + amount: amounts.amount, + stakeAmount: amounts.stakeAmount, + validatorId: 1 + }) }) describe('when stakes again', function() { testStakeRevert( wallets[1].getChecksumAddressString(), wallets[1].getPublicKeyString(), - web3.utils.toWei('200'), - web3.utils.toWei('200') + toWei('200'), + toWei('200') ) }) }) @@ -213,13 +214,13 @@ module.exports = function(accounts) { testStakeRevert( wallets[2].getChecksumAddressString(), wallets[2].getPublicKeyString(), - web3.utils.toWei('250'), - web3.utils.toWei('150') + toWei('250'), + toWei('150') ) }) describe('when reStakes while on going auction', function() { it('when auction is active', async function() { - let auctionBid = web3.utils.toWei('10000') + let auctionBid = toWei('10000') const auctionUser = wallets[4].getAddressString() await this.stakeToken.mint(auctionUser, auctionBid) await this.stakeToken.approve(this.stakeManager.address, auctionBid, { @@ -245,21 +246,21 @@ module.exports = function(accounts) { describe('when user stakes', function() { const amounts = walletAmounts[wallets[3].getAddressString()] - testStake( - wallets[3].getChecksumAddressString(), - wallets[3].getPublicKeyString(), - amounts.amount, - amounts.stakeAmount, - 1 - ) + testStake({ + user: wallets[3].getChecksumAddressString(), + userPubkey: wallets[3].getPublicKeyString(), + amount: amounts.amount, + stakeAmount: amounts.stakeAmount, + validatorId: 1 + }) }) describe('when other user stakes beyond validator threshold', function() { testStakeRevert( wallets[4].getChecksumAddressString(), wallets[4].getPublicKeyString(), - web3.utils.toWei('100'), - web3.utils.toWei('100'), + toWei('100'), + toWei('100'), true ) }) @@ -272,7 +273,7 @@ module.exports = function(accounts) { const _wallets = [wallets[1], wallets[2], wallets[3]] let expectedValidatorId = 1 for (const wallet of _wallets) { - await doStake(wallet, { approveAmount: web3.utils.toWei('100'), stakeAmount: web3.utils.toWei('100') }).call(this) + await doStake(wallet, { approveAmount: toWei('100'), stakeAmount: toWei('100') }).call(this) const validatorId = await this.stakeManager.getValidatorId(wallet.getAddressString()) assertBigNumberEquality(expectedValidatorId, validatorId) @@ -284,14 +285,14 @@ module.exports = function(accounts) { describe('stake with heimdall fee', function() { before(freshDeploy) - testStake( - wallets[0].getChecksumAddressString(), - wallets[0].getPublicKeyString(), - web3.utils.toWei('200'), - web3.utils.toWei('150'), - 1, - web3.utils.toWei('50') - ) + testStake({ + user: wallets[0].getChecksumAddressString(), + userPubkey: wallets[0].getPublicKeyString(), + amount: toWei('200'), + stakeAmount: toWei('150'), + validatorId: 1, + fee: toWei('50') + }) }) describe('when Alice stakes, change signer and stakes with old signer', function() { @@ -314,6 +315,33 @@ module.exports = function(accounts) { await expectRevert(doStake(wallets[3], { signer: AliceWallet.getPublicKeyString() }).call(this), 'Invalid signer') }) }) + + describe('minimum deposit', function() { + const MinBidFraction = '1000' // 10% + const CorrectDeposit = toWei('100.1') + before(freshDeploy) + before(doStake(wallets[1], { aproveAmount: toWei('1001'), stakeAmount: toWei('1000') })) + before(async function() { + await this.stakeManager.setMinBidStakeFraction(MinBidFraction) + }) + + testStakeRevert( + wallets[2].getChecksumAddressString(), + wallets[2].getPublicKeyString(), + toWei('100'), + toWei('1'), + true + ) + + testStake({ + user: wallets[2].getChecksumAddressString(), + userPubkey: wallets[2].getPublicKeyString(), + amount: toWei('200'), + stakeAmount: CorrectDeposit, + validatorId: '2', + totalStake: new BN(toWei('1000')).add(new BN(CorrectDeposit)) + }) + }) }) describe('unstake', function() { @@ -454,7 +482,7 @@ module.exports = function(accounts) { }) it('when unstakes during auction', async function() { - const amount = web3.utils.toWei('1200') + const amount = toWei('1200') const auctionUser = wallets[4].getAddressString() await this.stakeToken.mint(auctionUser, amount) @@ -580,7 +608,7 @@ module.exports = function(accounts) { const Alice = wallets[2] const Bob = wallets[3] const Eve = wallets[4] - const stakeAmount = web3.utils.toWei('100') + const stakeAmount = toWei('100') before('Alice stake', doStake(Alice, { noMinting: true, stakeAmount })) before('Bob stake', doStake(Bob, { noMinting: true, stakeAmount })) @@ -606,13 +634,13 @@ module.exports = function(accounts) { }) it('must have correct reward', async function() { - assertBigNumberEquality(this.reward, web3.utils.toWei('3000')) + assertBigNumberEquality(this.reward, toWei('3000')) }) it('must emit ClaimRewards', async function() { await expectEvent.inTransaction(this.receipt.tx, StakingInfo, 'ClaimRewards', { validatorId: this.validatorId, - amount: web3.utils.toWei('3000'), + amount: toWei('3000'), totalAmount: await this.stakeManager.totalRewardsLiquidated() }) }) @@ -635,13 +663,13 @@ module.exports = function(accounts) { }) it('must have correct reward', async function() { - assertBigNumberEquality(this.reward, web3.utils.toWei('3000')) + assertBigNumberEquality(this.reward, toWei('3000')) }) it('must emit ClaimRewards', async function() { await expectEvent.inTransaction(this.receipt.tx, StakingInfo, 'ClaimRewards', { validatorId: this.validatorId, - amount: web3.utils.toWei('3000'), + amount: toWei('3000'), totalAmount: await this.stakeManager.totalRewardsLiquidated() }) }) @@ -666,21 +694,21 @@ module.exports = function(accounts) { it('Eve must have correct rewards', async function() { const validatorId = await this.stakeManager.getValidatorId(Eve.getAddressString()) this.reward = await this.stakeManager.validatorReward(validatorId) - assertBigNumberEquality(this.reward, web3.utils.toWei('12000')) + assertBigNumberEquality(this.reward, toWei('12000')) }) }) }) }) describe('restake', function() { - const initialStake = web3.utils.toWei('1000') + const initialStake = toWei('1000') const initialStakers = [wallets[0], wallets[1]] function doDeploy(acceptDelegation) { return async function() { await prepareForTest(8, 8).call(this) - const checkpointReward = new BN(web3.utils.toWei('10000')) + const checkpointReward = new BN(toWei('10000')) await this.governance.update( this.stakeManager.address, @@ -710,7 +738,7 @@ module.exports = function(accounts) { this.validatorReward = checkpointReward.mul(new BN(100 - proposerBonus)).div(new BN(100)).mul(new BN(auctionPeriod - currentEpoch)) this.validatorId = '1' this.user = initialStakers[0].getAddressString() - this.amount = web3.utils.toWei('100') + this.amount = toWei('100') await this.stakeToken.mint(this.user, this.amount) await this.stakeToken.approve(this.stakeManager.address, this.amount, { diff --git a/test/units/staking/stakeManager/StakeManager.test.js b/test/units/staking/stakeManager/StakeManager.test.js index e0c5b542b..17ad04002 100644 --- a/test/units/staking/stakeManager/StakeManager.test.js +++ b/test/units/staking/stakeManager/StakeManager.test.js @@ -2033,7 +2033,7 @@ contract('StakeManager', async function(accounts) { await checkPoint(InitialStakers, this.rootChainOwner, this.stakeManager) } - await this.stakeManager.setMinBidStakeFraction(MinBidFraction) + await this.stakeManager.setMinBidStakeFraction(MinBidFraction) }) describe('when bid hasn\'t reached a minimum total stake fraction', function() {