GeodeUtils Library

GeodeUtilsLib.sol Library is where the DAO operations of the Geode Universe are handled. This library is responsible for the administrative process of the Geode Universe, used in the Portal.sol. The most important aspect of this contract is where the 'Limited Upgradability' is implemented.

Please check the Key Components of the Geode Universe to have a better understanding of this contract and its roles, if you haven't already.

Also DataStoreLib.sol functions are being used in this file so you might also want to check this.

This library contains both functions called by users(ID) and admins(GOVERNANCE, SENATE )

Structs

To begin with, the structs will tell us a lot about the rest of the code. There are two types of structs in the GeodeUtilsLib.sol, which are, Universe and Proposal structs.

Universe Struct

struct Universe {
address SENATE;
address GOVERNANCE;
uint256 OPERATION_FEE;
uint256 MAX_OPERATION_FEE;
uint256 FEE_DENOMINATOR;
uint256 SENATE_EXPIRE_TIMESTAMP;
address approvedUpgrade;
uint256 _electorCount;
mapping(uint256 => bool) _electorTypes;
mapping(uint256 => Proposal) _proposalForId;
}
Quick Reminders:
GOVERNANCE is a community that works to improve the core product and ensures its adoption in the DeFi ecosystem
  • Suggests updates, such as new planets, operators, contract upgrades and new Senate, on the Ecosystem without any permissions to force them.
SENATE A multi-signature address that consists of privileged Members
  • controlling the state of governance, updates and other planets in the Geode Ecosystem.
  • Note that, the Senate is proposed by Governance and voted by all planets, if 2/3 approves.
OPERATION_FEE : The fee that Geode takes from the validator rewards. Planned to be 0 (zero) . limited by MAX_OPERATION_FEE.
MAX_OPERATION_FEE: The limiting factor on the OPERATION_FEE. Set by the Senate.
FEE_DENOMINATOR: ALL "fee" variables are limited by FEE_DENOMINATOR = 100%
SENATE_EXPIRE_TIMESTAMP: The Senate has the maximum operation date of 2 years. Which is determined by the:
uint32 public constant MAX_SENATE_PERIOD = 730 days; // 2 years
In every Senate election the variable is calculated by;
self.SENATE_EXPIRE_TIMESTAMP = block.timestamp + MAX_SENATE_PERIOD; // 2 years
approvedUpgrade: Variable to keep track of the last approval of a contract upgrade.
_electorTypes: only given types can vote. Currently only type(5) can vote, which represents Planets. And type(4) can not vote, which is Node Operators. To See types: DataStoreLib.sol.
_proposalForId: Mapping to reach Proposal from the ID. When an ID is claimed by/given to a Controller it can not be proposed again.

Proposal Struct

struct Proposal {
address CONTROLLER;
uint256 TYPE;
uint256 deadline;
bytes name;
}
name: unique parameter for proposals. id is created by keccak(name)
TYPE: Separates the proposals and related functionality between different ID types. There are 4 types reserved on GeodeUtilsLib which are:
  • TYPE 0 = Inactive
  • TYPE 1 = Senate: controls state of governance, contract updates and other members of A Universe.
  • TYPE 2 =Upgrade: address of the implementation for desired contract upgrade
CONTROLLER: the address that refers to the "change "that is proposed by given proposal ID. This change can refer giving the control of an ID to a user, a new implementation contract, a new Senate etc.
deadline: Deadline of the Proposal, refers to last timestamp until a proposal expires, Limited by MAX_PROPOSAL_DURATION = 1 weeks;
  • Expired proposals can not be approved by Senate
  • Expired proposals can be overridden by new proposals

Functions

Constraints

uint32 public constant MIN_PROPOSAL_DURATION = 1 days;
uint32 public constant MAX_PROPOSAL_DURATION = 1 weeks;
uint32 public constant MAX_SENATE_PERIOD = 730 days;
1) A proposal has 1 week time limit, if 1 week is exceeded the proposal becomes invalid. No need to veto a proposal.
2) Elected Senate's duty term is restricted by 730 days (2 years). After the 2 year period, a new Senate MUST be elected.

Modifiers

modifier onlySenate(Universe storage self) {
require(msg.sender == self.SENATE, "GeodeUtils: SENATE role needed");
require(
block.timestamp < self.SENATE_EXPIRE_TIMESTAMP,
"GeodeUtils: SENATE not active"
);
_;
}
Basically these are prior requirements to be met for calling specific functions. Names are self-explanatory.
There is no onlyGovernance modifier here. Governance is not an entity that has any control over this Library, but there can be some limitations about the mentioned functions within the Implementation of Portal.sol.

Proposals

To learn the specifics of a given proposal:
Portal.getProposal(id);

newProposal

function newProposal(
...
address _CONTROLLER,
uint256 _type,
uint256 _duration,
bytes calldata _name
) external {
require(_duration < MAX_PROPOSAL_DURATION, "GeodeUtils: duration exceeds");
uint256 id = uint256(keccak256(abi.encodePacked(_name)));
require(
self._proposalForId[id].deadline < block.timestamp,
"GeodeUtils: name already proposed"
);
self._proposalForId[id] = Proposal({
CONTROLLER: _CONTROLLER,
TYPE: _type,
deadline: block.timestamp + _duration,
name: _name
});
}
This function is self-explanatory, creates a new proposal, checks if the proposal duration is still valid. It then checks if the proposal has already been proposed, by checking the block.timestamp.
If all the requirements above are met by this part of the function it sets the proposal to the datastore with the given arguments.
Additional Information:
To ensure the flexibility of Governance-less upgrades in the future, anyone can create a Proposal.
A proposal can be overridden if: expired OR approved. DATASTORE(id) will not be overridden until the proposal is approved.

approveProposal

function approveProposal(
...
uint256 id
) external onlySenate(self) {
require(
self._proposalForId[id].deadline >= block.timestamp,
"GeodeUtils: proposal expired"
);
require(
self._proposalForId[id].TYPE != 1,
"GeodeUtils: Senate can not approve Senate Proposal"
);
_DATASTORE.writeBytesForId(id, "name", self._proposalForId[id].name);
_DATASTORE.writeAddressForId(
id,
"CONTROLLER",
self._proposalForId[id].CONTROLLER
);
_DATASTORE.writeUintForId(id, "TYPE", self._proposalForId[id].TYPE);
_DATASTORE.allIdsByType[self._proposalForId[id].TYPE].push(id);
if (self._proposalForId[id].TYPE == 2) {
self.approvedUpgrade = self._proposalForId[id].CONTROLLER;
}
self._proposalForId[id].deadline = block.timestamp;
if (self._electorTypes[_DATASTORE.readUintForId(id, "TYPE")])
self._electorCount += 1;
emit ProposalApproved(id);
}
When a proposal is sent, the last step would be the Senate Approval. However when Governance propose for a new Senate, the Senate should not be the decision maker for the Senate election.
The other requirement for a proposal is to approve it's deadline. Approvals check if the pre-determined deadline for a proposal with the respective 'id' is still valid.
If the requirements are met then the write operations for the Proposal kicks in and specifics for the proposal are written to the dynamic storage.(Check DataStoreLib.sol) .
The crucial part in this function is to separate the type for the proposal. So the rest of the function basically checks the type and acts accordingly. Type specific changes for reserved_types(1,2,3) are implemented here, * any other addition should take place in Portal, as not related

Senate Proposal Functions

setElectorType

function setElectorType(
...
uint256 _type,
bool _isElector
){
require(
self._electorTypes[_type] != _isElector,
"GeodeUtils: type already _isElector"
);
require(
_type != 0 && _type != 1 && _type != 2 && _type != 3,
"GeodeUtils: 0, Senate, Upgrade, ProxyAdmin cannot be elector!"
);
self._electorTypes[_type] = _isElector;
if (_isElector) {
self._electorCount += _DATASTORE.allIdsByType[_type].length;
} else {
self._electorCount -= _DATASTORE.allIdsByType[_type].length;
}
emit NewElectorType(_type);
}
The name of the function is self-explanatory. The main operation of this function is to assign which type of addresses can vote for the Senate proposal to elect the new Senate. Basically this function sets a Type as an elector for the Senate Proposals. Also keeps the state of the elector count, and stores it.
_type parameter indicates to the type of the elector.
_isElector parameter is a Boolean. If true, selected type can vote for the Senate.

approveSenate

function approveSenate(
...
uint256 proposalId,
uint256 electorId
){
require(
self._proposalForId[proposalId].TYPE == 1,
"GeodeUtils: NOT Senate Proposal"
);
require(
self._proposalForId[proposalId].deadline >= block.timestamp,
"GeodeUtils: proposal expired"
);
require(
_DATASTORE.readAddressForId(electorId, "CONTROLLER") == msg.sender,
"GeodeUtils: msg.sender should be CONTROLLER of given electorId!"
);
require(
self._electorTypes[_DATASTORE.readUintForId(electorId, "TYPE")],
"GeodeUtils: NOT an elector"
);
require(
_DATASTORE.readUintForId(
proposalId,
bytes32(keccak256(abi.encodePacked(electorId, "voted")))
) == 0,
" GeodeUtils: already approved"
);
_DATASTORE.writeUintForId(
proposalId,
bytes32(keccak256(abi.encodePacked(electorId, "voted"))),
1
);
emit Vote(proposalId, electorId);
_DATASTORE.writeUintForId(
proposalId,
"approvalCount",
_DATASTORE.readUintForId(proposalId, "approvalCount") + 1
);
if (
_DATASTORE.readUintForId(proposalId, "approvalCount") >=
((self._electorCount + 1) * 2) / 3
) {
self.SENATE = self._proposalForId[proposalId].CONTROLLER;
self._proposalForId[proposalId].deadline = block.timestamp;
self.SENATE_EXPIRE_TIMESTAMP = block.timestamp + MAX_SENATE_PERIOD; // 2 years
emit NewSenate(self.SENATE, self.SENATE_EXPIRE_TIMESTAMP);
}
}
The approveSenate function is where senate election functionalities are implemented. The critical functionality here is the new Senate cannot be approved, unless the approval count for the new Senate exceeds the count of 2/3 of all electors. So for a new Senate to be elected there should be at least 4 electors ( _DATASTORE.readUintForId(proposalId, "approvalCount">= ((self._electorCount + 1) * 2) / 3) ) for this code to work properly.
This function takes 4 arguments, Universe storage self, is being used to reach, to Universe Struct,
DataStoreUtils.DataStore storage _DATASTORE is being initiated to use datastore functionalities (read and write ). Please see DataStoreLib.sol
From the proposalId,we can reach the specifics of the proposal and check the requirements such as, proposal type, and the deadline. This argument is being used throughout the function. Read and write operations on the proposal is done through the proposalId.
From the ElectorId, first it is being checked if the electorId has the CONTROLLER role then the second check is, if the given electorId has the right type for the election. And the last one checks for if the electorId at issue has already voted or not.
If all of the requirements are met, then the ApprovalCount is the current state of the approvalCount is read from the datastore and incremented by 1, and written to the DataStore. Just before that the state of the ElectorID is set to "voted" so eliminating double voting.
THEN, the last piece of code:
if ( _DATASTORE.readUintForId(proposalId, "approvalCount") >=
((self._electorCount + 1) * 2) / 3
) {
self.SENATE = self._proposalForId[proposalId].CONTROLLER;
self._proposalForId[proposalId].deadline = block.timestamp;
self.SENATE_EXPIRE_TIMESTAMP = block.timestamp + MAX_SENATE_PERIOD; // 2 years
return;
Checks for if the approvalCount exceeds the total number of electors, and if so, the new Senate is elected. Then the necessary assignment operations take place for the Senate.
Copy link
On this page
Structs
Universe Struct
Proposal Struct
Functions
Modifiers
Proposals
newProposal
approveProposal
Senate Proposal Functions
setElectorType
approveSenate