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

Add satellites NFT distribution #1787

Merged
merged 4 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface IElectionInspectorModule {
/// @notice Returns if user has voted in the given election
function hasVotedInEpoch(
address user,
uint precinct,
uint chainId,
uint epochIndex
) external view returns (bool);

Expand Down
9 changes: 4 additions & 5 deletions protocol/governance/contracts/interfaces/IElectionModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {Epoch} from "../storage/Epoch.sol";

/// @title Module for electing a council, represented by a set of NFT holders
interface IElectionModule {
error NotMothership();
error AlreadyNominated();
error ElectionAlreadyEvaluated();
error ElectionNotEvaluated();
Expand All @@ -24,7 +23,7 @@ interface IElectionModule {
event NominationWithdrawn(address indexed candidate, uint256 indexed epochId);
event VoteRecorded(
address indexed voter,
uint256 indexed precinct,
uint256 indexed chainId,
uint256 indexed epochId,
uint256 votingPower
);
Expand Down Expand Up @@ -124,19 +123,19 @@ interface IElectionModule {
function getNominees() external view returns (address[] memory);

/// @notice Returns if user has voted in the current election
function hasVoted(address user, uint256 precinct) external view returns (bool);
function hasVoted(address user, uint256 chainId) external view returns (bool);

/// @notice Returns the vote power of user in the current election
function getVotePower(
address user,
uint256 precinct,
uint256 chainId,
uint256 electionId
) external view returns (uint);

/// @notice Returns the list of candidates that a particular ballot has
function getBallotCandidates(
address voter,
uint256 precinct,
uint256 chainId,
uint256 electionId
) external view returns (address[] memory);

Expand Down
82 changes: 49 additions & 33 deletions protocol/governance/contracts/modules/core/BaseElectionModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,6 @@ contract BaseElectionModule is

uint256 private constant _CROSSCHAIN_GAS_LIMIT = 100000;

/// @dev Used to allow certain functions to only be executed on the "mothership" chain.
/// The mothership is considered to be the first chain id in the supported networks list.
modifier onlyMothership() {
if (CrossChain.load().getSupportedNetworks()[0] != block.chainid.to64()) {
revert NotMothership();
}

_;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed this in favor of CrossChain.onlyOnChainAt(0); helper fn


function initOrUpdateElectionSettings(
address[] memory initialCouncil,
uint8 minimumActiveMembers,
Expand All @@ -49,6 +39,7 @@ contract BaseElectionModule is
uint64 nominationPeriodDuration,
uint64 votingPeriodDuration
) external override {
// TODO: initialization should be called only on mothership and broadcasted?
OwnableStorage.onlyOwner();

_initOrUpdateElectionSettings(
Expand Down Expand Up @@ -153,6 +144,8 @@ contract BaseElectionModule is
uint64 newVotingPeriodStartDate,
uint64 newEpochEndDate
) external override {
// TODO: onlyOnMothership?

OwnableStorage.onlyOwner();
Council.onlyInPeriod(Council.ElectionPeriod.Administration);
Council.Data storage council = Council.load();
Expand Down Expand Up @@ -180,6 +173,8 @@ contract BaseElectionModule is
uint64 votingPeriodDuration,
uint64 maxDateAdjustmentTolerance
) external override {
// TODO: onlyOnMothership?

OwnableStorage.onlyOwner();
Council.onlyInPeriod(Council.ElectionPeriod.Administration);

Expand All @@ -194,6 +189,8 @@ contract BaseElectionModule is
}

function dismissMembers(address[] calldata membersToDismiss) external override {
// TODO: onlyOnMothership?

OwnableStorage.onlyOwner();

Council.Data storage store = Council.load();
Expand All @@ -219,19 +216,22 @@ contract BaseElectionModule is
}

function nominate() public virtual override {
// TODO: onlyOnMothership?
Copy link
Member

Choose a reason for hiding this comment

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

i am in favor of keeping it simple and ensuring nominate is only callable on the mothership


Council.onlyInPeriods(Council.ElectionPeriod.Nomination, Council.ElectionPeriod.Vote);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Enable calling nominate() on Voting period


SetUtil.AddressSet storage nominees = Council.load().getCurrentElection().nominees;
Council.onlyInPeriod(Council.ElectionPeriod.Nomination);

if (nominees.contains(msg.sender)) revert AlreadyNominated();

nominees.add(msg.sender);

emit CandidateNominated(msg.sender, Council.load().currentElectionId);

// TODO: add ballot id to emitted event
}

function withdrawNomination() external override {
// TODO: onlyOnMothership?

SetUtil.AddressSet storage nominees = Council.load().getCurrentElection().nominees;
Council.onlyInPeriod(Council.ElectionPeriod.Nomination);

Expand Down Expand Up @@ -276,17 +276,15 @@ contract BaseElectionModule is

CrossChain.Data storage cc = CrossChain.load();
cc.transmit(
cc.getSupportedNetworks()[0],
cc.getChainIdAt(0),
abi.encodeWithSelector(this._recvCast.selector, msg.sender, block.chainid, ballot),
_CROSSCHAIN_GAS_LIMIT
);
}

function _recvCast(
address voter,
uint256 precinct,
Ballot.Data calldata ballot
) external onlyMothership {
function _recvCast(address voter, uint256 chainId, Ballot.Data calldata ballot) external {
CrossChain.onlyOnChainAt(0);
CrossChain.onlyCrossChain();
Council.onlyInPeriod(Council.ElectionPeriod.Vote);

_validateCandidates(ballot.votedCandidates);
Expand All @@ -295,7 +293,7 @@ contract BaseElectionModule is
Election.Data storage election = council.getCurrentElection();
uint256 currentElectionId = council.currentElectionId;

Ballot.Data storage storedBallot = Ballot.load(currentElectionId, voter, precinct);
Ballot.Data storage storedBallot = Ballot.load(currentElectionId, voter, chainId);

storedBallot.copy(ballot);
storedBallot.validate();
Expand All @@ -307,11 +305,12 @@ contract BaseElectionModule is

election.ballotPtrs.push(ballotPtr);

emit VoteRecorded(msg.sender, precinct, currentElectionId, ballot.votingPower);
emit VoteRecorded(voter, chainId, currentElectionId, ballot.votingPower);
}

/// @dev ElectionTally needs to be extended to specify how votes are counted
function evaluate(uint numBallots) external override onlyMothership {
function evaluate(uint numBallots) external override {
CrossChain.onlyOnChainAt(0);
Council.onlyInPeriod(Council.ElectionPeriod.Evaluation);

Election.Data storage election = Council.load().getCurrentElection();
Expand All @@ -336,7 +335,8 @@ contract BaseElectionModule is
}

/// @dev Burns previous NFTs and mints new ones
function resolve() public virtual override onlyMothership {
function resolve() public virtual override {
CrossChain.onlyOnChainAt(0);
Council.onlyInPeriod(Council.ElectionPeriod.Evaluation);

Council.Data storage store = Council.load();
Expand All @@ -346,16 +346,32 @@ contract BaseElectionModule is

uint newEpochIndex = store.currentElectionId + 1;

CrossChain.Data storage cc = CrossChain.load();
cc.broadcast(
cc.getSupportedNetworks(),
abi.encodeWithSelector(
this._recvResolve.selector,
election.winners.values(),
newEpochIndex
),
_CROSSCHAIN_GAS_LIMIT
);
}

function _recvResolve(address[] calldata winners, uint256 newEpochIndex) external {
CrossChain.onlyOnChainAt(0);
Copy link
Member

Choose a reason for hiding this comment

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

I just noticed this line here: CrossChain.onlyOnChainAt(0);

I think this should be removed since _recvResolve needs to be called on every network in order to distribute the NFTs on each chain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perfect, will merge as it is, and create a separate PR with all the Mothership/Satellites exec validations

CrossChain.onlyCrossChain();

Council.Data storage store = Council.load();
Election.Data storage election = store.getCurrentElection();

_removeAllCouncilMembers(newEpochIndex);
_addCouncilMembers(election.winners.values(), newEpochIndex);
_addCouncilMembers(winners, newEpochIndex);

election.resolved = true;

store.newElection();

emit EpochStarted(newEpochIndex);

// TODO: Broadcast message to distribute the new NFTs on all chains
}

function getEpochSchedule() external view override returns (Epoch.Data memory epoch) {
Expand Down Expand Up @@ -397,27 +413,27 @@ contract BaseElectionModule is
return Council.load().getCurrentElection().nominees.values();
}

function hasVoted(address user, uint256 precinct) public view override returns (bool) {
function hasVoted(address user, uint256 chainId) public view override returns (bool) {
Council.Data storage council = Council.load();
Ballot.Data storage ballot = Ballot.load(council.currentElectionId, user, precinct);
Ballot.Data storage ballot = Ballot.load(council.currentElectionId, user, chainId);
return ballot.votingPower > 0 && ballot.votedCandidates.length > 0;
}

function getVotePower(
address user,
uint256 precinct,
uint256 chainId,
uint256 electionId
) external view override returns (uint) {
Ballot.Data storage ballot = Ballot.load(electionId, user, precinct);
Ballot.Data storage ballot = Ballot.load(electionId, user, chainId);
return ballot.votingPower;
}

function getBallotCandidates(
address voter,
uint256 precinct,
uint256 chainId,
uint256 electionId
) external view override returns (address[] memory) {
return Ballot.load(electionId, voter, precinct).votedCandidates;
return Ballot.load(electionId, voter, chainId).votedCandidates;
}

function isElectionEvaluated() public view override returns (bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,56 @@ contract ElectionInspectorModule is IElectionInspectorModule {
using SetUtil for SetUtil.AddressSet;
using Ballot for Ballot.Data;

function getEpochStartDateForIndex(uint epochIndex) external view override returns (uint64) {
function getEpochStartDateForIndex(uint256 epochIndex) external view override returns (uint64) {
return Election.load(epochIndex).epoch.startDate;
}

function getEpochEndDateForIndex(uint epochIndex) external view override returns (uint64) {
function getEpochEndDateForIndex(uint256 epochIndex) external view override returns (uint64) {
return Election.load(epochIndex).epoch.endDate;
}

function getNominationPeriodStartDateForIndex(
uint epochIndex
uint256 epochIndex
) external view override returns (uint64) {
return Election.load(epochIndex).epoch.nominationPeriodStartDate;
}

function getVotingPeriodStartDateForIndex(
uint epochIndex
uint256 epochIndex
) external view override returns (uint64) {
return Election.load(epochIndex).epoch.votingPeriodStartDate;
}

function wasNominated(
address candidate,
uint epochIndex
uint256 epochIndex
) external view override returns (bool) {
return Election.load(epochIndex).nominees.contains(candidate);
}

function getNomineesAtEpoch(uint epochIndex) external view override returns (address[] memory) {
function getNomineesAtEpoch(
uint256 epochIndex
) external view override returns (address[] memory) {
return Election.load(epochIndex).nominees.values();
}

function hasVotedInEpoch(
address user,
uint precinct,
uint epochIndex
uint256 chainId,
uint256 epochIndex
) external view override returns (bool) {
return Ballot.load(epochIndex, user, precinct).hasVoted();
return Ballot.load(epochIndex, user, chainId).hasVoted();
}

function getCandidateVotesInEpoch(
address candidate,
uint epochIndex
uint256 epochIndex
) external view override returns (uint) {
return Election.load(epochIndex).candidateVoteTotals[candidate];
}

function getElectionWinnersInEpoch(
uint epochIndex
uint256 epochIndex
) external view override returns (address[] memory) {
return Election.load(epochIndex).winners.values();
}
Expand Down
8 changes: 4 additions & 4 deletions protocol/governance/contracts/storage/Ballot.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.0;

/**
* @title Ballot
* @dev A single vote cast by a address/precinct combination.
* @dev A single vote cast by a address/chainId combination.
*
* A ballot goes through a few stages:
* 1. The ballot is empty and all values are 0
Expand All @@ -20,12 +20,12 @@ library Ballot {
}

function load(
uint electionId,
uint256 electionId,
address voter,
uint256 precinct
uint256 chainId
) internal pure returns (Data storage self) {
bytes32 s = keccak256(
abi.encode("io.synthetix.governance.Ballot", electionId, voter, precinct)
abi.encode("io.synthetix.governance.Ballot", electionId, voter, chainId)
);

assembly {
Expand Down
13 changes: 12 additions & 1 deletion protocol/governance/contracts/storage/Council.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ library Council {
// Council token id's by council member address
mapping(address => uint) councilTokenIds;
// id of the current epoch
uint currentElectionId;
uint256 currentElectionId;
}

enum ElectionPeriod {
Expand Down Expand Up @@ -115,6 +115,17 @@ library Council {
}
}

/// @dev Used to allow certain functions to only operate within a given periods
function onlyInPeriods(
Council.ElectionPeriod period1,
Council.ElectionPeriod period2
) internal view {
Council.ElectionPeriod currentPeriod = Council.getCurrentPeriod(load());
if (currentPeriod != period1 && currentPeriod != period2) {
revert NotCallableInCurrentPeriod();
}
}

/// @dev Ensures epoch dates are in the correct order, durations are above minimums, etc
function validateEpochSchedule(
Data storage self,
Expand Down
1 change: 1 addition & 0 deletions protocol/governance/contracts/storage/Election.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ library Election {
SetUtil.AddressSet winners;
// List of all ballot ids in this election
bytes32[] ballotPtrs;
// Total votes count for a given candidate
mapping(address => uint256) candidateVoteTotals;
}

Expand Down
Loading
Loading