Skip to content
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
1 change: 1 addition & 0 deletions contracts/solidity/GovProxyAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ contract GovProxyAdmin is GovernanceVote {
virtual
needVote(
bytes32(
// keccak256("upgradeAndCall")
0xe739b9109d83c1c6d0d640fe9ed476fc5862a6de5483b00678a3fffa7a2be2f6
),
keccak256(abi.encode(proxy, newImplementation, data))
Expand Down
6 changes: 6 additions & 0 deletions contracts/solidity/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.25;
import {Errors} from "./libraries/Errors.sol";
import {IGovReward} from "./interfaces/IGovReward.sol";
import {IGovernance} from "./interfaces/IGovernance.sol";
import {IPolicy} from "./interfaces/IPolicy.sol";
import {ERC1967Utils, GovProxyUpgradeable} from "./base/GovProxyUpgradeable.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {ReentrancyGuard} from"@openzeppelin/contracts/utils/ReentrancyGuard.sol";
Expand All @@ -12,6 +13,9 @@ contract Governance is IGovernance, ReentrancyGuard, GovProxyUpgradeable {
using EnumerableSet for EnumerableSet.AddressSet;

address public constant SELF = 0x1212100000000000000000000000000000000001;
// Policy contract
address public constant POLICY =
0x1212000000000000000000000000000000000002;
// GovReward contract
address public constant GOV_REWARD =
0x1212000000000000000000000000000000000003;
Expand Down Expand Up @@ -111,6 +115,8 @@ contract Governance is IGovernance, ReentrancyGuard, GovProxyUpgradeable {
if (tx.origin != msg.sender) revert Errors.OnlyEOA();
if (msg.value < registerFee) revert Errors.InsufficientValue();
if (shareRate > 1000) revert Errors.InvalidShareRate();
if (candidateList.length() >= IPolicy(POLICY).getCandidateLimit())
revert Errors.RegisterDisabled();
if (exitHeightOf[msg.sender] > 0) revert Errors.LeftNotClaimed();
if (!candidateList.add(msg.sender)) revert Errors.CandidateExists();
if (receivedVotes[msg.sender] > 0) {
Expand Down
29 changes: 29 additions & 0 deletions contracts/solidity/Policy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import {ERC1967Utils, GovProxyUpgradeable} from "./base/GovProxyUpgradeable.sol"

contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
address public constant SELF = 0x1212100000000000000000000000000000000002;
uint256 public constant DEFAULT_CANDIDATE_LIMIT = 2000;

mapping(address => bool) public isBlackListed;
uint256 public minGasTipCap;
uint256 public baseFee;
uint256 internal candidateLimit;

// Only for precompiled uups implementation in genesis file, need to be removed when upgrading the contract.
// This override is added because "immutable __self" in UUPSUpgradeable is not avaliable in precompiled contract.
Expand Down Expand Up @@ -39,6 +41,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
external
needVote(
bytes32(
// keccak256("addBlackList")
0x4912b57f7ea75243ecaff76a75bdedbc13a6f58c1c967b0427b8aee0a276309e
),
keccak256(abi.encode(_addr))
Expand All @@ -55,6 +58,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
external
needVote(
bytes32(
// keccak256("removeBlackList")
0x310cc9bfce6443143f03d0cdc4d66afa0b3c689539eb3e65cb1820b56d672465
),
keccak256(abi.encode(_addr))
Expand All @@ -71,6 +75,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
external
needVote(
bytes32(
// keccak256("setMinGasTipCap")
0x089197e4f35b8ada456b5531e8c1759ee3fce703602a3a957b5c9d2831082156
),
keccak256(abi.encode(_gasTipCap))
Expand All @@ -87,6 +92,7 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
external
needVote(
bytes32(
// keccak256("setBaseFee")
0x83113031fe9312a872d9176bc1a087dc38ca109c517a596998332e2fb8409acc
),
keccak256(abi.encode(_baseFee))
Expand All @@ -96,4 +102,27 @@ contract Policy is IPolicy, GovernanceVote, GovProxyUpgradeable {
baseFee = _baseFee;
emit SetBaseFee(_baseFee);
}

function setCandidateLimit(
uint256 _candidateLimit
)
external
needVote(
bytes32(
// keccak256("setCandidateLimit")
0x172d358b638a8ee3e962dd73800c4025c48eb0f79c479bc2cdd1f63e72779efc
),
keccak256(abi.encode(_candidateLimit))
)
{
if (_candidateLimit <= 0) revert Errors.InvalidCandidateLimit();
candidateLimit = _candidateLimit;
emit SetCandidateLimit(_candidateLimit);
}

function getCandidateLimit() external view returns (uint256) {
uint256 limit = candidateLimit;
if (limit > 0) return limit;
else return DEFAULT_CANDIDATE_LIMIT;
}
}
1 change: 1 addition & 0 deletions contracts/solidity/Treasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract Treasury is GovernanceVote, ITreasury {
external
needVote(
bytes32(
// keccak256("fundBridge")
0xdd6d322687f552c30b168d744bbd29145a2095a3557a58387f7e7230c9449179
),
keccak256(abi.encode(_amount))
Expand Down
16 changes: 16 additions & 0 deletions contracts/solidity/interfaces/IPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,32 @@ interface IPolicy {
event RemoveBlackList(address indexed addr);
event SetMinGasTipCap(uint256 gasTipCap);
event SetBaseFee(uint256 baseFee);
event SetCandidateLimit(uint256 candidateLimit);

// add an address to blacklist policy
function addBlackList(address _addr) external;

// remove an address from blacklist policy
function removeBlackList(address _addr) external;

// check if an address is blacklisted by policy
function isBlackListed(address _addr) external view returns (bool);

// set a new value to minimum gas tip cap policy
function setMinGasTipCap(uint256 _gasTipCap) external;

// get the value of minimum gas tip cap policy
function minGasTipCap() external view returns (uint256);

// set a new value to base fee policy
function setBaseFee(uint256 _baseFee) external;

// get the value of base fee policy
function baseFee() external view returns (uint256);

// set candidate limit (increase only)
function setCandidateLimit(uint256 _candidateLimit) external;

// return the value of candidate limit policy
function getCandidateLimit() external view returns (uint256);
}
2 changes: 2 additions & 0 deletions contracts/solidity/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ library Errors {
error BlacklistNotExists();
error InvalidMinGasTipCap();
error InvalidBaseFee();
error InvalidCandidateLimit();

// Governance Errors
error SideCallNotAllowed();
error OnlyEOA();
error InsufficientValue();
error InvalidShareRate();
error RegisterDisabled();
error SameCandidate();
error CandidateExists();
error CandidateNotExists();
Expand Down
24 changes: 24 additions & 0 deletions contracts/test/Governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const STANDBY_VALIDATORS = [
"0xd711da2d8c71a801fc351163337656f1321343a0"
];

const MIN_GAS_TIP_CAP = ethers.parseUnits("1", "gwei");
const BASE_FEE = ethers.parseUnits("1", "gwei");
const CANDIDATE_LIMIT = 2000;

describe("Governance", function () {

let Governance: any;
Expand All @@ -43,10 +47,18 @@ describe("Governance", function () {

// Deploy Governance contract
const governance_deploy = await ethers.deployContract("Governance");
const reward_deploy = await ethers.deployContract("GovReward");
const policy_deploy = await ethers.deployContract("Policy");

// Copy Bytecode to native address
const governance_code = await ethers.provider.send("eth_getCode", [governance_deploy.target]);
await ethers.provider.send("hardhat_setCode", [GOV_PROXY, governance_code]);

const reward_code = await ethers.provider.send("eth_getCode", [reward_deploy.target]);
await ethers.provider.send("hardhat_setCode", [REWARD_PROXY, reward_code]);

const policy_code = await ethers.provider.send("eth_getCode", [policy_deploy.target]);
await ethers.provider.send("hardhat_setCode", [POLICY_PROXY, policy_code]);
const contract = require("../artifacts/solidity/Governance.sol/Governance.json");
Governance = new ethers.Contract(GOV_PROXY, contract.abi, user);

Expand Down Expand Up @@ -74,6 +86,11 @@ describe("Governance", function () {
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6c", ethers.toBeHex(STANDBY_VALIDATORS[4], 32)]);
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6d", ethers.toBeHex(STANDBY_VALIDATORS[5], 32)]);
await ethers.provider.send("hardhat_setStorageAt", [GOV_PROXY, "0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6e", ethers.toBeHex(STANDBY_VALIDATORS[6], 32)]);

// Write Policy config to storage
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x2", ethers.toBeHex(MIN_GAS_TIP_CAP, 32)]);
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x3", ethers.toBeHex(BASE_FEE, 32)]);
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x4", ethers.toBeHex(CANDIDATE_LIMIT, 32)]);
});

describe("genesis", function () {
Expand Down Expand Up @@ -143,6 +160,13 @@ describe("Governance", function () {
).to.be.revertedWithCustomError(Governance, ERRORS.INVALID_SHARE_RATE);
});

// it("Should revert if the candidate amount exceeds limit", async function () {
// await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x4", ethers.toBeHex(0, 32)]);
// await expect(
// Governance.connect(candidate1).registerCandidate(500, { value: REGISTER_FEE })
// ).to.be.revertedWithCustomError(Governance, ERRORS.REGISTER_DISABLED);
// });

it("Should register a new candidate if all conditions are met", async function () {
await expect(
Governance.connect(candidate1).registerCandidate(500, { value: REGISTER_FEE })
Expand Down
40 changes: 40 additions & 0 deletions contracts/test/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const STANDBY_VALIDATORS = [

const MIN_GAS_TIP_CAP = ethers.parseUnits("1", "gwei");
const BASE_FEE = ethers.parseUnits("1", "gwei");
const CANDIDATE_LIMIT = 2000;

describe("Policy", function () {

Expand Down Expand Up @@ -88,6 +89,7 @@ describe("Policy", function () {
// Write Policy config to storage
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x2", ethers.toBeHex(MIN_GAS_TIP_CAP, 32)]);
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x3", ethers.toBeHex(BASE_FEE, 32)]);
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x4", ethers.toBeHex(CANDIDATE_LIMIT, 32)]);
});

describe("genesis", function () {
Expand All @@ -97,6 +99,9 @@ describe("Policy", function () {
it("Should get base fee as expected", async function () {
expect(await Policy.baseFee()).to.eq(BASE_FEE);
});
it("Should get candidate limit as expected", async function () {
expect(await Policy.getCandidateLimit()).to.eq(CANDIDATE_LIMIT);
});
});

describe("addBlackList", function () {
Expand Down Expand Up @@ -257,4 +262,39 @@ describe("Policy", function () {
).emit(Policy, "SetBaseFee");
});
});

describe("setCandidateLimit", function () {
it("Should revert if the sender is not a validator", async function () {
await expect(
Policy.connect(signers[7]).setCandidateLimit(2001)
).to.be.revertedWithCustomError(Policy, ERRORS.NOT_MINER);
});

it("Should change the candidate limit if meets the threshold", async function () {
for (let i = 0; i < 4; i++) {
await expect(
Policy.connect(signers[i]).setCandidateLimit(2001)
).not.to.be.reverted;
}
expect(await Policy.getCandidateLimit()).to.eq(2001);
});

it("Should emit an event if meets the threshold", async function () {
for (let i = 0; i < 3; i++) {
await expect(
Policy.connect(signers[i]).setCandidateLimit(2001)
).not.to.be.reverted;
}
await expect(
Policy.connect(signers[3]).setCandidateLimit(2001)
).emit(Policy, "SetCandidateLimit");
});
});

describe("setCandidateLimit", function () {
it("Should return default value if not setted", async function () {
await ethers.provider.send("hardhat_setStorageAt", [POLICY_PROXY, "0x4", ethers.toBeHex(0, 32)]);
expect(await Policy.getCandidateLimit()).to.eq(CANDIDATE_LIMIT);
});
});
});
2 changes: 2 additions & 0 deletions contracts/test/helpers/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ export const ERRORS = {
BLACKLIST_NOT_EXISTS: 'BlacklistNotExists()',
INVALID_MIN_GAS_TIP_CAP: 'InvalidMinGasTipCap()',
INVALID_BASE_FEE: 'InvalidBaseFee()',
INVALID_CANDIDATE_LIMIT: 'InvalidCandidateLimit()',
SIDE_CALL_OT_ALLOWED: 'SideCallNotAllowed()',
ONLY_EOA: 'OnlyEOA()',
INSUFFICIENT_VALUE: 'InsufficientValue()',
INVALID_SHARE_RATE: 'InvalidShareRate()',
REGISTER_DISABLED: 'RegisterDisabled()',
SAME_CANDIDATE: 'SameCandidate()',
CANDIDATE_EXISTS: 'CandidateExists()',
CANDIDATE_NOT_EXISTS: 'CandidateNotExists()',
Expand Down