Skip to content

Commit 232765e

Browse files
committed
Refactor contracts.
1 parent c75a703 commit 232765e

File tree

103 files changed

+16919
-13126
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+16919
-13126
lines changed
File renamed without changes.
File renamed without changes.

contracts/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# PowerVoting-Contract
2+
3+
## Overview
4+
The PowerVoting-Contract consists of two contracts:
5+
1. **Vote Contract**: Responsible for creating proposals and conducting proposal voting.
6+
2. **FipEditor Contract**: Handles permission management and supports adding and deleting FipEditors through proposals.
7+
8+
## Deployment and Upgrade Process
9+
10+
### Prerequisites
11+
- Node.js version v18.13.0 or higher.
12+
13+
### Installation
14+
Install the necessary libraries by running the following command:
15+
```bash
16+
npm install
17+
```
18+
19+
### Environment Variable Configuration
20+
1. Copy the `.env.example` file to `.env`:
21+
```bash
22+
cp .env.example .env
23+
```
24+
2. Configure the corresponding network keys in the `.env` file:
25+
- `PRIVATE_KEY_TESTNET`: The private key of the account for testnet deployment.
26+
- `PRIVATE_KEY_MAINNET`: The private key of the account for mainnet deployment.
27+
28+
### Contract Deployment
29+
1. **Deploy the FipEditor Contract**
30+
Run the following command to deploy the FipEditor contract:
31+
```bash
32+
npx hardhat run scripts/deploy_fip.ts --network [network_name]
33+
```
34+
2. **Deploy the Vote Contract**
35+
Run the following command to deploy the Vote contract:
36+
```bash
37+
npx hardhat run scripts/deploy_vote.ts --network [network_name]
38+
```
39+
3. **Contract Address Storage**
40+
After the contracts are deployed, the contract addresses will be saved in the `[network_name]_config.json` file in the `scripts` directory. The content format is as follows:
41+
```json
42+
{
43+
"POWER_VOTING_FIP": "",
44+
"POWER_VOTING_VOTE": ""
45+
}
46+
```
47+
48+
### Contract Upgrade
49+
The contracts support upgrade via the UUPS (Universal Upgradeable Proxy Standard) pattern.
50+
- **Upgrade the FipEditor Contract**
51+
Run the following command to upgrade the FipEditor contract:
52+
```bash
53+
npx hardhat run scripts/upgrade_fip.ts --network [network_name]
54+
```
55+
- **Upgrade the Vote Contract**
56+
Run the following command to upgrade the Vote contract:
57+
```bash
58+
npx hardhat run scripts/upgrade_vote.ts --network [network_name]
59+
```
60+
61+
## Notes
62+
- Replace `[network_name]` with the actual network name (e.g., `filecoin_testnet`, `filecoin_mainnet`) when running the deployment and upgrade commands.
63+
- Ensure that the private keys in the `.env` file are kept secure and not exposed.

contracts/contracts/FipEditor.sol

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright (C) 2023-2024 StorSwift Inc.
3+
// This file is part of the PowerVoting library.
4+
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at:
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
pragma solidity ^0.8.20;
17+
18+
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
19+
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
20+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
21+
import {IPowerVotingFipEditor} from "./interfaces/IPowerVoting-Fip.sol";
22+
import {FipEditorProposal, HasVoted, FipEditorProposalCreateInfo, FipEditorProposalVoteInfo} from "./types.sol";
23+
24+
contract PowerVotingFipEditor is
25+
Ownable2StepUpgradeable,
26+
UUPSUpgradeable,
27+
IPowerVotingFipEditor
28+
{
29+
// Define constants for proposal types
30+
int8 public constant PROPOSAL_TYPE_APPROVE = 1;
31+
int8 public constant PROPOSAL_TYPE_REVOKE = 0;
32+
//fipeditor status
33+
int8 public constant FITEITOR_STATUS_REVOKED = 0; //default status
34+
int8 public constant FITEITOR_STATUS_ADDING = 1;
35+
int8 public constant FITEITOR_STATUS_APPROVED = 2;
36+
int8 public constant FITEITOR_STATUS_REVOKING = 3;
37+
// Use EnumerableSet library to handle integers
38+
using EnumerableSet for EnumerableSet.UintSet;
39+
using EnumerableSet for EnumerableSet.AddressSet;
40+
41+
uint256 public candidateInfoMaxLength;
42+
mapping(address => int8) public fipEditorStatusMap;
43+
// fip editor proposal id
44+
uint256 public fipEditorProposalId;
45+
uint256 public fipEditorCount;
46+
// fip editor proposal mapping, key: fip editor proposal id, value: fip editor proposal
47+
mapping(uint256 => FipEditorProposal) idToFipEditorProposal;
48+
// Set to store IDs of proposals id
49+
EnumerableSet.UintSet proposalIdSet;
50+
51+
/**
52+
* @notice Initializes the contract by setting up UUPS upgrade ability and ownership.
53+
*/
54+
function initialize() public initializer {
55+
address sender = msg.sender;
56+
fipEditorCount = 1;
57+
fipEditorStatusMap[sender] = FITEITOR_STATUS_APPROVED;
58+
candidateInfoMaxLength = 1000;
59+
__UUPSUpgradeable_init();
60+
__Ownable_init(sender);
61+
}
62+
63+
/**
64+
* @dev Modifier that ensures the provided address is non-zero.
65+
* @param addr The address to check.
66+
*/
67+
modifier nonZeroAddress(address addr) {
68+
if (addr == address(0)) {
69+
revert ZeroAddressError();
70+
}
71+
_;
72+
}
73+
74+
/**
75+
* @notice Authorizes an upgrade to a new implementation contract.
76+
* @param newImplementation The address of the new implementation contract.
77+
*/
78+
function _authorizeUpgrade(
79+
address newImplementation
80+
) internal override onlyOwner {}
81+
82+
/**
83+
* @dev Modifier to allow only FIP editors to call the function.
84+
* Reverts with a custom error message if the caller is not a FIP editor.
85+
*/
86+
modifier onlyFIPEditors() {
87+
if (
88+
fipEditorStatusMap[msg.sender] != FITEITOR_STATUS_APPROVED &&
89+
fipEditorStatusMap[msg.sender] != FITEITOR_STATUS_REVOKING
90+
) {
91+
revert OnlyFipEditorsAllowedError();
92+
}
93+
_;
94+
}
95+
96+
/**
97+
* @notice update the length
98+
* @param _candidateInfoMaxLength max length of candidate info
99+
*/
100+
function setLengthLimits(
101+
uint256 _candidateInfoMaxLength
102+
) external override onlyFIPEditors {
103+
candidateInfoMaxLength = _candidateInfoMaxLength;
104+
}
105+
106+
/**
107+
* create a proposal to add or remove fipeditor
108+
* @param candidateAddress candidate address
109+
* @param candidateInfo candidate info
110+
* @param fipEditorProposalType fipeditor proposal type:PROPOSAL_TYPE_APPROVE or PROPOSAL_TYPE_REVOKE
111+
*/
112+
function createFipEditorProposal(
113+
address candidateAddress,
114+
string calldata candidateInfo,
115+
int8 fipEditorProposalType
116+
) external override onlyFIPEditors nonZeroAddress(candidateAddress) {
117+
// Ensure the proposal type is valid (must be 1 for approval or 0 for revocation)
118+
if (
119+
fipEditorProposalType != PROPOSAL_TYPE_APPROVE &&
120+
fipEditorProposalType != PROPOSAL_TYPE_REVOKE
121+
) {
122+
revert InvalidProposalTypeError();
123+
}
124+
// Ensure the proposer is not proposing themselves
125+
if (candidateAddress == msg.sender) {
126+
revert CannotProposeToSelfError();
127+
}
128+
if (bytes(candidateInfo).length > candidateInfoMaxLength) {
129+
revert CandidateInfoLimitError(candidateInfoMaxLength);
130+
}
131+
132+
int8 candidateFipEditorStatus = fipEditorStatusMap[candidateAddress];
133+
// Check if the address is already a FIP editor and the proposal is not to revoke (0)
134+
if (
135+
candidateFipEditorStatus == FITEITOR_STATUS_APPROVED &&
136+
fipEditorProposalType != PROPOSAL_TYPE_REVOKE
137+
) {
138+
revert AddressIsAlreadyFipEditorError();
139+
}
140+
141+
// Check if the address already has an active proposal
142+
if (
143+
candidateFipEditorStatus == FITEITOR_STATUS_ADDING ||
144+
candidateFipEditorStatus == FITEITOR_STATUS_REVOKING
145+
) {
146+
revert AddressHasActiveProposalError();
147+
}
148+
149+
// If the address is not an FIP editor, it cannot be revoked
150+
if (
151+
candidateFipEditorStatus != FITEITOR_STATUS_APPROVED &&
152+
fipEditorProposalType == PROPOSAL_TYPE_REVOKE
153+
) {
154+
revert AddressNotFipEditorError();
155+
}
156+
157+
// There must be at minimum two votes to revoke a FIP Editor.
158+
if (
159+
fipEditorProposalType == PROPOSAL_TYPE_REVOKE && fipEditorCount <= 2
160+
) {
161+
revert InsufficientEditorsError();
162+
}
163+
164+
++fipEditorProposalId;
165+
166+
//update FipEditor status
167+
if (fipEditorProposalType == PROPOSAL_TYPE_APPROVE) {
168+
fipEditorStatusMap[candidateAddress] = FITEITOR_STATUS_ADDING;
169+
} else {
170+
fipEditorStatusMap[candidateAddress] = FITEITOR_STATUS_REVOKING;
171+
}
172+
//keep active proposal
173+
proposalIdSet.add(fipEditorProposalId);
174+
// Create a new proposal and store it in the mapping
175+
FipEditorProposal storage proposal = idToFipEditorProposal[
176+
fipEditorProposalId
177+
];
178+
proposal.candidateAddress = candidateAddress;
179+
proposal.proposalId = fipEditorProposalId;
180+
proposal.proposalType = fipEditorProposalType;
181+
182+
// creating a proposal defaults to voting on the proposal
183+
voteFipEditorProposal(fipEditorProposalId);
184+
185+
//emit create event
186+
FipEditorProposalCreateInfo
187+
memory eventInfo = FipEditorProposalCreateInfo({
188+
proposalId: fipEditorProposalId,
189+
proposalType: fipEditorProposalType,
190+
creator: msg.sender,
191+
candidateInfo: candidateInfo,
192+
candidateAddress: candidateAddress
193+
});
194+
195+
emit FipEditorProposalCreateEvent(eventInfo);
196+
}
197+
198+
/**
199+
* vote on the proposal
200+
* @param proposalId proposal
201+
*/
202+
function voteFipEditorProposal(
203+
uint256 proposalId
204+
) public override onlyFIPEditors {
205+
if (!proposalIdSet.contains(proposalId)) {
206+
revert InvalidApprovalProposalId();
207+
}
208+
FipEditorProposal storage proposal = idToFipEditorProposal[proposalId];
209+
// Check if the sender has already voted for this proposal
210+
if (proposal.votedAddress[msg.sender]) {
211+
revert AddressHasActiveProposalError();
212+
}
213+
// Ensure the voter is not voting on their own proposal
214+
if (proposal.candidateAddress == msg.sender) {
215+
revert CannotVoteForOwnProposalError();
216+
}
217+
218+
proposal.voters.add(msg.sender);
219+
proposal.votedAddress[msg.sender] = true;
220+
221+
FipEditorProposalVoteInfo memory voteInfo = FipEditorProposalVoteInfo({
222+
voter: msg.sender,
223+
proposalId: proposalId
224+
});
225+
emit FipEditorProposalVoteEvent(voteInfo);
226+
227+
//check proposal result
228+
_checkProposalResult(proposalId);
229+
}
230+
231+
/**
232+
* Check the status of the proposal and calculate the result
233+
* @param proposalId proposal id
234+
*/
235+
function _checkProposalResult(uint256 proposalId) private {
236+
FipEditorProposal storage proposal = idToFipEditorProposal[proposalId];
237+
uint256 voterCount = proposal.voters.length();
238+
if (
239+
proposal.proposalType == PROPOSAL_TYPE_REVOKE &&
240+
voterCount == fipEditorCount - 1 &&
241+
fipEditorCount > 2
242+
) {
243+
address candidateAddress = proposal.candidateAddress;
244+
//update fipeditor count
245+
fipEditorCount--;
246+
//will delete proposal
247+
_processPassedProposal(
248+
proposalId,
249+
proposal.candidateAddress,
250+
FITEITOR_STATUS_REVOKED
251+
);
252+
//to pass a revoke proposal, need to review all proposals
253+
_cleanupProposals(candidateAddress);
254+
} else if (
255+
//everyone agreed
256+
proposal.proposalType == PROPOSAL_TYPE_APPROVE &&
257+
voterCount == fipEditorCount
258+
) {
259+
//update fipeditor count
260+
fipEditorCount++;
261+
262+
_processPassedProposal(
263+
proposalId,
264+
proposal.candidateAddress,
265+
FITEITOR_STATUS_APPROVED
266+
);
267+
}
268+
}
269+
270+
/**
271+
* After the proposal is passed, the corresponding status needs to be updated
272+
* @param proposalId proposal id
273+
* @param candidateAddress candidate address
274+
* @param status update status
275+
*/
276+
function _processPassedProposal(
277+
uint256 proposalId,
278+
address candidateAddress,
279+
int8 status
280+
) private {
281+
fipEditorStatusMap[candidateAddress] = status;
282+
proposalIdSet.remove(proposalId);
283+
delete idToFipEditorProposal[proposalId];
284+
emit FipEditorProposalPassedEvent(proposalId);
285+
}
286+
287+
/**
288+
* After the fipeditor is revoked, needs to clean up all his votes
289+
* @param candidateAddress address of the candidate
290+
*/
291+
function _cleanupProposals(address candidateAddress) private {
292+
uint256[] memory proposalIds = proposalIdSet.values();
293+
for (uint256 i = 0; i < proposalIds.length; i++) {
294+
uint256 id = proposalIds[i];
295+
FipEditorProposal storage proposal = idToFipEditorProposal[id];
296+
if (proposal.voters.contains(candidateAddress)) {
297+
proposal.votedAddress[msg.sender] = false;
298+
proposal.voters.remove(candidateAddress);
299+
}
300+
//After the voter or fipeditor changes, the proposal results should be rechecked
301+
_checkProposalResult(id);
302+
}
303+
}
304+
305+
function isFipEditor(address sender) external view override returns (bool) {
306+
return
307+
fipEditorStatusMap[sender] == FITEITOR_STATUS_APPROVED ||
308+
fipEditorStatusMap[sender] == FITEITOR_STATUS_REVOKING;
309+
}
310+
}

0 commit comments

Comments
 (0)