Skip to content

Commit dd3d1e5

Browse files
txhslbelane
andauthored
rebase master (#220)
* index some event parameters * update solidity compiler version * systemcontract: governance unit tests (#210) * update gitignore * init hardhat project * first ut * systemcontract: add candidate management ut and getter check (#211) * systemcontract: use customized error library (#209) * systemcontract: use customized error library * update existing ut * systemcontract: add policy unit tests (#214) * systemcontract: add ut about reward distribution and fix several ut (#215) * systemcontract: optimize gas cost (#195) * systemcontract: optimize gas cost --------- Co-authored-by: belane <[email protected]>
1 parent ca856ce commit dd3d1e5

18 files changed

+1311
-158
lines changed

.gitignore

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,16 @@ logs/
5353
/privnet/**/*.log
5454
/privnet/**/geth/
5555

56-
tests/spec-tests/
56+
tests/spec-tests/
57+
58+
# contracts dev
59+
node_modules
60+
.env
61+
/contracts/cache
62+
/contracts/artifacts
63+
/contracts/typechain
64+
/contracts/typechain-types
65+
/contracts/coverage
66+
/contracts/coverage.json
67+
/contracts/ignition
68+
/contracts/package-lock.json

contracts/hardhat.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { HardhatUserConfig } from "hardhat/config";
2+
import "@nomicfoundation/hardhat-toolbox";
3+
4+
const config: HardhatUserConfig = {
5+
solidity: {
6+
version: "0.8.25",
7+
settings: {
8+
evmVersion: "paris",
9+
optimizer: {
10+
enabled: true,
11+
runs: 500,
12+
},
13+
},
14+
},
15+
paths: {
16+
sources: "./solidity",
17+
tests: "./test",
18+
}
19+
};
20+
21+
export default config;

contracts/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "neox-governance",
3+
"scripts": {
4+
"compile": "npx hardhat compile",
5+
"test": "npx hardhat test"
6+
},
7+
"devDependencies": {
8+
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
9+
"hardhat": "^2.22.3"
10+
},
11+
"dependencies": {
12+
"@openzeppelin/contracts-upgradeable": "^5.0.2"
13+
}
14+
}

contracts/solidity/ERC1967Proxy.sol

Lines changed: 0 additions & 46 deletions
This file was deleted.

contracts/solidity/Errors.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.25;
3+
4+
library Errors {
5+
// Universal Errors
6+
error NotAdmin();
7+
error NotGovernance();
8+
9+
// GovReward Errors
10+
error TransferFailed();
11+
12+
// GovernanceVote Errors
13+
error NotMiner();
14+
15+
// Policy Errors
16+
error BlacklistExists();
17+
error BlacklistNotExists();
18+
error InvalidMinGasTipCap();
19+
error InvalidBaseFee();
20+
21+
// Governance Errors
22+
error SideCallNotAllowed();
23+
error OnlyEOA();
24+
error InsufficientValue();
25+
error InvalidShareRate();
26+
error CandidateExists();
27+
error CandidateNotExists();
28+
error LeftNotClaimed();
29+
error CandidateWithdrawNotAllowed();
30+
error MultipleVoteNotAllowed();
31+
error NoVote();
32+
}

contracts/solidity/GovProxyAdmin.sol

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.20;
2+
pragma solidity ^0.8.25;
33

44
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
55
import "./GovernanceVote.sol";
@@ -27,8 +27,10 @@ contract GovProxyAdmin is GovernanceVote {
2727
payable
2828
virtual
2929
needVote(
30-
keccak256("upgradeAndCall"),
31-
keccak256(abi.encode(proxy, newImplementation, data))
30+
bytes32(
31+
0xe739b9109d83c1c6d0d640fe9ed476fc5862a6de5483b00678a3fffa7a2be2f6
32+
),
33+
keccak256(abi.encodePacked(proxy, newImplementation, data))
3234
)
3335
{
3436
proxy.upgradeToAndCall{value: msg.value}(newImplementation, data);

contracts/solidity/GovReward.sol

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.20;
2+
pragma solidity ^0.8.25;
33

4+
import "./Errors.sol";
45
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
56

6-
library TransferHelper {
7-
function safeTransfer(address token, address to, uint256 value) internal {
8-
// bytes4(keccak256(bytes('transfer(address,uint256)')));
9-
(bool success, bytes memory data) = token.call(
10-
abi.encodeWithSelector(0xa9059cbb, to, value)
11-
);
12-
require(
13-
success && (data.length == 0 || abi.decode(data, (bool))),
14-
"safeTransfer: transfer failed"
15-
);
16-
}
17-
18-
function safeTransferETH(address to, uint256 value) internal {
19-
(bool success, ) = to.call{value: value}(new bytes(0));
20-
require(success, "safeTransferETH: ETH transfer failed");
21-
}
22-
}
23-
247
interface IGovernance {
258
// get current consensus group
269
function getCurrentConsensus() external view returns (address[] memory);
@@ -42,12 +25,12 @@ contract GovReward is IGovReward, UUPSUpgradeable {
4225
receive() external payable {}
4326

4427
modifier onlyGov() {
45-
require(msg.sender == GOV, "not governance");
28+
if (msg.sender != GOV) revert Errors.NotGovernance();
4629
_;
4730
}
4831

4932
modifier onlyAdmin() {
50-
require(msg.sender == GOV_ADMIN, "not admin");
33+
if (msg.sender != GOV_ADMIN) revert Errors.NotAdmin();
5134
_;
5235
}
5336

@@ -81,7 +64,12 @@ contract GovReward is IGovReward, UUPSUpgradeable {
8164

8265
function withdraw() external onlyGov {
8366
if (address(this).balance > 0) {
84-
TransferHelper.safeTransferETH(GOV, address(this).balance);
67+
_safeTransferETH(GOV, address(this).balance);
8568
}
8669
}
70+
71+
function _safeTransferETH(address to, uint value) internal {
72+
(bool success, ) = to.call{value: value}(new bytes(0));
73+
if (!success) revert Errors.TransferFailed();
74+
}
8775
}

contracts/solidity/Governance.sol

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.20;
2+
pragma solidity ^0.8.25;
33

4+
import "./Errors.sol";
45
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
56
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
67
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
78

89
interface IGovernance {
910
event Register(address candidate);
1011
event Exit(address candidate);
11-
event Vote(address voter, address to, uint amount);
12-
event Revoke(address voter, address from, uint amount);
13-
event VoterClaim(address voter, uint reward);
12+
event Vote(address indexed voter, address indexed to, uint amount);
13+
event Revoke(address indexed voter, address indexed from, uint amount);
14+
event VoterClaim(address indexed voter, uint reward);
1415
event CandidateWithdraw(address candidate, uint amount);
1516
event Persist(address[] validators);
1617

@@ -106,7 +107,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
106107
mapping(address => mapping(uint => uint)) public epochStartGasPerVote;
107108

108109
modifier onlyAdmin() {
109-
require(msg.sender == GOV_ADMIN, "not admin");
110+
if (msg.sender != GOV_ADMIN) revert Errors.NotAdmin();
110111
_;
111112
}
112113

@@ -135,7 +136,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
135136
}
136137

137138
receive() external payable nonReentrant {
138-
require(msg.sender == GOV_REWARD, "side call not allowed");
139+
if (msg.sender != GOV_REWARD) revert Errors.SideCallNotAllowed();
139140
address[] memory validators = currentConsensus;
140141
uint length = validators.length;
141142
for (uint i = 0; i < length; i++) {
@@ -160,12 +161,11 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
160161
}
161162

162163
function registerCandidate(uint shareRate) external payable {
163-
require(tx.origin == msg.sender, "only allow EOA");
164-
require(msg.value >= registerFee, "insufficient amount");
165-
require(shareRate < 1000, "invalid rate");
166-
require(!candidateList.contains(msg.sender), "candidate exists");
167-
require(exitHeightOf[msg.sender] == 0, "left not claimed");
168-
candidateList.add(msg.sender);
164+
if (tx.origin != msg.sender) revert Errors.OnlyEOA();
165+
if (msg.value < registerFee) revert Errors.InsufficientValue();
166+
if (shareRate > 1000) revert Errors.InvalidShareRate();
167+
if (exitHeightOf[msg.sender] > 0) revert Errors.LeftNotClaimed();
168+
if (!candidateList.add(msg.sender)) revert Errors.CandidateExists();
169169
if (receivedVotes[msg.sender] > 0) {
170170
totalVotes += receivedVotes[msg.sender];
171171
}
@@ -177,9 +177,9 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
177177
}
178178

179179
function exitCandidate() external {
180-
require(candidateList.contains(msg.sender), "candidate not exists");
180+
if (!candidateList.remove(msg.sender))
181+
revert Errors.CandidateNotExists();
181182
// remove candidate list, balance still locked
182-
candidateList.remove(msg.sender);
183183
exitHeightOf[msg.sender] = block.number;
184184
if (receivedVotes[msg.sender] > 0) {
185185
totalVotes -= receivedVotes[msg.sender];
@@ -190,11 +190,10 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
190190
function withdrawRegisterFee() external nonReentrant {
191191
// require 2 epochs to exit candidate list
192192
// NOTE: suppose epoch change always happens in time
193-
require(
194-
exitHeightOf[msg.sender] > 0 &&
195-
block.number > exitHeightOf[msg.sender] + 2 * epochDuration,
196-
"withdraw not allowed"
197-
);
193+
if (
194+
exitHeightOf[msg.sender] <= 0 ||
195+
block.number <= exitHeightOf[msg.sender] + 2 * epochDuration
196+
) revert Errors.CandidateWithdrawNotAllowed();
198197

199198
// send back balance
200199
uint amount = candidateBalanceOf[msg.sender];
@@ -207,13 +206,12 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
207206
}
208207

209208
function vote(address candidateTo) external payable nonReentrant {
210-
require(msg.value >= minVoteAmount, "insufficient amount");
211-
require(candidateList.contains(candidateTo), "candidate not allowed");
209+
if (msg.value < minVoteAmount) revert Errors.InsufficientValue();
210+
if (!candidateList.contains(candidateTo))
211+
revert Errors.CandidateNotExists();
212212
address votedCandidate = votedTo[msg.sender];
213-
require(
214-
votedCandidate == candidateTo || votedCandidate == address(0),
215-
"only one choice is allowed"
216-
);
213+
if (votedCandidate != candidateTo && votedCandidate != address(0))
214+
revert Errors.MultipleVoteNotAllowed();
217215

218216
// settle reward here
219217
uint unclaimedReward = 0;
@@ -241,10 +239,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
241239
function revokeVote() external nonReentrant {
242240
address candidateFrom = votedTo[msg.sender];
243241
uint amount = votedAmount[msg.sender];
244-
require(
245-
candidateFrom != address(0) && amount > 0,
246-
"revoke not allowed"
247-
);
242+
if (candidateFrom == address(0) || amount <= 0) revert Errors.NoVote();
248243

249244
// settle reward here
250245
uint unclaimedReward = _settleReward(msg.sender, candidateFrom);
@@ -290,7 +285,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
290285

291286
function claimReward() external nonReentrant {
292287
address votedCandidate = votedTo[msg.sender];
293-
require(votedCandidate != address(0), "claim not allowed");
288+
if (votedCandidate == address(0)) revert Errors.NoVote();
294289
uint unclaimedReward = _settleReward(msg.sender, votedCandidate);
295290
if (unclaimedReward > 0) _safeTransferETH(msg.sender, unclaimedReward);
296291
}
@@ -303,7 +298,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
303298

304299
function onPersist() external {
305300
// NOTE: suppose onPersist always happens at the beginning of every block
306-
require(msg.sender == SYS_CALL, "side call not allowed");
301+
if (msg.sender != SYS_CALL) revert Errors.SideCallNotAllowed();
307302
// only settle validator reward if there is no epoch change
308303
IGovReward(GOV_REWARD).withdraw();
309304
if (block.number < currentEpochStartHeight + epochDuration) return;
@@ -322,7 +317,7 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
322317
if (length < consensusSize || totalVotes < voteTargetAmount) {
323318
currentConsensus = standByValidators;
324319
} else {
325-
currentConsensus = _computeConsensus();
320+
currentConsensus = _computeConsensus(candidates);
326321
}
327322
emit Persist(currentConsensus);
328323
}
@@ -337,9 +332,9 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
337332
) internal view returns (uint) {
338333
// NOTE: suppose onPersist always happens at the beginning of every block, then latestGasPerVote is always the latest
339334
uint height = voteHeight[voter];
335+
if (currentEpochStartHeight <= height) return 0;
340336
uint lastGasPerVote = voterGasPerVote[voter];
341337
uint latestGasPerVote = candidateGasPerVote[candidate];
342-
if (currentEpochStartHeight <= height) return 0;
343338

344339
// NOTE: suppose epoch change always happens at the beginning of a block, then vote in that block should wait another epoch to farm reward
345340
uint voteEpochEndGasPerVote = epochStartGasPerVote[candidate][
@@ -366,12 +361,13 @@ contract Governance is IGovernance, ReentrancyGuard, UUPSUpgradeable {
366361

367362
function _safeTransferETH(address to, uint value) internal {
368363
(bool success, ) = to.call{value: value}(new bytes(0));
369-
require(success, "safeTransferETH: ETH transfer failed");
364+
if (!success) revert Errors.TransferFailed();
370365
}
371366

372-
function _computeConsensus() internal view returns (address[] memory) {
367+
function _computeConsensus(
368+
address[] memory candidates
369+
) internal view returns (address[] memory) {
373370
// build up a votes array
374-
address[] memory candidates = getCandidates();
375371
uint length = candidates.length;
376372
uint[] memory votes = new uint[](length);
377373
for (uint i = 0; i < length; i++) {

0 commit comments

Comments
 (0)