Skip to content

Commit

Permalink
Lock initial validator stake
Browse files Browse the repository at this point in the history
  • Loading branch information
varasev committed Aug 26, 2020
1 parent 229d644 commit 958cdff
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 9 deletions.
40 changes: 33 additions & 7 deletions contracts/base/StakingAuRaBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
mapping(address => address[]) internal _stakerPools;
mapping(address => mapping(address => uint256)) internal _stakerPoolsIndexes;
mapping(address => mapping(address => mapping(uint256 => uint256))) internal _stakeAmountByEpoch;
mapping(address => uint256) internal _stakeInitial;

// Reserved storage space to allow for layout changes in the future.
uint256[25] private ______gapForInternal;
uint256[24] private ______gapForInternal;

/// @dev The limit of the minimum candidate stake (CANDIDATE_MIN_STAKE).
uint256 public candidateMinStake;
Expand Down Expand Up @@ -373,6 +374,7 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
address stakingAddress = _pools[i];
require(stakeAmount[stakingAddress][stakingAddress] == 0);
_stake(stakingAddress, stakingAddress, stakingAmount);
_stakeInitial[stakingAddress] = stakingAmount;
emit PlacedStake(stakingAddress, stakingAddress, stakingEpoch, stakingAmount);
}

Expand Down Expand Up @@ -504,6 +506,9 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
stakeAmountTotal[_poolStakingAddress] = newStakeAmountTotal;

if (staker == _poolStakingAddress) {
// Initial validator cannot withdraw their initial stake
require(newStakeAmount >= _stakeInitial[staker]);

// The amount to be withdrawn must be the whole staked amount or
// must not exceed the diff between the entire amount and `candidateMinStake`
require(newStakeAmount == 0 || newStakeAmount >= candidateMinStake);
Expand Down Expand Up @@ -691,13 +696,19 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
/// @param _staker The staker address that is going to withdraw.
function maxWithdrawAllowed(address _poolStakingAddress, address _staker) public view returns(uint256) {
address miningAddress = validatorSetContract.miningByStakingAddress(_poolStakingAddress);
bool isDelegator = _poolStakingAddress != _staker;

if (!_isWithdrawAllowed(miningAddress, _poolStakingAddress != _staker)) {
if (!_isWithdrawAllowed(miningAddress, isDelegator)) {
return 0;
}

uint256 canWithdraw = stakeAmount[_poolStakingAddress][_staker];

if (!isDelegator) {
// Initial validator cannot withdraw their initial stake
canWithdraw = canWithdraw.sub(_stakeInitial[_staker]);
}

if (!validatorSetContract.isValidatorOrPending(miningAddress)) {
// The pool is not a validator and is not going to become one,
// so the staker can only withdraw staked amount minus already
Expand All @@ -723,8 +734,9 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
/// @param _staker The staker address that is going to order the withdrawal.
function maxWithdrawOrderAllowed(address _poolStakingAddress, address _staker) public view returns(uint256) {
address miningAddress = validatorSetContract.miningByStakingAddress(_poolStakingAddress);
bool isDelegator = _poolStakingAddress != _staker;

if (!_isWithdrawAllowed(miningAddress, _poolStakingAddress != _staker)) {
if (!_isWithdrawAllowed(miningAddress, isDelegator)) {
return 0;
}

Expand All @@ -738,7 +750,15 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
// If the pool is an active or pending validator, the staker can order withdrawal
// up to their total staking amount minus an already ordered amount
// minus an amount staked during the current staking epoch
return stakeAmount[_poolStakingAddress][_staker].sub(stakeAmountByCurrentEpoch(_poolStakingAddress, _staker));

uint256 canOrder = stakeAmount[_poolStakingAddress][_staker];

if (!isDelegator) {
// Initial validator cannot withdraw their initial stake
canOrder = canOrder.sub(_stakeInitial[_staker]);
}

return canOrder.sub(stakeAmountByCurrentEpoch(_poolStakingAddress, _staker));
}

/// @dev Prevents sending tokens directly to the `StakingAuRa` contract address
Expand Down Expand Up @@ -1090,14 +1110,20 @@ contract StakingAuRaBase is UpgradeableOwned, IStakingAuRa {
require(_poolStakingAddress != address(0));
require(_amount != 0);

// How much can `staker` withdraw from `_poolStakingAddress` at the moment?
// How much can `_staker` withdraw from `_poolStakingAddress` at the moment?
require(_amount <= maxWithdrawAllowed(_poolStakingAddress, _staker));

uint256 newStakeAmount = stakeAmount[_poolStakingAddress][_staker].sub(_amount);

// The amount to be withdrawn must be the whole staked amount or
// must not exceed the diff between the entire amount and MIN_STAKE
uint256 minAllowedStake = (_poolStakingAddress == _staker) ? candidateMinStake : delegatorMinStake;
// must not exceed the diff between the entire amount and min allowed stake
uint256 minAllowedStake;
if (_poolStakingAddress == _staker) {
require(newStakeAmount >= _stakeInitial[_staker]); // initial validator cannot withdraw their initial stake
minAllowedStake = candidateMinStake;
} else {
minAllowedStake = delegatorMinStake;
}
require(newStakeAmount == 0 || newStakeAmount >= minAllowedStake);

stakeAmount[_poolStakingAddress][_staker] = newStakeAmount;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posdao-contracts",
"version": "0.1.6",
"version": "0.1.7",
"description": "Smart contracts for POSDAO consensus",
"main": "index.js",
"scripts": {
Expand Down
6 changes: 6 additions & 0 deletions test/BlockRewardAuRa.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,19 @@ contract('BlockRewardAuRa', async accounts => {
accounts[2],
accounts[3]
]);

await setCurrentBlockNumber(stakingEpochEndBlock.add(new BN(1)));

for (let i = 0; i < validators.length; i++) {
(await blockRewardAuRa.snapshotPoolValidatorStakeAmount.call(nextStakingEpoch, validators[i])).should.be.bignumber.equal(
candidateMinStake
);
(await blockRewardAuRa.snapshotPoolTotalStakeAmount.call(nextStakingEpoch, validators[i])).should.be.bignumber.equal(
candidateMinStake.add(delegatorMinStake.mul(new BN(3)))
);

const stakingAddress = accounts[4 + i];
await stakingAuRa.orderWithdraw(stakingAddress, candidateMinStake, {from: stakingAddress}).should.be.rejectedWith(ERROR_MSG);
}

const validatorsToBeFinalized = (await validatorSetAuRa.validatorsToBeFinalized.call()).miningAddresses;
Expand Down
17 changes: 17 additions & 0 deletions test/StakingAuRa.js
Original file line number Diff line number Diff line change
Expand Up @@ -2902,6 +2902,23 @@ contract('StakingAuRa', async accounts => {
await stakingAuRa.withdraw(initialStakingAddresses[1], mintAmount.add(new BN(1)), {from: initialStakingAddresses[1]}).should.be.rejectedWith(ERROR_MSG);
await stakingAuRa.withdraw(initialStakingAddresses[1], mintAmount, {from: initialStakingAddresses[1]}).should.be.fulfilled;
});
it('initial validator cannot withdraw initial stake', async () => {
(await stakingAuRa.candidateMinStake.call()).should.be.bignumber.equal(mintAmount.div(new BN(2)));
const zero = new BN(0);
const one = new BN(1);
const initialStake = mintAmount.sub(one);
const stakingAddress = initialStakingAddresses[1];
await stakingAuRa.stake(stakingAddress, initialStake, { from: stakingAddress }).should.be.fulfilled;
await stakingAuRa.setInitialStake(stakingAddress, initialStake).should.be.fulfilled;
await stakingAuRa.stake(stakingAddress, one, { from: stakingAddress }).should.be.fulfilled;
(await stakingAuRa.stakeAmount.call(stakingAddress, stakingAddress)).should.be.bignumber.equal(mintAmount);
await stakingAuRa.withdraw(stakingAddress, one, { from: stakingAddress }).should.be.fulfilled;
(await stakingAuRa.stakeAmount.call(stakingAddress, stakingAddress)).should.be.bignumber.equal(initialStake);
await stakingAuRa.withdraw(stakingAddress, one, { from: stakingAddress }).should.be.rejectedWith(ERROR_MSG);
await stakingAuRa.withdraw(stakingAddress, initialStake, { from: stakingAddress }).should.be.rejectedWith(ERROR_MSG);
await stakingAuRa.setInitialStake(stakingAddress, zero).should.be.fulfilled;
await stakingAuRa.withdraw(stakingAddress, initialStake, { from: stakingAddress }).should.be.fulfilled;
});
it('should fail if withdraw already ordered amount', async () => {
// Set `initiateChangeAllowed` boolean flag to `true`
await validatorSetAuRa.setCurrentBlockNumber(1).should.be.fulfilled;
Expand Down
4 changes: 4 additions & 0 deletions test/mockContracts/StakingAuRaBaseMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ contract StakingAuRaBaseMock is StakingAuRaBase {
_currentBlockNumber = _blockNumber;
}

function setInitialStake(address _stakingAddress, uint256 _amount) public {
_stakeInitial[_stakingAddress] = _amount;
}

function setStakeAmountTotal(address _poolStakingAddress, uint256 _amount) public {
stakeAmountTotal[_poolStakingAddress] = _amount;
}
Expand Down

0 comments on commit 958cdff

Please sign in to comment.