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

Checkpoints optimization v2 #355

Open
wants to merge 12 commits into
base: v0.3.0-backport
Choose a base branch
from
4 changes: 1 addition & 3 deletions contracts/root/predicates/ERC20Predicate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions contracts/root/predicates/IPredicate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 2 additions & 5 deletions contracts/staking/slashing/SlashingManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
147 changes: 85 additions & 62 deletions contracts/staking/stakeManager/StakeManager.sol

Large diffs are not rendered by default.

34 changes: 15 additions & 19 deletions contracts/staking/stakeManager/StakeManagerExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down
8 changes: 4 additions & 4 deletions contracts/staking/stakeManager/StakeManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
18 changes: 18 additions & 0 deletions contracts/staking/stakeManager/StakeManagerStorageExtension.sol
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
}
34 changes: 20 additions & 14 deletions contracts/staking/validatorShare/ValidatorShare.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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");

Expand All @@ -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);
}

Expand All @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down
11 changes: 8 additions & 3 deletions test/units/staking/SlashingManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -174,6 +176,7 @@ contract('Slashing:validator', async function(accounts) {
})
})
})

contract('Slashing:delegation', async function(accounts) {
let stakeToken
let stakeManager
Expand Down Expand Up @@ -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'))
})
})
})
2 changes: 1 addition & 1 deletion test/units/staking/ValidatorShare.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
})
Expand Down
24 changes: 10 additions & 14 deletions test/units/staking/stakeManager/StakeManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}))
})
})
})

Expand Down Expand Up @@ -2104,6 +2097,7 @@ contract('StakeManager', async function(accounts) {
prepareToTest()
testConfirmAuctionBidForNewValidator()
})

describe('when 1000 dynasties has passed', function() {
prepareToTest()
before(async function() {
Expand All @@ -2113,6 +2107,7 @@ contract('StakeManager', async function(accounts) {

testConfirmAuctionBidForNewValidator()
})

describe('when validator has more stake then last bid', function() {
prepareToTest()
before(async function() {
Expand Down Expand Up @@ -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 })
})
})
Expand All @@ -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)
})

Expand Down Expand Up @@ -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)
})
})

Expand Down Expand Up @@ -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)
})
})
})
Expand Down