Skip to content
This repository has been archived by the owner on Mar 1, 2024. It is now read-only.

Auction improvements #377

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion contracts/staking/stakeManager/StakeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ contract StakeManager is
Governance Methods
*/

function setMinBidStakeFraction(uint256 fraction) public onlyGovernance {
minBidStakeFraction = fraction;
}

function setBidCooldown(uint256 cooldown) public onlyGovernance {
bidCooldown = cooldown;
}

function setDelegationEnabled(bool enabled) public onlyGovernance {
delegationEnabled = enabled;
}
Expand Down Expand Up @@ -451,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);
}
Expand Down
15 changes: 12 additions & 3 deletions contracts/staking/stakeManager/StakeManagerExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -57,9 +57,17 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag
perceivedStake = perceivedStake.add(validators[validatorId].delegatedAmount);

Auction storage auction = validatorAuction[validatorId];

// do not allow bidding too often
cooldown = lastBidTimestamp[msg.sender];
require(cooldown == 0 || cooldown < 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");
Expand All @@ -74,6 +82,7 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag
auction.user = msg.sender;
auction.acceptDelegation = _acceptDelegation;
auction.signerPubkey = _signerPubkey;
lastBidTimestamp[msg.sender] = block.timestamp + bidCooldown;

logger.logStartAuction(validatorId, currentValidatorAmount, amount);
}
Expand Down
1 change: 1 addition & 0 deletions contracts/staking/stakeManager/StakeManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
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;
// how many checkpoints to reward
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;
// 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;
}
14 changes: 14 additions & 0 deletions test/helpers/deployer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
114 changes: 71 additions & 43 deletions test/units/staking/stakeManager/StakeManager.Staking.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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 })
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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')
)
})
})
Expand All @@ -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, {
Expand All @@ -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
)
})
Expand All @@ -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)
Expand All @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 }))
Expand All @@ -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()
})
})
Expand All @@ -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()
})
})
Expand All @@ -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,
Expand Down Expand Up @@ -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, {
Expand Down
Loading