This contract sets up an account in the core Accounts contract, enabling it to lock CELO and vote in validator elections. The system's pool of CELO is held by this contract. This contract needs to be interacted with to lock/vote/activate votes, as assigned to validator groups according to Manager's strategy, and to finalize withdrawals of CELO, after the unlocking period of LockedGold has elapsed.
Deposits CELO sent via msg.value as unlocked CELO intended as votes for groups. Only callable by the Manager contract, which must restrict which groups are valid.
function scheduleVotes(address[] calldata groups, uint256[] calldata votes)
external
payable
onlyManager
{
if (groups.length != votes.length) {
revert GroupsAndVotesArrayLengthsMismatch();
}
uint256 totalVotes;
for (uint256 i = 0; i < groups.length; i++) {
scheduledVotes[groups[i]].toVote += votes[i];
totalVotes += votes[i];
emit VotesScheduled(groups[i], votes[i]);
}
if (totalVotes != uint256(msg.value)) {
revert TotalVotesMismatch(msg.value, totalVotes);
}
}
scheduleWithdrawals
Schedule a list of withdrawals to be refunded to a beneficiary.
Starts withdrawal of CELO from `group`. If there is any unlocked CELO for the group, that CELO is used for immediate withdrawal. Otherwise, CELO is taken from pending and active votes, which are subject to the unlock period of LockedGold.sol.
function withdraw(
address beneficiary,
address group,
address lesserAfterPendingRevoke,
address greaterAfterPendingRevoke,
address lesserAfterActiveRevoke,
address greaterAfterActiveRevoke,
uint256 index
) external returns (uint256) {
uint256 withdrawalAmount = scheduledVotes[group].toWithdrawFor[beneficiary];
if (withdrawalAmount == 0) {
revert NoScheduledWithdrawal(beneficiary, group);
}
// Emit early to return without needing to emit in multiple places.
emit CeloWithdrawalStarted(beneficiary, group, withdrawalAmount);
// Subtract withdrawal amount from all bookkeeping
scheduledVotes[group].toWithdrawFor[beneficiary] = 0;
scheduledVotes[group].toWithdraw -= withdrawalAmount;
totalScheduledWithdrawals -= withdrawalAmount;
uint256 immediateWithdrawalAmount = scheduledVotes[group].toVote;
if (immediateWithdrawalAmount > 0) {
if (immediateWithdrawalAmount > withdrawalAmount) {
immediateWithdrawalAmount = withdrawalAmount;
}
scheduledVotes[group].toVote -= immediateWithdrawalAmount;
// The benefit of using getGoldToken().transfer() rather than transferring
// using a message value is that the recepient's callback is not called, thus
// removing concern that a malicious beneficiary would control code at this point.
bool success = getGoldToken().transfer(beneficiary, immediateWithdrawalAmount);
if (!success) {
revert CeloTransferFailed(beneficiary, immediateWithdrawalAmount);
}
// If we've withdrawn the entire amount, return.
if (immediateWithdrawalAmount == withdrawalAmount) {
return immediateWithdrawalAmount;
}
}
// We know that withdrawalAmount is >= immediateWithdrawalAmount.
uint256 revokeAmount = withdrawalAmount - immediateWithdrawalAmount;
ILockedGold lockedGold = getLockedGold();
// Save the pending withdrawal for `beneficiary`.
pendingWithdrawals[beneficiary].push(
PendingWithdrawal(revokeAmount, block.timestamp + lockedGold.unlockingPeriod())
);
revokeVotes(
group,
revokeAmount,
lesserAfterPendingRevoke,
greaterAfterPendingRevoke,
lesserAfterActiveRevoke,
greaterAfterActiveRevoke,
index
);
lockedGold.unlock(revokeAmount);
return immediateWithdrawalAmount;
}
activateAndVote
Activates any activatable pending votes for group, and locks & votes any unlocked CELO for group. Callable by anyone. In practice, this is expected to be called near the end of each epoch by an off-chain agent.
function activateAndVote(
address group,
address voteLesser,
address voteGreater
) external {
IElection election = getElection();
// The amount of unlocked CELO for group that we want to lock and vote with.
uint256 unlockedCeloForGroup = scheduledVotes[group].toVote;
// Reset the unlocked CELO amount for group.
scheduledVotes[group].toVote = 0;
// If there are activatable pending votes from this contract for group, activate them.
if (election.hasActivatablePendingVotes(address(this), group)) {
// Revert if the activation fails.
if (!election.activate(group)) {
revert ActivatePendingVotesFailed(group);
}
}
// If there is no CELO to lock up and vote with, return.
if (unlockedCeloForGroup == 0) {
return;
}
// Lock up the unlockedCeloForGroup in LockedGold, which increments the
// non-voting LockedGold balance for this contract.
getLockedGold().lock{value: unlockedCeloForGroup}();
// Vote for group using the newly locked CELO, reverting if it fails.
if (!election.vote(group, unlockedCeloForGroup, voteLesser, voteGreater)) {
revert VoteFailed(group, unlockedCeloForGroup);
}
}
finishPendingWithdrawal
Finishes a pending withdrawal created as a result of a `Account.withdraw` call, claiming CELO after the `unlockingPeriod` defined in LockedGold.sol. Callable by anyone, but ultimately the withdrawal goes to `beneficiary`. The pending withdrawal info found in both Account.sol and LockedGold must match to ensure that the beneficiary is claiming the appropriate pending withdrawal.
function finishPendingWithdrawal(
address beneficiary,
uint256 localPendingWithdrawalIndex,
uint256 lockedGoldPendingWithdrawalIndex
) external returns (uint256 amount) {
(uint256 value, uint256 timestamp) = validatePendingWithdrawalRequest(
beneficiary,
localPendingWithdrawalIndex,
lockedGoldPendingWithdrawalIndex
);
// Remove the pending withdrawal.
PendingWithdrawal[] storage localPendingWithdrawals = pendingWithdrawals[beneficiary];
localPendingWithdrawals[localPendingWithdrawalIndex] = localPendingWithdrawals[
localPendingWithdrawals.length - 1
];
localPendingWithdrawals.pop();
// Process withdrawal.
getLockedGold().withdraw(lockedGoldPendingWithdrawalIndex);
/**
* The benefit of using getGoldToken().transfer() is that the recepients callback
* is not called thus removing concern that a malicious
* caller would control code at this point.
*/
bool success = getGoldToken().transfer(beneficiary, value);
if (!success) {
revert CeloTransferFailed(beneficiary, value);
}
emit CeloWithdrawalFinished(beneficiary, value, timestamp);
return value;
}
getTotalCelo
Gets the total amount of CELO this contract controls. This is the unlocked CELO balance of the contract plus the amount of LockedGold for this contract, which includes unvoting and voting LockedGold.
Returns the total amount of CELO this contract controls, including LockedGold.
function getTotalCelo() external view returns (uint256) {
// LockedGold's getAccountTotalLockedGold returns any non-voting locked gold +
// voting locked gold for each group the account is voting for, which is an
// O(# of groups voted for) operation.
return
address(this).balance +
getLockedGold().getAccountTotalLockedGold(address(this)) -
totalScheduledWithdrawals;
}
getPendingWithdrawals
Returns the pending withdrawals for a beneficiary.
function getPendingWithdrawals(address beneficiary)
external
view
returns (uint256[] memory values, uint256[] memory timestamps)
{
uint256 length = pendingWithdrawals[beneficiary].length;
values = new uint256[](length);
timestamps = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
PendingWithdrawal memory p = pendingWithdrawals[beneficiary][i];
values[i] = p.value;
timestamps[i] = p.timestamp;
}
return (values, timestamps);
}
getNumberPendingWithdrawals
Returns the number of pending withdrawals for a beneficiary.
Returns the total amount of CELO directed towards `group`. This is the Unlocked CELO balance for `group` plus the combined amount in pending and active votes made by this contract.
Revokes votes from a validator group. It first attempts to revoke pending votes, and then active votes if necessary. Reverts if `revokeAmount` exceeds the total number of pending and active votes for the group from this contract.