Skip to content

Commit a88db31

Browse files
committed
systemcontract: apply blacklist policy to governance election
1 parent cada98a commit a88db31

File tree

4 files changed

+83
-15
lines changed

4 files changed

+83
-15
lines changed

contracts/solidity/Governance.sol

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ contract Governance is IGovernance, ReentrancyGuard, GovProxyUpgradeable {
1414

1515
address public constant SELF = 0x1212100000000000000000000000000000000001;
1616
// Policy contract
17-
address public constant POLICY =
18-
0x1212000000000000000000000000000000000002;
17+
address public constant POLICY = 0x1212000000000000000000000000000000000002;
1918
// GovReward contract
2019
address public constant GOV_REWARD =
2120
0x1212000000000000000000000000000000000003;
@@ -132,14 +131,7 @@ contract Governance is IGovernance, ReentrancyGuard, GovProxyUpgradeable {
132131
}
133132

134133
function exitCandidate() external {
135-
if (!candidateList.remove(msg.sender))
136-
revert Errors.CandidateNotExists();
137-
// remove candidate list, balance still locked
138-
exitHeightOf[msg.sender] = block.number;
139-
if (receivedVotes[msg.sender] > 0) {
140-
totalVotes -= receivedVotes[msg.sender];
141-
}
142-
emit Exit(msg.sender);
134+
if (!_exitCandidate(msg.sender)) revert Errors.CandidateNotExists();
143135
}
144136

145137
function withdrawRegisterFee() external nonReentrant {
@@ -277,10 +269,26 @@ contract Governance is IGovernance, ReentrancyGuard, GovProxyUpgradeable {
277269
emit Persist(currentConsensus);
278270
}
279271

272+
function forceExitCandidate(address candidate) external {
273+
if (msg.sender != POLICY) revert Errors.SideCallNotAllowed();
274+
_exitCandidate(candidate);
275+
}
276+
280277
function getCurrentConsensus() public view returns (address[] memory) {
281278
return currentConsensus;
282279
}
283280

281+
function _exitCandidate(address candidate) internal returns (bool) {
282+
if (!candidateList.remove(candidate)) return false;
283+
// remove candidate list, balance still locked
284+
exitHeightOf[candidate] = block.number;
285+
if (receivedVotes[candidate] > 0) {
286+
totalVotes -= receivedVotes[candidate];
287+
}
288+
emit Exit(candidate);
289+
return true;
290+
}
291+
284292
function _computeReward(
285293
address voter,
286294
address candidate

contracts/solidity/Policy.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
5252
{
5353
if (isBlackListed[_addr]) revert Errors.BlacklistExists();
5454
isBlackListed[_addr] = true;
55+
IGovernance(GOV).forceExitCandidate(_addr);
5556
emit AddBlackList(_addr);
5657
}
5758

contracts/solidity/interfaces/IGovernance.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ interface IGovernance {
4040
// compute and update cached consensus group
4141
function onPersist() external;
4242

43+
// force exit a candidate in election
44+
function forceExitCandidate(address candidate) external;
45+
4346
// get consensus size
4447
function consensusSize() external view returns (uint);
4548
}

contracts/test/Governance.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const CANDIDATE_LIMIT = 2000;
3535

3636
describe("Governance", function () {
3737

38-
let Governance: any;
38+
let Governance: any, Policy: any;
3939
let user: any, candidate1: any, candidate2: any;
4040

4141
beforeEach(async function () {
@@ -59,8 +59,11 @@ describe("Governance", function () {
5959

6060
const policy_code = await ethers.provider.send("eth_getCode", [policy_deploy.target]);
6161
await ethers.provider.send("hardhat_setCode", [POLICY_PROXY, policy_code]);
62-
const contract = require("../artifacts/solidity/Governance.sol/Governance.json");
63-
Governance = new ethers.Contract(GOV_PROXY, contract.abi, user);
62+
63+
const governance_contract = require("../artifacts/solidity/Governance.sol/Governance.json");
64+
Governance = new ethers.Contract(GOV_PROXY, governance_contract.abi, user);
65+
const policy_contract = require("../artifacts/solidity/Policy.sol/Policy.json");
66+
Policy = new ethers.Contract(POLICY_PROXY, policy_contract.abi, user);
6467

6568
// Write Governance config to storage
6669
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x1", ethers.toBeHex(CONSENSUS_SIZE, 32)]);
@@ -86,7 +89,7 @@ describe("Governance", function () {
8689
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6c", ethers.toBeHex(STANDBY_VALIDATORS[4], 32)]);
8790
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6d", ethers.toBeHex(STANDBY_VALIDATORS[5], 32)]);
8891
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6e", ethers.toBeHex(STANDBY_VALIDATORS[6], 32)]);
89-
92+
9093
// Write Policy config to storage
9194
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x2", ethers.toBeHex(MIN_GAS_TIP_CAP, 32)]);
9295
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x3", ethers.toBeHex(BASE_FEE, 32)]);
@@ -399,6 +402,59 @@ describe("Governance", function () {
399402
await MockSysCall.call_onPersist(Governance)
400403
).to.changeEtherBalance(STANDBY_VALIDATORS[0], 1000000000000000000n);
401404
});
405+
406+
it("Should reject blacklisted addresses in election", async function () {
407+
let signers = await ethers.getSigners();
408+
for (let i = 0; i < CONSENSUS_SIZE; i++) {
409+
await Governance.connect(signers[i]).registerCandidate(500, { value: REGISTER_FEE });
410+
await Governance.connect(signers[i]).vote(signers[i], { value: VOTE_TARGET_AMOUNT });
411+
}
412+
await mine(EPOCH_DURATION);
413+
await MockSysCall.call_onPersist(Governance);
414+
415+
// Register a new candidate and vote
416+
await Governance.connect(signers[CONSENSUS_SIZE]).registerCandidate(500, { value: REGISTER_FEE });
417+
await Governance.connect(signers[CONSENSUS_SIZE]).vote(signers[CONSENSUS_SIZE], { value: 2n * VOTE_TARGET_AMOUNT });
418+
419+
// Add this candidate to blacklist
420+
for (let i = 0; i < 4; i++) {
421+
await expect(
422+
Policy.connect(signers[i]).addBlackList(signers[1])
423+
).not.to.be.reverted;
424+
}
425+
expect(await Policy.isBlackListed(signers[1])).to.eq(true);
426+
427+
// Check election result
428+
await mine(EPOCH_DURATION);
429+
await MockSysCall.call_onPersist(Governance);
430+
let consensus = await Governance.getCurrentConsensus();
431+
for (let i = 0; i < CONSENSUS_SIZE; i++) {
432+
expect(consensus[i]).to.not.eq(signers[1].address);
433+
}
434+
});
435+
436+
it("Should take standby validators as consensus if good addresses are fewer than threshold", async function () {
437+
let signers = await ethers.getSigners();
438+
for (let i = 0; i < CONSENSUS_SIZE; i++) {
439+
await Governance.connect(signers[i]).registerCandidate(500, { value: REGISTER_FEE });
440+
await Governance.connect(signers[i]).vote(signers[i], { value: VOTE_TARGET_AMOUNT });
441+
}
442+
await mine(EPOCH_DURATION);
443+
await MockSysCall.call_onPersist(Governance);
444+
445+
// Add this candidate to blacklist
446+
for (let i = 0; i < 4; i++) {
447+
await expect(
448+
Policy.connect(signers[i]).addBlackList(signers[0])
449+
).not.to.be.reverted;
450+
}
451+
expect(await Policy.isBlackListed(signers[0])).to.eq(true);
452+
453+
// Check election result
454+
await mine(EPOCH_DURATION);
455+
await MockSysCall.call_onPersist(Governance);
456+
expect(await Governance.getCurrentConsensus()).to.deep.equal(STANDBY_VALIDATORS);
457+
});
402458
});
403459

404460
describe("vote", function () {
@@ -410,7 +466,7 @@ describe("Governance", function () {
410466

411467
it("Should revert if target is not a candidate", async function () {
412468
await expect(
413-
Governance.connect(candidate1).vote(candidate1, { value: MIN_VOTE_AMOUNT})
469+
Governance.connect(candidate1).vote(candidate1, { value: MIN_VOTE_AMOUNT })
414470
).to.be.revertedWithCustomError(Governance, ERRORS.CANDIDATE_NOT_EXISTS);
415471
});
416472

0 commit comments

Comments
 (0)