Skip to content

Commit

Permalink
feat: replace deposit_v4 with deposit_v5 code
Browse files Browse the repository at this point in the history
  • Loading branch information
86667 committed Jan 9, 2025
1 parent a73922a commit 575142b
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 838 deletions.
254 changes: 121 additions & 133 deletions zilliqa/src/contracts/deposit_v4.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct Committee {
uint256 totalStake;
bytes[] stakerKeys;
mapping(bytes => CommitteeStakerEntry) stakers;
uint64 epoch;
}

struct Staker {
Expand Down Expand Up @@ -81,15 +82,10 @@ contract Deposit is UUPSUpgradeable {

/// @custom:storage-location erc7201:zilliqa.storage.DepositStorage
struct DepositStorage {
// The committee in the current epoch and the 2 epochs following it. The value for the current epoch
// is stored at index (currentEpoch() % 3).
// The last 3 committees which had changes. The current committee is the one with the largest epoch.
Committee[3] _committee;
// All stakers. Keys into this map are stored by the `Committee`.
mapping(bytes => Staker) _stakersMap;
// The latest epoch for which the committee was calculated. It is implied that no changes have (yet) occurred in
// future epochs, either because those epochs haven't happened yet or because they have happened, but no deposits
// or withdrawals were made.
uint64 latestComputedEpoch;
uint256 minimumStake;
uint256 maximumStakers;
uint64 blocksPerEpoch;
Expand Down Expand Up @@ -150,16 +146,19 @@ contract Deposit is UUPSUpgradeable {

function committee() private view returns (Committee storage) {
DepositStorage storage $ = _getDepositStorage();
if ($.latestComputedEpoch <= currentEpoch()) {
// If the current epoch is after the latest computed epoch, it is implied that no changes have happened to
// the committee since the latest computed epoch. Therefore, it suffices to return the committee at that
// latest computed epoch.
return $._committee[$.latestComputedEpoch % 3];
} else {
// Otherwise, the committee has been changed. The caller who made the change will have pre-computed the
// result for us, so we can just return it.
return $._committee[currentEpoch() % 3];
// The current committee is the one whose epoch is the largest out of those less than or equal to currentEpoch().
// Any committees with an epoch larger than current is a future committee.
Committee storage currentCommittee = $._committee[0];
for (
uint256 i = 1;
i < $._committee.length;
i++
) {
if ($._committee[i].epoch <= currentEpoch() && $._committee[i].epoch > currentCommittee.epoch) {
currentCommittee = $._committee[i];
}
}
return currentCommittee;
}

function minimumStake() public view returns (uint256) {
Expand Down Expand Up @@ -217,13 +216,104 @@ contract Deposit is UUPSUpgradeable {
return committee().totalStake;
}

function getFutureTotalStake() public view returns (uint256) {
function getFutureCommittee() private view returns (Committee storage) {
DepositStorage storage $ = _getDepositStorage();
// if `latestComputedEpoch > currentEpoch()`
// then `latestComputedEpoch` determines the future committee we need
// otherwise there are no committee changes after `currentEpoch()`
// i.e. `latestComputedEpoch` determines the most recent committee
return $._committee[$.latestComputedEpoch % 3].totalStake;
// The future committee is determined by the committee with the largest epoch
Committee storage futureCommittee = $._committee[0];
for (
uint256 i = 1;
i < $._committee.length;
i++
) {
if ($._committee[i].epoch > futureCommittee.epoch) {
futureCommittee = $._committee[i];
}
}
return futureCommittee;
}

function getOrCreateFutureCommittee() private returns (Committee storage) {
DepositStorage storage $ = _getDepositStorage();

Committee storage futureCommittee = getFutureCommittee();
// Future Committee may already exist, return immediately
if (futureCommittee.epoch == currentEpoch() + 2) {
return futureCommittee;
}

// Create a new committee. Overwrite the committee with the smallest epoch which by now will have passed
Committee storage committeeToOverwrite = $._committee[0];
for (
uint256 i = 1;
i < $._committee.length;
i++
) {
if ($._committee[i].epoch < committeeToOverwrite.epoch) {
committeeToOverwrite = $._committee[i];
}
}

committeeToOverwrite.epoch = currentEpoch() + 2;

// Now set new committee to be the same as that of the most recent update
// The operation we want to do is: committeeToOverwrite = futureCommittee but we are careful to write only when necessary

// Overwrite existing staker's data if necessary
bool stakerIndexChanged = false;
for (
uint256 j = 0;
j < committeeToOverwrite.stakerKeys.length;
j++
) {
bytes memory stakerKey = committeeToOverwrite.stakerKeys[j];
CommitteeStakerEntry storage stakerInFutureCommittee = futureCommittee.stakers[stakerKey];
// If staker exists in future committee then update in new
if (stakerInFutureCommittee.index != 0) {
CommitteeStakerEntry storage stakerInCommitteeToOverwrite = committeeToOverwrite.stakers[stakerKey];
if (stakerInFutureCommittee.index != stakerInCommitteeToOverwrite.index) {
stakerIndexChanged = true;
stakerInCommitteeToOverwrite.index = stakerInFutureCommittee.index;
}
if (stakerInFutureCommittee.balance != stakerInCommitteeToOverwrite.balance) {
stakerInCommitteeToOverwrite.balance = stakerInFutureCommittee.balance;
}
// Otherwise remove them
} else {
delete committeeToOverwrite.stakers[stakerKey];
stakerIndexChanged = true;
}
}

// Now add any stakers which are in future committee which but not in our new committee
for (
uint256 j = 0;
j < futureCommittee.stakerKeys.length;
j++
) {
bytes storage stakerKey = futureCommittee
.stakerKeys[j];
if (committeeToOverwrite.stakers[stakerKey].index == 0) {
committeeToOverwrite.stakers[
stakerKey
] = futureCommittee.stakers[stakerKey];
stakerIndexChanged = true;
}
}

if (futureCommittee.totalStake != committeeToOverwrite.totalStake) {
committeeToOverwrite.totalStake = futureCommittee
.totalStake;
}
if (stakerIndexChanged) {
committeeToOverwrite.stakerKeys = futureCommittee
.stakerKeys;
}

return committeeToOverwrite;
}

function getFutureTotalStake() public view returns (uint256) {
return getFutureCommittee().totalStake;
}

function getStakersData()
Expand Down Expand Up @@ -286,19 +376,10 @@ contract Deposit is UUPSUpgradeable {
if (blsPubKey.length != 48) {
revert UnexpectedArgumentLength("bls public key", 48);
}
DepositStorage storage $ = _getDepositStorage();

// if `latestComputedEpoch > currentEpoch()`
// then `latestComputedEpoch` determines the future committee we need
// otherwise there are no committee changes after `currentEpoch()`
// i.e. `latestComputedEpoch` determines the most recent committee
Committee storage latestCommittee = $._committee[
$.latestComputedEpoch % 3
];

// We don't need to check if `blsPubKey` is in `stakerKeys` here. If the `blsPubKey` is not a staker, the
// balance will default to zero.
return latestCommittee.stakers[blsPubKey].balance;
return getFutureCommittee().stakers[blsPubKey].balance;
}

function getRewardAddress(
Expand Down Expand Up @@ -383,88 +464,14 @@ contract Deposit is UUPSUpgradeable {
return $._stakersMap[blsPubKey].peerId;
}

function updateLatestComputedEpoch() internal {
DepositStorage storage $ = _getDepositStorage();
// If the latest computed epoch is less than two epochs ahead of the current one, we must fill in the missing
// epochs. This just involves copying the committee from the previous epoch to the next one. It is assumed that
// the caller will then want to update the future epochs.
if ($.latestComputedEpoch < currentEpoch() + 2) {
Committee storage latestComputedCommittee = $._committee[
$.latestComputedEpoch % 3
];
// Note the early exit condition if `latestComputedEpoch + 3` which ensures this loop will not run more
// than twice. This is acceptable because we only store 3 committees at a time, so once we have updated two
// of them to the latest computed committee, there is no more work to do.
for (
uint64 i = $.latestComputedEpoch + 1;
i <= currentEpoch() + 2 && i < $.latestComputedEpoch + 3;
i++
) {
// The operation we want to do is: `_committee[i % 3] = latestComputedCommittee` but we are careful to write only when necessary
Committee storage committeeToUpdate = $._committee[i % 3];
bool stakerIndexChanged = false;

// Overwrite existing staker's data
for (
uint256 j = 0;
j < committeeToUpdate.stakerKeys.length;
j++
) {
bytes memory stakerKey = committeeToUpdate.stakerKeys[j];
CommitteeStakerEntry storage stakerInLatestCommittee = latestComputedCommittee.stakers[stakerKey];
// If staker exists in latest then update in new
if (stakerInLatestCommittee.index != 0) {
CommitteeStakerEntry storage stakerInCommitteeToUpdate = committeeToUpdate.stakers[stakerKey];
if (stakerInLatestCommittee.index != stakerInCommitteeToUpdate.index) {
stakerIndexChanged = true;
stakerInCommitteeToUpdate.index = stakerInLatestCommittee.index;
}
if (stakerInLatestCommittee.balance != stakerInCommitteeToUpdate.balance) {
stakerInCommitteeToUpdate.balance = stakerInLatestCommittee.balance;
}
// Otherwise remove them
} else {
delete committeeToUpdate.stakers[stakerKey];
stakerIndexChanged = true;
}
}

// Now add any new stakers
for (
uint256 j = 0;
j < latestComputedCommittee.stakerKeys.length;
j++
) {
bytes storage stakerKey = latestComputedCommittee
.stakerKeys[j];
if (committeeToUpdate.stakers[stakerKey].index == 0) {
committeeToUpdate.stakers[
stakerKey
] = latestComputedCommittee.stakers[stakerKey];
stakerIndexChanged = true;
}
}

if (latestComputedCommittee.totalStake != committeeToUpdate.totalStake) {
committeeToUpdate.totalStake = latestComputedCommittee
.totalStake;
}
if (stakerIndexChanged) {
committeeToUpdate.stakerKeys = latestComputedCommittee
.stakerKeys;
}
}

$.latestComputedEpoch = currentEpoch() + 2;
}
}

// Returns the next block number at which new stakers are added,
// existing ones removed and/or deposits of existing stakers change
function nextUpdate() public view returns (uint256 blockNumber) {
DepositStorage storage $ = _getDepositStorage();
if ($.latestComputedEpoch > currentEpoch())
blockNumber = $.latestComputedEpoch * $.blocksPerEpoch;
uint256 latestCommitteeEpoch = getFutureCommittee().epoch;
if (latestCommitteeEpoch > currentEpoch()) {
DepositStorage storage $ = _getDepositStorage();
blockNumber = latestCommitteeEpoch * $.blocksPerEpoch;
}
}

// keep in-sync with zilliqa/src/precompiles.rs
Expand Down Expand Up @@ -536,11 +543,7 @@ contract Deposit is UUPSUpgradeable {
staker.signingAddress = signingAddress;
staker.controlAddress = msg.sender;

updateLatestComputedEpoch();

Committee storage futureCommittee = $._committee[
(currentEpoch() + 2) % 3
];
Committee storage futureCommittee = getOrCreateFutureCommittee();

if (futureCommittee.stakerKeys.length >= $.maximumStakers) {
revert TooManyStakers();
Expand All @@ -557,22 +560,10 @@ contract Deposit is UUPSUpgradeable {
futureCommittee.stakerKeys.push(blsPubKey);

emit StakerAdded(blsPubKey, nextUpdate(), msg.value);

}

function depositTopup(bytes calldata blsPubKey) public payable onlyControlAddress(blsPubKey) {
DepositStorage storage $ = _getDepositStorage();

bytes storage stakerKey = $._stakerKeys[msg.sender];
if (stakerKey.length == 0) {
revert KeyNotStaked();
}

updateLatestComputedEpoch();

Committee storage futureCommittee = $._committee[
(currentEpoch() + 2) % 3
];
Committee storage futureCommittee = getOrCreateFutureCommittee();
if (futureCommittee.stakers[blsPubKey].index == 0) {
revert KeyNotStaked();
}
Expand All @@ -590,11 +581,8 @@ contract Deposit is UUPSUpgradeable {
function unstake(bytes calldata blsPubKey, uint256 amount) public onlyControlAddress(blsPubKey) {
DepositStorage storage $ = _getDepositStorage();

updateLatestComputedEpoch();

Committee storage futureCommittee = $._committee[
(currentEpoch() + 2) % 3
];
Committee storage futureCommittee = getOrCreateFutureCommittee();
if (futureCommittee.stakers[blsPubKey].index == 0) {
revert KeyNotStaked();
}
Expand Down Expand Up @@ -712,4 +700,4 @@ contract Deposit is UUPSUpgradeable {
(bool sent, ) = msg.sender.call{value: releasedAmount}("");
require(sent, "failed to send");
}
}
}
Loading

0 comments on commit 575142b

Please sign in to comment.