diff --git a/contracts/root/predicates/ERC20Predicate.sol b/contracts/root/predicates/ERC20Predicate.sol index c1fd8cffa..fc68b38b7 100644 --- a/contracts/root/predicates/ERC20Predicate.sol +++ b/contracts/root/predicates/ERC20Predicate.sol @@ -9,9 +9,7 @@ import {SafeMath} from "openzeppelin-solidity/contracts/math/SafeMath.sol"; import {IErcPredicate} from "./IPredicate.sol"; import {Registry} from "../../common/Registry.sol"; -import { - WithdrawManagerHeader -} from "../withdrawManager/WithdrawManagerStorage.sol"; +import {WithdrawManagerHeader} from "../withdrawManager/WithdrawManagerStorage.sol"; contract ERC20Predicate is IErcPredicate { using RLPReader for bytes; diff --git a/contracts/root/predicates/IPredicate.sol b/contracts/root/predicates/IPredicate.sol index e84e2a1c7..9b50f8f66 100644 --- a/contracts/root/predicates/IPredicate.sol +++ b/contracts/root/predicates/IPredicate.sol @@ -7,9 +7,7 @@ import {RLPEncode} from "../../common/lib/RLPEncode.sol"; import {IWithdrawManager} from "../withdrawManager/IWithdrawManager.sol"; import {IDepositManager} from "../depositManager/IDepositManager.sol"; -import { - ExitsDataStructure -} from "../withdrawManager/WithdrawManagerStorage.sol"; +import {ExitsDataStructure} from "../withdrawManager/WithdrawManagerStorage.sol"; import {ChainIdMixin} from "../../common/mixin/ChainIdMixin.sol"; interface IPredicate { diff --git a/contracts/staking/slashing/SlashingManager.sol b/contracts/staking/slashing/SlashingManager.sol index a76428cab..ecb3f91b0 100644 --- a/contracts/staking/slashing/SlashingManager.sol +++ b/contracts/staking/slashing/SlashingManager.sol @@ -103,13 +103,10 @@ contract SlashingManager is ISlashingManager, Ownable { break; } else if (stakeManager.isValidator(validatorId) && signer > lastAdd) { lastAdd = signer; - uint256 amount; - uint256 delegatedAmount; - (amount,,,,,,,,,,,delegatedAmount,) = stakeManager.validators(validatorId); + (, uint256 totalAmount) = stakeManager.signerState(signer); // add delegation power - amount = amount.add(delegatedAmount); - _stakePower = _stakePower.add(amount); + _stakePower = _stakePower.add(totalAmount); } } return (_stakePower >= stakeManager.currentValidatorSetTotalStake().mul(2).div(3).add(1)); diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index f11909521..b55f5ace1 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -44,7 +44,7 @@ contract StakeManager is } struct UnstakedValidatorsContext { - uint256 deactivationEpoch; + uint256 signedStakePower; uint256[] deactivatedValidators; uint256 validatorIndex; } @@ -148,7 +148,7 @@ contract StakeManager is } function delegatedAmount(uint256 validatorId) public view returns (uint256) { - return validators[validatorId].delegatedAmount; + return signerState[validators[validatorId].signer].totalAmount.sub(validators[validatorId].amount); } function delegatorsReward(uint256 validatorId) public view returns (uint256) { @@ -158,7 +158,8 @@ contract StakeManager is function validatorReward(uint256 validatorId) public view returns (uint256) { uint256 _validatorReward; - if (validators[validatorId].deactivationEpoch == 0) { + (, uint256 deactivationEpoch) = _readStatus(validators[validatorId].signer); + if (deactivationEpoch == 0) { (_validatorReward, ) = _evaluateValidatorAndDelegationReward(validatorId); } return validators[validatorId].reward.add(_validatorReward).sub(INITIALIZED_AMOUNT); @@ -177,11 +178,11 @@ contract StakeManager is } function isValidator(uint256 validatorId) public view returns (bool) { + (Status status, uint256 deactivationEpoch) = _readStatus(validators[validatorId].signer); return _isValidator( - validators[validatorId].status, - validators[validatorId].amount, - validators[validatorId].deactivationEpoch, + status, + deactivationEpoch, currentEpoch ); } @@ -411,10 +412,10 @@ contract StakeManager is function unstake(uint256 validatorId) external onlyStaker(validatorId) { require(validatorAuction[validatorId].amount == 0); - Status status = validators[validatorId].status; + (Status status, uint256 deactivationEpoch) = _readStatus(validators[validatorId].signer); require( validators[validatorId].activationEpoch > 0 && - validators[validatorId].deactivationEpoch == 0 && + deactivationEpoch == 0 && (status == Status.Active || status == Status.Locked) ); @@ -457,12 +458,13 @@ contract StakeManager is } function unstakeClaim(uint256 validatorId) public onlyStaker(validatorId) { - uint256 deactivationEpoch = validators[validatorId].deactivationEpoch; + address signer = validators[validatorId].signer; + + (Status status, uint256 deactivationEpoch) = _readStatus(signer); // can only claim stake back after WITHDRAWAL_DELAY require( deactivationEpoch > 0 && - deactivationEpoch.add(WITHDRAWAL_DELAY) <= currentEpoch && - validators[validatorId].status != Status.Unstaked + deactivationEpoch.add(WITHDRAWAL_DELAY) <= currentEpoch && status != Status.Unstaked ); uint256 amount = validators[validatorId].amount; @@ -476,10 +478,10 @@ contract StakeManager is validators[validatorId].amount = 0; validators[validatorId].jailTime = 0; - validators[validatorId].signer = address(0); - signerToValidator[validators[validatorId].signer] = INCORRECT_VALIDATOR_ID; - validators[validatorId].status = Status.Unstaked; + signerToValidator[signer] = INCORRECT_VALIDATOR_ID; + _writeStatus(signer, Status.Unstaked, deactivationEpoch); + signerState[signer].totalAmount = signerState[signer].totalAmount.sub(amount); _transferToken(msg.sender, amount); logger.logUnstaked(msg.sender, validatorId, amount, newTotalStaked); @@ -490,7 +492,9 @@ contract StakeManager is uint256 amount, bool stakeRewards ) public onlyWhenUnlocked onlyStaker(validatorId) { - require(validators[validatorId].deactivationEpoch == 0, "No restaking"); + address signer = validators[validatorId].signer; + (, uint256 deactivationEpoch) = _readStatus(signer); + require(deactivationEpoch == 0, "No restaking"); if (amount > 0) { _transferTokenFrom(msg.sender, address(this), amount); @@ -506,6 +510,7 @@ contract StakeManager is uint256 newTotalStaked = totalStaked.add(amount); totalStaked = newTotalStaked; validators[validatorId].amount = validators[validatorId].amount.add(amount); + signerState[signer].totalAmount = signerState[signer].totalAmount.add(amount); updateTimeline(int256(amount), 0, 0); @@ -544,11 +549,13 @@ contract StakeManager is } function increaseValidatorDelegatedAmount(uint256 validatorId, uint256 amount) private { - validators[validatorId].delegatedAmount = validators[validatorId].delegatedAmount.add(amount); + address signer = validators[validatorId].signer; + signerState[signer].totalAmount = signerState[signer].totalAmount.add(amount); } function decreaseValidatorDelegatedAmount(uint256 validatorId, uint256 amount) public onlyDelegation(validatorId) { - validators[validatorId].delegatedAmount = validators[validatorId].delegatedAmount.sub(amount); + address signer = validators[validatorId].signer; + signerState[signer].totalAmount = signerState[signer].totalAmount.sub(amount); } function updateSigner(uint256 validatorId, bytes memory signerPubkey) public onlyStaker(validatorId) { @@ -563,6 +570,10 @@ contract StakeManager is signerToValidator[currentSigner] = INCORRECT_VALIDATOR_ID; signerToValidator[signer] = validatorId; validators[validatorId].signer = signer; + + signerState[signer] = signerState[currentSigner]; + delete signerState[currentSigner]; + _updateSigner(currentSigner, signer); // reset update time to current time @@ -577,7 +588,6 @@ contract StakeManager is uint256[3][] calldata sigs ) external onlyRootChain returns (uint256) { uint256 _currentEpoch = currentEpoch; - uint256 signedStakePower; address lastAdd; uint256 totalStakers = validatorState.stakerCount; @@ -603,19 +613,16 @@ contract StakeManager is break; } - uint256 validatorId = signerToValidator[signer]; - uint256 amount = validators[validatorId].amount; - Status status = validators[validatorId].status; - unstakeCtx.deactivationEpoch = validators[validatorId].deactivationEpoch; + (Status status, uint256 deactivationEpoch) = _readStatus(signer); - if (_isValidator(status, amount, unstakeCtx.deactivationEpoch, _currentEpoch)) { + if (_isValidator(status, deactivationEpoch, _currentEpoch)) { lastAdd = signer; - signedStakePower = signedStakePower.add(validators[validatorId].delegatedAmount).add(amount); + unstakeCtx.signedStakePower = unstakeCtx.signedStakePower.add(signerState[signer].totalAmount); - if (unstakeCtx.deactivationEpoch != 0) { + if (deactivationEpoch != 0) { // this validator not a part of signers list anymore - unstakeCtx.deactivatedValidators[unstakeCtx.validatorIndex] = validatorId; + unstakeCtx.deactivatedValidators[unstakeCtx.validatorIndex] = signerToValidator[signer]; unstakeCtx.validatorIndex++; } else { unsignedCtx = _fillUnsignedValidators(unsignedCtx, signer); @@ -623,7 +630,7 @@ contract StakeManager is } else if (status == Status.Locked) { // TODO fix double unsignedValidators appearance // make sure that jailed validator doesn't get his rewards too - unsignedCtx.unsignedValidators[unsignedCtx.unsignedValidatorIndex] = validatorId; + unsignedCtx.unsignedValidators[unsignedCtx.unsignedValidatorIndex] = signerToValidator[signer]; unsignedCtx.unsignedValidatorIndex++; unsignedCtx.validatorIndex++; } @@ -636,7 +643,7 @@ contract StakeManager is _increaseRewardAndAssertConsensus( blockInterval, proposer, - signedStakePower, + unstakeCtx.signedStakePower, stateRoot, unsignedCtx.unsignedValidators, unsignedCtx.unsignedValidatorIndex, @@ -674,8 +681,11 @@ contract StakeManager is uint256 jailedAmount; uint256 totalAmount; uint256 i; + uint256 delSlashedAmount; for (; i < slashingInfoList.length; i++) { + uint256 delSlashedAmount = 0; + RLPReader.RLPItem[] memory slashData = slashingInfoList[i].toList(); uint256 validatorId = slashData[0].toUint(); @@ -684,17 +694,22 @@ contract StakeManager is uint256 _amount = slashData[1].toUint(); totalAmount = totalAmount.add(_amount); + address signer = validators[validatorId].signer; + uint256 totalAmount = signerState[signer].totalAmount; + address delegationContract = validators[validatorId].contractAddress; if (delegationContract != address(0x0)) { - uint256 delSlashedAmount = + delSlashedAmount = IValidatorShare(delegationContract).slash( validators[validatorId].amount, - validators[validatorId].delegatedAmount, + totalAmount, _amount ); _amount = _amount.sub(delSlashedAmount); } + signerState[signer].totalAmount = totalAmount.sub(_amount.add(delSlashedAmount)); + uint256 validatorStakeSlashed = validators[validatorId].amount.sub(_amount); validators[validatorId].amount = validatorStakeSlashed; @@ -713,8 +728,12 @@ contract StakeManager is } function unjail(uint256 validatorId) public onlyStaker(validatorId) { - require(validators[validatorId].status == Status.Locked, "Not jailed"); - require(validators[validatorId].deactivationEpoch == 0, "Already unstaking"); + address signer = validators[validatorId].signer; + + (Status status, uint256 deactivationEpoch) = _readStatus(signer); + + require(status == Status.Locked, "Not jailed"); + require(deactivationEpoch == 0, "Already unstaking"); uint256 _currentEpoch = currentEpoch; require(validators[validatorId].jailTime <= _currentEpoch, "Incomplete jail period"); @@ -728,11 +747,10 @@ contract StakeManager is } // undo timeline so that validator is normal validator - updateTimeline(int256(amount.add(validators[validatorId].delegatedAmount)), 1, 0); + updateTimeline(int256(signerState[signer].totalAmount), 1, 0); - validators[validatorId].status = Status.Active; - - address signer = validators[validatorId].signer; + _writeStatus(signer, Status.Active, deactivationEpoch); + logger.logUnjailed(validatorId, signer); } @@ -760,13 +778,14 @@ contract StakeManager is } } + // TODO should be callable by the validator NFT owner instead function updateValidatorDelegation(bool delegation) external { uint256 validatorId = signerToValidator[msg.sender]; + (Status status, uint256 deactivationEpoch) = _readStatus(msg.sender); require( _isValidator( - validators[validatorId].status, - validators[validatorId].amount, - validators[validatorId].deactivationEpoch, + status, + deactivationEpoch, currentEpoch ), "not validator" @@ -791,11 +810,10 @@ contract StakeManager is function _isValidator( Status status, - uint256 amount, uint256 deactivationEpoch, uint256 _currentEpoch ) private pure returns (bool) { - return (amount > 0 && (deactivationEpoch == 0 || deactivationEpoch > _currentEpoch) && status == Status.Active); + return (deactivationEpoch == 0 || deactivationEpoch > _currentEpoch) && status == Status.Active; } function _fillUnsignedValidators(UnsignedValidatorsContext memory context, address signer) @@ -927,16 +945,15 @@ contract StakeManager is // attempt to save gas in case if rewards were updated previosuly if (initialRewardPerStake < currentRewardPerStake) { uint256 validatorsStake = validators[validatorId].amount; - uint256 delegatedAmount = validators[validatorId].delegatedAmount; - if (delegatedAmount > 0) { - uint256 combinedStakePower = validatorsStake.add(delegatedAmount); + uint256 totalAmount = signerState[validators[validatorId].signer].totalAmount; + if (totalAmount > validatorsStake) { _increaseValidatorRewardWithDelegation( validatorId, validatorsStake, - delegatedAmount, + totalAmount, _getEligibleValidatorReward( validatorId, - combinedStakePower, + totalAmount, currentRewardPerStake, initialRewardPerStake ) @@ -982,10 +999,9 @@ contract StakeManager is function _increaseValidatorRewardWithDelegation( uint256 validatorId, uint256 validatorsStake, - uint256 delegatedAmount, + uint256 combinedStakePower, uint256 reward ) private { - uint256 combinedStakePower = delegatedAmount.add(validatorsStake); (uint256 validatorReward, uint256 delegatorsReward) = _getValidatorAndDelegationReward(validatorId, validatorsStake, reward, combinedStakePower); @@ -1028,7 +1044,7 @@ contract StakeManager is returns (uint256 validatorReward, uint256 delegatorsReward) { uint256 validatorsStake = validators[validatorId].amount; - uint256 combinedStakePower = validatorsStake.add(validators[validatorId].delegatedAmount); + uint256 combinedStakePower = signerState[validators[validatorId].signer].totalAmount; uint256 eligibleReward = rewardPerStake - validators[validatorId].initialRewardPerStake; return _getValidatorAndDelegationReward( @@ -1047,9 +1063,13 @@ contract StakeManager is uint256 _currentEpoch = currentEpoch; validators[validatorId].jailTime = _currentEpoch.add(jailCheckpoints); - validators[validatorId].status = Status.Locked; - logger.logJailed(validatorId, _currentEpoch, validators[validatorId].signer); - return validators[validatorId].amount.add(validators[validatorId].delegatedAmount); + + address signer = validators[validatorId].signer; + (, uint256 deactivationEpoch) = _readStatus(signer); + _writeStatus(signer, Status.Locked, deactivationEpoch); + + logger.logJailed(validatorId, _currentEpoch, signer); + return signerState[signer].totalAmount; } function _stakeFor( @@ -1065,25 +1085,28 @@ contract StakeManager is uint256 newTotalStaked = totalStaked.add(amount); totalStaked = newTotalStaked; - + // TODO initialize only required fields validators[validatorId] = Validator({ reward: INITIALIZED_AMOUNT, amount: amount, activationEpoch: _currentEpoch, - deactivationEpoch: 0, + deactivationEpoch_deprecated: 0, jailTime: 0, signer: signer, contractAddress: acceptDelegation ? validatorShareFactory.create(validatorId, address(_logger), registry) : address(0x0), - status: Status.Active, + status_deprecated: Status_deprecated.Active, commissionRate: 0, lastCommissionUpdate: 0, delegatorsReward: INITIALIZED_AMOUNT, - delegatedAmount: 0, + delegatedAmount_deprecated: 0, initialRewardPerStake: rewardPerStake }); + _writeStatus(signer, Status.Active, 0); + signerState[signer].totalAmount = amount; + latestSignerUpdateEpoch[validatorId] = _currentEpoch; NFTContract.mint(user, validatorId); @@ -1107,22 +1130,22 @@ contract StakeManager is uint256 amount = validators[validatorId].amount; address validator = ownerOf(validatorId); - validators[validatorId].deactivationEpoch = exitEpoch; - - // unbond all delegators in future - int256 delegationAmount = int256(validators[validatorId].delegatedAmount); + address signer = validators[validatorId].signer; + (Status status, ) = _readStatus(signer); + _writeStatus(signer, status, exitEpoch); + // disabled delegation address delegationContract = validators[validatorId].contractAddress; if (delegationContract != address(0)) { IValidatorShare(delegationContract).lock(); } - _removeSigner(validators[validatorId].signer); + _removeSigner(signer); _liquidateRewards(validatorId, validator); uint256 targetEpoch = exitEpoch <= currentEpoch ? 0 : exitEpoch; - updateTimeline(-(int256(amount) + delegationAmount), -1, targetEpoch); + updateTimeline(-int256(signerState[signer].totalAmount), -1, targetEpoch); logger.logUnstakeInit(validator, validatorId, exitEpoch, amount); } diff --git a/contracts/staking/stakeManager/StakeManagerExtension.sol b/contracts/staking/stakeManager/StakeManagerExtension.sol index c74dbcca4..96bff5ba6 100644 --- a/contracts/staking/stakeManager/StakeManagerExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerExtension.sol @@ -24,12 +24,16 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag bytes calldata _signerPubkey ) external { uint256 currentValidatorAmount = validators[validatorId].amount; + address signer = validators[validatorId].signer; + // re-use variable, dirty. It's deactivationEpoch + (, uint256 senderValidatorId) = _readStatus(signer); require( - validators[validatorId].deactivationEpoch == 0 && currentValidatorAmount != 0, + senderValidatorId == 0 && currentValidatorAmount != 0, "Invalid validator for an auction" ); - uint256 senderValidatorId = signerToValidator[msg.sender]; + + senderValidatorId = signerToValidator[msg.sender]; // make sure that signer wasn't used already require( NFTContract.balanceOf(msg.sender) == 0 && // existing validators can't bid @@ -53,8 +57,7 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag "Invalid auction period" ); - uint256 perceivedStake = currentValidatorAmount; - perceivedStake = perceivedStake.add(validators[validatorId].delegatedAmount); + uint256 perceivedStake = signerState[signer].totalAmount; Auction storage auction = validatorAuction[validatorId]; uint256 currentAuctionAmount = auction.amount; @@ -98,14 +101,14 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag ); require(auction.user != address(0x0), "Invalid auction"); + address signer = validators[validatorId].signer; uint256 validatorAmount = validators[validatorId].amount; - uint256 perceivedStake = validatorAmount; + uint256 perceivedStake = signerState[signer].totalAmount; uint256 auctionAmount = auction.amount; - perceivedStake = perceivedStake.add(validators[validatorId].delegatedAmount); - + (, uint256 deactivationEpoch) = _readStatus(signer); // validator is last auctioner - if (perceivedStake >= auctionAmount && validators[validatorId].deactivationEpoch == 0) { + if (perceivedStake >= auctionAmount && deactivationEpoch == 0) { require(token.transfer(auctionUser, auctionAmount), "Bid return failed"); //cleanup auction data auction.startEpoch = _currentEpoch; @@ -127,17 +130,10 @@ contract StakeManagerExtension is StakeManagerStorage, Initializable, StakeManag function migrateValidatorsData(uint256 validatorIdFrom, uint256 validatorIdTo) external { for (uint256 i = validatorIdFrom; i < validatorIdTo; ++i) { - ValidatorShare contractAddress = ValidatorShare(validators[i].contractAddress); - if (contractAddress != ValidatorShare(0)) { - // move validator rewards out from ValidatorShare contract - validators[i].reward = contractAddress.validatorRewards_deprecated().add(INITIALIZED_AMOUNT); - validators[i].delegatedAmount = contractAddress.activeAmount(); - validators[i].commissionRate = contractAddress.commissionRate_deprecated(); - } else { - validators[i].reward = validators[i].reward.add(INITIALIZED_AMOUNT); - } - - validators[i].delegatorsReward = INITIALIZED_AMOUNT; + address signer = validators[i].signer; + + signerState[signer].totalAmount = validators[i].amount.add(validators[i].delegatedAmount_deprecated).sub(INITIALIZED_AMOUNT); + _writeStatus(signer, Status(uint256(validators[i].status_deprecated)), validators[i].deactivationEpoch_deprecated); } } diff --git a/contracts/staking/stakeManager/StakeManagerStorage.sol b/contracts/staking/stakeManager/StakeManagerStorage.sol index 6b448a838..ede1b8383 100644 --- a/contracts/staking/stakeManager/StakeManagerStorage.sol +++ b/contracts/staking/stakeManager/StakeManagerStorage.sol @@ -10,7 +10,7 @@ import {StakingNFT} from "./StakingNFT.sol"; import {ValidatorShareFactory} from "../validatorShare/ValidatorShareFactory.sol"; contract StakeManagerStorage is GovernanceLockable, RootChainable { - enum Status {Inactive, Active, Locked, Unstaked} + enum Status_deprecated {Inactive, Active, Locked, Unstaked} struct Auction { uint256 amount; @@ -34,15 +34,15 @@ contract StakeManagerStorage is GovernanceLockable, RootChainable { uint256 amount; uint256 reward; uint256 activationEpoch; - uint256 deactivationEpoch; + uint256 deactivationEpoch_deprecated; uint256 jailTime; address signer; address contractAddress; - Status status; + Status_deprecated status_deprecated; uint256 commissionRate; uint256 lastCommissionUpdate; uint256 delegatorsReward; - uint256 delegatedAmount; + uint256 delegatedAmount_deprecated; uint256 initialRewardPerStake; } diff --git a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol index dca5cd233..ce6ae8723 100644 --- a/contracts/staking/stakeManager/StakeManagerStorageExtension.sol +++ b/contracts/staking/stakeManager/StakeManagerStorageExtension.sol @@ -1,6 +1,13 @@ pragma solidity 0.5.17; contract StakeManagerStorageExtension { + enum Status {Inactive, Active, Locked, Unstaked} + + struct Signer { + uint256 status; + uint256 totalAmount; + } + address public eventsHub; uint256 public rewardPerStake; address public extensionCode; @@ -14,4 +21,15 @@ contract StakeManagerStorageExtension { uint256 public maxRewardedCheckpoints; // increase / decrease value for faster or slower checkpoints, 0 - 100% uint256 public checkpointRewardDelta; + + mapping(address => Signer) public signerState; + + function _readStatus(address signer) internal view returns(Status status, uint256 deactivationEpoch) { + uint256 combinedStatus = signerState[signer].status; + return (Status(combinedStatus >> 240), uint256(uint240(combinedStatus))); + } + + function _writeStatus(address signer, Status status, uint256 deactivationEpoch) internal { + signerState[signer].status = (uint256(status) << 240) | deactivationEpoch; + } } diff --git a/contracts/staking/validatorShare/ValidatorShare.sol b/contracts/staking/validatorShare/ValidatorShare.sol index f9c10e460..a915911aa 100644 --- a/contracts/staking/validatorShare/ValidatorShare.sol +++ b/contracts/staking/validatorShare/ValidatorShare.sol @@ -110,7 +110,7 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I */ function buyVoucher(uint256 _amount, uint256 _minSharesToMint) public returns(uint256 amountToDeposit) { - _withdrawAndTransferReward(msg.sender); + _withdrawAndTransferReward(msg.sender, msg.sender); amountToDeposit = _buyShares(_amount, _minSharesToMint, msg.sender); require(stakeManager.delegationDeposit(validatorId, amountToDeposit, msg.sender), "deposit failed"); @@ -159,12 +159,17 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I } function withdrawRewards() public { - uint256 rewards = _withdrawAndTransferReward(msg.sender); + uint256 rewards = _withdrawAndTransferReward(msg.sender, msg.sender); + require(rewards >= minAmount, "Too small rewards amount"); + } + + function withdrawRewardsTo(address to) public { + uint256 rewards = _withdrawAndTransferReward(msg.sender, to); require(rewards >= minAmount, "Too small rewards amount"); } function migrateOut(address user, uint256 amount) external onlyOwner { - _withdrawAndTransferReward(user); + _withdrawAndTransferReward(user, user); (uint256 totalStaked, uint256 rate) = getTotalStake(user); require(totalStaked >= amount, "Migrating too much"); @@ -181,7 +186,7 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I } function migrateIn(address user, uint256 amount) external onlyOwner { - _withdrawAndTransferReward(user); + _withdrawAndTransferReward(user, user); _buyShares(amount, 0, user); } @@ -194,16 +199,17 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I function slash( uint256 validatorStake, - uint256 delegatedAmount, + uint256 totalAmount, uint256 totalAmountToSlash ) external onlyOwner returns (uint256) { uint256 _withdrawPool = withdrawPool; - uint256 delegationAmount = delegatedAmount.add(_withdrawPool); + uint256 delegationAmount = totalAmount.sub(validatorStake).add(_withdrawPool); if (delegationAmount == 0) { return 0; } + // total amount to be slashed from delegation pool (active + inactive) - uint256 _amountToSlash = delegationAmount.mul(totalAmountToSlash).div(validatorStake.add(delegationAmount)); + uint256 _amountToSlash = delegationAmount.mul(totalAmountToSlash).div(totalAmount); uint256 _amountToSlashWithdrawalPool = _withdrawPool.mul(_amountToSlash).div(delegationAmount); // slash inactive pool @@ -281,7 +287,7 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I uint256 shares = claimAmount.mul(precision).div(rate); require(shares <= maximumSharesToBurn, "too much slippage"); - _withdrawAndTransferReward(msg.sender); + _withdrawAndTransferReward(msg.sender, msg.sender); _burn(msg.sender, shares); stakeManager.updateValidatorState(validatorId, -int256(claimAmount)); @@ -358,11 +364,11 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I return liquidRewards; } - function _withdrawAndTransferReward(address user) private returns (uint256) { - uint256 liquidRewards = _withdrawReward(user); + function _withdrawAndTransferReward(address from, address to) private returns (uint256) { + uint256 liquidRewards = _withdrawReward(from); if (liquidRewards != 0) { - require(stakeManager.transferFunds(validatorId, liquidRewards, user), "Insufficent rewards"); - stakingLogger.logDelegatorClaimRewards(validatorId, user, liquidRewards); + require(stakeManager.transferFunds(validatorId, liquidRewards, to), "Insufficent rewards"); + stakingLogger.logDelegatorClaimRewards(validatorId, from, liquidRewards); } return liquidRewards; } @@ -401,9 +407,9 @@ contract ValidatorShare is IValidatorShare, ERC20NonTradable, OwnableLockable, I uint256 value ) internal { // get rewards for recipient - _withdrawAndTransferReward(to); + _withdrawAndTransferReward(to, to); // convert rewards to shares - _withdrawAndTransferReward(from); + _withdrawAndTransferReward(from, from); // move shares to recipient super._transfer(from, to, value); } diff --git a/test/units/staking/SlashingManager.test.js b/test/units/staking/SlashingManager.test.js index f69d245ea..c86494966 100644 --- a/test/units/staking/SlashingManager.test.js +++ b/test/units/staking/SlashingManager.test.js @@ -149,8 +149,10 @@ contract('Slashing:validator', async function(accounts) { }) it('validator must be unstaked within current epoch', async function() { - const validatorData = await stakeManager.validators(slashedValidatorId) - assertBigNumberEquality(validatorData.deactivationEpoch, await stakeManager.currentEpoch()) + const state = await stakeManager.signerState(validatorAddr) + // mask out deactivation epoch + const deactivationEpoch = state.status.and(web3.utils.toBN('1766847064778384329583297500742918515827483896875618958121606201292619775')) + assertBigNumberEquality(deactivationEpoch, await stakeManager.currentEpoch()) }) it('validator must not be jailed', async function() { @@ -174,6 +176,7 @@ contract('Slashing:validator', async function(accounts) { }) }) }) + contract('Slashing:delegation', async function(accounts) { let stakeToken let stakeManager @@ -229,10 +232,12 @@ contract('Slashing:delegation', async function(accounts) { logs[0].event.should.equal('Slashed') assertBigNumberEquality(logs[0].args.amount, web3.utils.toWei('200')) const validator1 = await stakeManager.validators(1) + validator = await stakeManager.validators(2) + const state = await stakeManager.signerState(wallets[1].getAddressString()) assertBigNumberEquality(validator1.amount, web3.utils.toWei('900')) - assertBigNumberEquality(validator.delegatedAmount, web3.utils.toWei('230')) + assertBigNumberEquality(state.totalAmount.sub(validator.amount), web3.utils.toWei('230')) }) }) }) diff --git a/test/units/staking/ValidatorShare.test.js b/test/units/staking/ValidatorShare.test.js index 0e2be563a..b123b602b 100644 --- a/test/units/staking/ValidatorShare.test.js +++ b/test/units/staking/ValidatorShare.test.js @@ -104,7 +104,6 @@ contract('ValidatorShare', async function() { this.stakeToken = await TestToken.new('MATIC', 'MATIC') await this.stakeManager.setStakingToken(this.stakeToken.address) - await this.stakeToken.mint(this.stakeManager.address, toWei('10000000')) this.validatorId = '8' @@ -647,6 +646,7 @@ contract('ValidatorShare', async function() { await buyVoucher(this.validatorContract, this.stakeAmount, this.user) }) + before('slash', async function() { await slash.call(this, [{ validator: this.validatorId, amount: this.stakeAmount }], [this.validatorUser], this.validatorUser) }) diff --git a/test/units/staking/stakeManager/StakeManager.test.js b/test/units/staking/stakeManager/StakeManager.test.js index 963597e84..1ce7cc60d 100644 --- a/test/units/staking/stakeManager/StakeManager.test.js +++ b/test/units/staking/stakeManager/StakeManager.test.js @@ -1547,13 +1547,6 @@ contract('StakeManager', async function(accounts) { from: validatorUser }), 'fee too small') }) - - it('when fee overflows', async function() { - const overflowFee = new BN(2).pow(new BN(256)) - await expectRevert.unspecified(this.stakeManager.topUpForFee(validatorUser, overflowFee, { - from: validatorUser - })) - }) }) }) @@ -2104,6 +2097,7 @@ contract('StakeManager', async function(accounts) { prepareToTest() testConfirmAuctionBidForNewValidator() }) + describe('when 1000 dynasties has passed', function() { prepareToTest() before(async function() { @@ -2113,6 +2107,7 @@ contract('StakeManager', async function(accounts) { testConfirmAuctionBidForNewValidator() }) + describe('when validator has more stake then last bid', function() { prepareToTest() before(async function() { @@ -2378,7 +2373,7 @@ contract('StakeManager', async function(accounts) { await this.stakeManager.unstakeClaim(aliceId, { from: initialStakers[1].getChecksumAddressString() }) }) - it('Should migrate', async function() { + it('should migrate', async function() { await this.stakeManager.migrateDelegation(aliceId, bobId, migrationAmount, { from: delegator }) }) }) @@ -2401,7 +2396,7 @@ contract('StakeManager', async function(accounts) { }) describe('Chad delegates to Alice', async function() { - it('Should delegate', async function() { + it('should delegate', async function() { this.receipt = await buyVoucher(aliceContract, delegationAmount, delegator) }) @@ -2431,7 +2426,8 @@ contract('StakeManager', async function(accounts) { it('Active amount must be updated', async function() { const validator = await this.stakeManager.validators(aliceId) - assertBigNumberEquality(validator.delegatedAmount, delegationAmountBN) + const state = await this.stakeManager.signerState(initialStakers[1].getChecksumAddressString()) + assertBigNumberEquality(state.totalAmount.sub(validator.amount), delegationAmountBN) }) }) @@ -2489,13 +2485,13 @@ contract('StakeManager', async function(accounts) { }) it('Alice active amount must be updated', async function() { - const validator = await this.stakeManager.validators(aliceId) - assertBigNumberEquality(validator.delegatedAmount, delegationAmountBN.sub(migrationAmountBN)) + const delegatedAmount = await this.stakeManager.delegatedAmount(aliceId) + assertBigNumberEquality(delegatedAmount, delegationAmountBN.sub(migrationAmountBN)) }) it('Bob active amount must be updated', async function() { - const validator = await this.stakeManager.validators(bobId) - assertBigNumberEquality(validator.delegatedAmount, migrationAmount) + const delegatedAmount = await this.stakeManager.delegatedAmount(bobId) + assertBigNumberEquality(delegatedAmount, migrationAmount) }) }) })