Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(basefee-treasury): implement impl #142

Merged

Conversation

github-actions[bot]
Copy link

Copy link
Author

github-actions bot commented Nov 14, 2024

Slither report

THIS CHECKLIST IS NOT COMPLETE. Use --show-ignored-findings to show all the results.
Summary

reentrancy-eth

Impact: High
Confidence: Medium

function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch {
unchecked {
uint256 newPeriod = _computePeriod(block.timestamp);
bool periodEnding = _isPeriodEnding(newPeriod);
uint256 lastPeriod = currentPeriod();
uint256 epoch = epochOf(block.number);
uint256 nextEpoch = epoch + 1;
IRandomBeacon randomBeacon = IRandomBeacon(getContract(ContractType.RANDOM_BEACON));
// This request is actually only invoked at the first epoch of the period.
randomBeacon.execRequestRandomSeedForNextPeriod(lastPeriod, newPeriod);
// Get all candidate ids
address[] memory allCids = _candidateIds;
_syncFastFinalityReward({ epoch: epoch, validatorIds: allCids });
if (periodEnding) {
ISlashIndicator slashIndicatorContract = ISlashIndicator(getContract(ContractType.SLASH_INDICATOR));
// Slash submit random beacon proof unavailability first, then update credit scores.
randomBeacon.execRecordAndSlashUnavailability(lastPeriod, newPeriod, address(slashIndicatorContract), allCids);
slashIndicatorContract.execUpdateCreditScores(allCids, lastPeriod);
(uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) =
_distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids);
_settleAndTransferDelegatingRewards(
lastPeriod, allCids, delegatorBlockMiningRewards, delegatorFastFinalityRewards
);
_tryRecycleLockedFundsFromEmergencyExits();
_recycleDeprecatedRewards();
address[] memory revokedCandidateIds = _syncCandidateSet(newPeriod);
if (revokedCandidateIds.length > 0) {
// Re-update `allCids` after unsatisfied candidates get removed.
allCids = _candidateIds;
slashIndicatorContract.execResetCreditScores(revokedCandidateIds);
}
// Wrap up the beacon period includes (1) finalizing the beacon proof, and (2) determining the validator list for the next period by new proof.
// Should wrap up the beacon after unsatisfied candidates get removed.
randomBeacon.execFinalizeBeaconAndPendingCids(lastPeriod, newPeriod, allCids);
_periodEndBlock[lastPeriod] = block.number;
_currentPeriodStartAtBlock = block.number + 1;
}
// Clear the previous validator set and block producer set before sync the new set from beacon.
_clearPreviousValidatorSetAndBlockProducerSet();
// Query the new validator set for upcoming epoch from the random beacon contract.
// Save new set into the contract storage.
address[] memory newValidatorIds = _syncValidatorSet(randomBeacon, newPeriod, nextEpoch);
// Activate applicable validators into the block producer set.
_updateApplicableValidatorToBlockProducerSet(newPeriod, nextEpoch, newValidatorIds);
emit WrappedUpEpoch(lastPeriod, epoch, periodEnding);
_periodOf[nextEpoch] = newPeriod;
_lastUpdatedPeriod = newPeriod;
}
}

function _distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(
uint256 lastPeriod,
address[] memory cids
) private returns (uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) {
address vId; // validator id
address payable treasury;
uint256 length = cids.length;
delegatorBlockMiningRewards = new uint256[](length);
delegatorFastFinalityRewards = new uint256[](length);
(uint256 minRate, uint256 maxRate) = IStaking(getContract(ContractType.STAKING)).getCommissionRateRange();
for (uint256 i; i < length; ++i) {
vId = cids[i];
treasury = _candidateInfo[vId].__shadowedTreasury;
if (!_isJailedById(vId) && !_miningRewardDeprecatedById(vId, lastPeriod)) {
(uint256 validatorFFReward, uint256 delegatorFFReward) = _calcCommissionReward({
vId: vId,
totalReward: _fastFinalityReward[vId],
maxCommissionRate: maxRate,
minCommissionRate: minRate
});
delegatorBlockMiningRewards[i] = _delegatorMiningReward[vId];
delegatorFastFinalityRewards[i] = delegatorFFReward;
_distributeMiningReward(vId, treasury);
_distributeFastFinalityReward(vId, treasury, validatorFFReward);
} else {
_totalDeprecatedReward += _validatorMiningReward[vId] + _delegatorMiningReward[vId] + _fastFinalityReward[vId];
}
delete _delegatorMiningReward[vId];
delete _validatorMiningReward[vId];
delete _fastFinalityReward[vId];
}
}

function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch {
unchecked {
uint256 newPeriod = _computePeriod(block.timestamp);
bool periodEnding = _isPeriodEnding(newPeriod);
uint256 lastPeriod = currentPeriod();
uint256 epoch = epochOf(block.number);
uint256 nextEpoch = epoch + 1;
IRandomBeacon randomBeacon = IRandomBeacon(getContract(ContractType.RANDOM_BEACON));
// This request is actually only invoked at the first epoch of the period.
randomBeacon.execRequestRandomSeedForNextPeriod(lastPeriod, newPeriod);
// Get all candidate ids
address[] memory allCids = _candidateIds;
_syncFastFinalityReward({ epoch: epoch, validatorIds: allCids });
if (periodEnding) {
ISlashIndicator slashIndicatorContract = ISlashIndicator(getContract(ContractType.SLASH_INDICATOR));
// Slash submit random beacon proof unavailability first, then update credit scores.
randomBeacon.execRecordAndSlashUnavailability(lastPeriod, newPeriod, address(slashIndicatorContract), allCids);
slashIndicatorContract.execUpdateCreditScores(allCids, lastPeriod);
(uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) =
_distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids);
_settleAndTransferDelegatingRewards(
lastPeriod, allCids, delegatorBlockMiningRewards, delegatorFastFinalityRewards
);
_tryRecycleLockedFundsFromEmergencyExits();
_recycleDeprecatedRewards();
address[] memory revokedCandidateIds = _syncCandidateSet(newPeriod);
if (revokedCandidateIds.length > 0) {
// Re-update `allCids` after unsatisfied candidates get removed.
allCids = _candidateIds;
slashIndicatorContract.execResetCreditScores(revokedCandidateIds);
}
// Wrap up the beacon period includes (1) finalizing the beacon proof, and (2) determining the validator list for the next period by new proof.
// Should wrap up the beacon after unsatisfied candidates get removed.
randomBeacon.execFinalizeBeaconAndPendingCids(lastPeriod, newPeriod, allCids);
_periodEndBlock[lastPeriod] = block.number;
_currentPeriodStartAtBlock = block.number + 1;
}
// Clear the previous validator set and block producer set before sync the new set from beacon.
_clearPreviousValidatorSetAndBlockProducerSet();
// Query the new validator set for upcoming epoch from the random beacon contract.
// Save new set into the contract storage.
address[] memory newValidatorIds = _syncValidatorSet(randomBeacon, newPeriod, nextEpoch);
// Activate applicable validators into the block producer set.
_updateApplicableValidatorToBlockProducerSet(newPeriod, nextEpoch, newValidatorIds);
emit WrappedUpEpoch(lastPeriod, epoch, periodEnding);
_periodOf[nextEpoch] = newPeriod;
_lastUpdatedPeriod = newPeriod;
}
}

function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch {
unchecked {
uint256 newPeriod = _computePeriod(block.timestamp);
bool periodEnding = _isPeriodEnding(newPeriod);
uint256 lastPeriod = currentPeriod();
uint256 epoch = epochOf(block.number);
uint256 nextEpoch = epoch + 1;
IRandomBeacon randomBeacon = IRandomBeacon(getContract(ContractType.RANDOM_BEACON));
// This request is actually only invoked at the first epoch of the period.
randomBeacon.execRequestRandomSeedForNextPeriod(lastPeriod, newPeriod);
// Get all candidate ids
address[] memory allCids = _candidateIds;
_syncFastFinalityReward({ epoch: epoch, validatorIds: allCids });
if (periodEnding) {
ISlashIndicator slashIndicatorContract = ISlashIndicator(getContract(ContractType.SLASH_INDICATOR));
// Slash submit random beacon proof unavailability first, then update credit scores.
randomBeacon.execRecordAndSlashUnavailability(lastPeriod, newPeriod, address(slashIndicatorContract), allCids);
slashIndicatorContract.execUpdateCreditScores(allCids, lastPeriod);
(uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) =
_distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids);
_settleAndTransferDelegatingRewards(
lastPeriod, allCids, delegatorBlockMiningRewards, delegatorFastFinalityRewards
);
_tryRecycleLockedFundsFromEmergencyExits();
_recycleDeprecatedRewards();
address[] memory revokedCandidateIds = _syncCandidateSet(newPeriod);
if (revokedCandidateIds.length > 0) {
// Re-update `allCids` after unsatisfied candidates get removed.
allCids = _candidateIds;
slashIndicatorContract.execResetCreditScores(revokedCandidateIds);
}
// Wrap up the beacon period includes (1) finalizing the beacon proof, and (2) determining the validator list for the next period by new proof.
// Should wrap up the beacon after unsatisfied candidates get removed.
randomBeacon.execFinalizeBeaconAndPendingCids(lastPeriod, newPeriod, allCids);
_periodEndBlock[lastPeriod] = block.number;
_currentPeriodStartAtBlock = block.number + 1;
}
// Clear the previous validator set and block producer set before sync the new set from beacon.
_clearPreviousValidatorSetAndBlockProducerSet();
// Query the new validator set for upcoming epoch from the random beacon contract.
// Save new set into the contract storage.
address[] memory newValidatorIds = _syncValidatorSet(randomBeacon, newPeriod, nextEpoch);
// Activate applicable validators into the block producer set.
_updateApplicableValidatorToBlockProducerSet(newPeriod, nextEpoch, newValidatorIds);
emit WrappedUpEpoch(lastPeriod, epoch, periodEnding);
_periodOf[nextEpoch] = newPeriod;
_lastUpdatedPeriod = newPeriod;
}
}

function wrapUpEpoch() external payable virtual override onlyCoinbase whenEpochEnding oncePerEpoch {
unchecked {
uint256 newPeriod = _computePeriod(block.timestamp);
bool periodEnding = _isPeriodEnding(newPeriod);
uint256 lastPeriod = currentPeriod();
uint256 epoch = epochOf(block.number);
uint256 nextEpoch = epoch + 1;
IRandomBeacon randomBeacon = IRandomBeacon(getContract(ContractType.RANDOM_BEACON));
// This request is actually only invoked at the first epoch of the period.
randomBeacon.execRequestRandomSeedForNextPeriod(lastPeriod, newPeriod);
// Get all candidate ids
address[] memory allCids = _candidateIds;
_syncFastFinalityReward({ epoch: epoch, validatorIds: allCids });
if (periodEnding) {
ISlashIndicator slashIndicatorContract = ISlashIndicator(getContract(ContractType.SLASH_INDICATOR));
// Slash submit random beacon proof unavailability first, then update credit scores.
randomBeacon.execRecordAndSlashUnavailability(lastPeriod, newPeriod, address(slashIndicatorContract), allCids);
slashIndicatorContract.execUpdateCreditScores(allCids, lastPeriod);
(uint256[] memory delegatorBlockMiningRewards, uint256[] memory delegatorFastFinalityRewards) =
_distributeRewardToTreasuriesAndCalculateTotalDelegatorsReward(lastPeriod, allCids);
_settleAndTransferDelegatingRewards(
lastPeriod, allCids, delegatorBlockMiningRewards, delegatorFastFinalityRewards
);
_tryRecycleLockedFundsFromEmergencyExits();
_recycleDeprecatedRewards();
address[] memory revokedCandidateIds = _syncCandidateSet(newPeriod);
if (revokedCandidateIds.length > 0) {
// Re-update `allCids` after unsatisfied candidates get removed.
allCids = _candidateIds;
slashIndicatorContract.execResetCreditScores(revokedCandidateIds);
}
// Wrap up the beacon period includes (1) finalizing the beacon proof, and (2) determining the validator list for the next period by new proof.
// Should wrap up the beacon after unsatisfied candidates get removed.
randomBeacon.execFinalizeBeaconAndPendingCids(lastPeriod, newPeriod, allCids);
_periodEndBlock[lastPeriod] = block.number;
_currentPeriodStartAtBlock = block.number + 1;
}
// Clear the previous validator set and block producer set before sync the new set from beacon.
_clearPreviousValidatorSetAndBlockProducerSet();
// Query the new validator set for upcoming epoch from the random beacon contract.
// Save new set into the contract storage.
address[] memory newValidatorIds = _syncValidatorSet(randomBeacon, newPeriod, nextEpoch);
// Activate applicable validators into the block producer set.
_updateApplicableValidatorToBlockProducerSet(newPeriod, nextEpoch, newValidatorIds);
emit WrappedUpEpoch(lastPeriod, epoch, periodEnding);
_periodOf[nextEpoch] = newPeriod;
_lastUpdatedPeriod = newPeriod;
}
}

function execDeprecatePools(
address[] calldata poolIds,
uint256 newPeriod
) external override onlyContract(ContractType.VALIDATOR) {
if (poolIds.length == 0) {
return;
}
for (uint256 i = 0; i < poolIds.length;) {
address poolId = poolIds[i];
PoolDetail storage _pool = _poolDetail[poolId];
// Deactivate the pool admin in the active mapping.
delete _adminOfActivePoolMapping[_pool.__shadowedPoolAdmin];
// Deduct and transfer the self staking amount to the pool admin.
uint256 deductingAmount = _pool.stakingAmount;
if (deductingAmount > 0) {
_deductStakingAmount(_pool, deductingAmount);
if (!_unsafeSendRONLimitGas(payable(_pool.__shadowedPoolAdmin), deductingAmount, DEFAULT_ADDITION_GAS)) {
emit StakingAmountTransferFailed(_pool.pid, _pool.__shadowedPoolAdmin, deductingAmount, address(this).balance);
}
}
// Settle the unclaimed reward and transfer to the pool admin.
uint256 lastRewardAmount = _claimReward(poolId, _pool.__shadowedPoolAdmin, newPeriod);
if (lastRewardAmount > 0) {
_unsafeSendRONLimitGas(payable(_pool.__shadowedPoolAdmin), lastRewardAmount, DEFAULT_ADDITION_GAS);
}
unchecked {
++i;
}
}
emit PoolsDeprecated(poolIds);
}

shadowing-state

Impact: High
Confidence: High

uint256[49] private ______gap;

uint256[50] private ______gap;

uninitialized-state

Impact: High
Confidence: High

uint256 internal _firstTrackedPeriodEnd;

mapping(bytes32 hash => ProposalInfo) internal _info;

uint256 internal _numberOfBlocksInEpoch;

mapping(uint256 epoch => uint256 period) internal _periodOf;

mapping(address => mapping(uint256 => bool)) internal _miningRewardDeprecatedAtPeriod;

uint256 internal _lastUpdatedPeriod;

mapping(address => uint256) internal _blockProducerJailedBlock;

mapping(address cid => bool isBlockProducer) internal _validatorMap;

mapping(uint256 period => uint256 endedAtBlock) internal _periodEndBlock;

mapping(uint256 idx => address cid) internal _validatorIds;

uint256 internal _currentPeriodStartAtBlock;

@TuDo1403 TuDo1403 force-pushed the implement-feature/basefee-treasury/impl branch from b381ba4 to 1c1ef24 Compare November 28, 2024 06:00
function _requireValidNonce(
uint256 nonce
) internal view {
require(nonce == getGlobalNonce(), ErrInvalidNonce(getGlobalNonce(), nonce));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fetch global nonce once

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since there will be no onchain failure, this could result in small bytecode increase but imo no gas will be saved even if we cache global nonce

* - `msg.sender` must be `proposal.executor`.
* - `hash` must be existed and passed (ProposalStatus must be Passed).
*/
function execute(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support bulk execution? Not sure if there are cases where multiple proposals need to be executed in single tx though

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may depend on proposal.executor so no.

Comment on lines 497 to 501
function _calcMinVoteAgainst(
uint256 totalVotePower
) internal view returns (uint96 minVoteAgainst) {
uint256 diff = _threshold.denom - _threshold.num;
minVoteAgainst = ((totalVotePower * diff) / _threshold.denom).toUint96();
require(minVoteAgainst != 0, ErrZeroMinVotePower());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be calculated by taking totalVotePower - _calcMinVoteFor. Because with your implementation, may _calcMinVoteFor + _calcMinVoteAgainst < totalVotePower, which leads to a situation where the proposal is canceled once the votes just meet _calcMinVoteAgainst, even though further voting could potentially exceed _calcMinVoteFor.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. To be more specific, let’s say the total vote power is 31 and the threshold is 0.5 (num = 1, denom = 2). Both _calcMinVoteAgainst and _calcMinVoteFor would return 15. Even if the current power of voteFor is 16, the proposal would still be canceled

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

acked. However, if it is actually come in this case on production, I still think it is okay if it is cancelled.

Comment on lines +374 to +377
require(
_computePeriod(profile.getId2RegisteredAt(cid) + COOLDOWN_PERIOD) <= _computePeriod($._createdAt),
ErrNewlyRegisteredCannotVote(cid)
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition prevents new candidates from voting on past proposals. Is it as expected?
Even if someone registers as a candidate and waits a day, he still can’t vote on past proposals.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As expected.

Copy link
Collaborator

@huyhuynh3103 huyhuynh3103 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to verify with protocol team whether adding balance to a contract without receive() function is possible at node layer

return false;
}

if ($._votePow.vFor < _calcMinVoteFor($._totalVotePow)) return false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in the case where totalVotePow = 101 and the threshold is 70%, if $._votePow.For = 70, it would not satisfy >= 101 * 70% = 70.7. To be precise, I believe the comparison should be whether $._votePow.vFor * denom >= totalVotePow * num. Similarly, for early proposal cancellation, we need to compare $._votePow.Against * denom > totalPow * (denom - num).

@TuDo1403 TuDo1403 force-pushed the implement-feature/basefee-treasury/impl branch from f0e83d2 to 8eedf8a Compare December 1, 2024 08:24
@TuDo1403 TuDo1403 merged commit 045ac33 into feature/basefee-treasury Dec 1, 2024
3 of 4 checks passed
@TuDo1403 TuDo1403 deleted the implement-feature/basefee-treasury/impl branch December 1, 2024 17:00
TuDo1403 added a commit that referenced this pull request Dec 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants