Skip to content

Commit 4bf207c

Browse files
Merge pull request #13 from rsksmart/governor
feat: Governor basic
2 parents ab97e33 + 9a53cfd commit 4bf207c

16 files changed

+683
-56
lines changed

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9"
2+
"solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9",
3+
"editor.defaultFormatter": "esbenp.prettier-vscode",
4+
"editor.formatOnSave": true
35
}

contracts/DaoTimelockUpgradable.sol

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
4+
import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
5+
6+
contract DaoTimelockUpgradable is
7+
UUPSUpgradeable,
8+
TimelockControllerUpgradeable
9+
{
10+
function initialize(
11+
uint256 minDelay,
12+
address[] memory proposers,
13+
address[] memory executors,
14+
address admin
15+
) public initializer {
16+
__UUPSUpgradeable_init();
17+
__AccessControl_init();
18+
__TimelockController_init(minDelay, proposers, executors, admin);
19+
}
20+
function _authorizeUpgrade(
21+
address newImplementation
22+
) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
23+
}

contracts/Governor.sol

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
5+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol";
6+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol";
7+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorStorageUpgradeable.sol";
8+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol";
9+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol";
10+
import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol";
11+
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
12+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
13+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
14+
15+
contract RootDao is
16+
Initializable,
17+
GovernorUpgradeable,
18+
GovernorSettingsUpgradeable,
19+
GovernorCountingSimpleUpgradeable,
20+
GovernorStorageUpgradeable,
21+
GovernorVotesUpgradeable,
22+
GovernorVotesQuorumFractionUpgradeable,
23+
GovernorTimelockControlUpgradeable,
24+
OwnableUpgradeable,
25+
UUPSUpgradeable
26+
{
27+
/// @custom:oz-upgrades-unsafe-allow constructor
28+
constructor() {
29+
_disableInitializers();
30+
}
31+
32+
function initialize(
33+
IVotes voteToken,
34+
TimelockControllerUpgradeable timelockController,
35+
address initialOwner
36+
) public initializer {
37+
__Governor_init("RootDao");
38+
__GovernorSettings_init(7200 /* 1 day */, 50400 /* 1 week */, 10 * 10 ** 18);
39+
__GovernorCountingSimple_init();
40+
__GovernorStorage_init();
41+
__GovernorVotes_init(voteToken);
42+
__GovernorVotesQuorumFraction_init(4);
43+
__GovernorTimelockControl_init(timelockController);
44+
__Ownable_init(initialOwner);
45+
__UUPSUpgradeable_init();
46+
}
47+
48+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
49+
50+
// The following functions are overrides required by Solidity.
51+
52+
function votingDelay()
53+
public
54+
view
55+
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
56+
returns (uint256)
57+
{
58+
return super.votingDelay();
59+
}
60+
61+
function votingPeriod()
62+
public
63+
view
64+
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
65+
returns (uint256)
66+
{
67+
return super.votingPeriod();
68+
}
69+
70+
function quorum(
71+
uint256 blockNumber
72+
) public view override(GovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable) returns (uint256) {
73+
return super.quorum(blockNumber);
74+
}
75+
76+
function state(
77+
uint256 proposalId
78+
) public view override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (ProposalState) {
79+
return super.state(proposalId);
80+
}
81+
82+
function proposalNeedsQueuing(
83+
uint256 proposalId
84+
) public view override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (bool) {
85+
return super.proposalNeedsQueuing(proposalId);
86+
}
87+
88+
function proposalThreshold()
89+
public
90+
view
91+
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
92+
returns (uint256)
93+
{
94+
return super.proposalThreshold();
95+
}
96+
97+
function _propose(
98+
address[] memory targets,
99+
uint256[] memory values,
100+
bytes[] memory calldatas,
101+
string memory description,
102+
address proposer
103+
) internal override(GovernorUpgradeable, GovernorStorageUpgradeable) returns (uint256) {
104+
return super._propose(targets, values, calldatas, description, proposer);
105+
}
106+
107+
function _queueOperations(
108+
uint256 proposalId,
109+
address[] memory targets,
110+
uint256[] memory values,
111+
bytes[] memory calldatas,
112+
bytes32 descriptionHash
113+
) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint48) {
114+
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
115+
}
116+
117+
function _executeOperations(
118+
uint256 proposalId,
119+
address[] memory targets,
120+
uint256[] memory values,
121+
bytes[] memory calldatas,
122+
bytes32 descriptionHash
123+
) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) {
124+
super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
125+
}
126+
127+
function _cancel(
128+
address[] memory targets,
129+
uint256[] memory values,
130+
bytes[] memory calldatas,
131+
bytes32 descriptionHash
132+
) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) {
133+
return super._cancel(targets, values, calldatas, descriptionHash);
134+
}
135+
136+
function _executor()
137+
internal
138+
view
139+
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
140+
returns (address)
141+
{
142+
return super._executor();
143+
}
144+
}

contracts/RIFToken.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ pragma solidity ^0.4.24;
33

44
import "rif-token-contracts/contracts/RIF/RIFToken.sol";
55

6+
contract RIFTokenContact is RIFToken {
7+
8+
}
9+
610
/* File must exist, else test/RIFToken.test.ts will fail */

contracts/TokenFaucet.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @title RIF Token Faucet
3+
* @author IOV Labs
4+
* @notice Original source code is taken from the repository:
5+
* https://github.com/riflabs/rif-faucet/blob/master/contracts/TokenFaucet.sol
6+
*/
7+
8+
pragma solidity ^0.8.20;
9+
10+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
12+
13+
contract TokenFaucet is Ownable {
14+
IERC20 public immutable tokenContract;
15+
16+
mapping(address => uint) cannotDispenseUntil;
17+
18+
uint256 public dispenseValue = 10 * 10 ** 18; // 10 tRIFs
19+
uint256 public dispenceFrequency = 1 hours;
20+
21+
event DispenceFrequencyChanged(address changer, uint256 oldValue, uint256 newValue);
22+
event DispenceValueChanged(address changer, uint256 oldValue, uint256 newValue);
23+
24+
modifier canDispense(address to) {
25+
require(cannotDispenseUntil[to] < block.timestamp, "CANNOT DISPENSE MORE THAN 1 TIME PER HOUR");
26+
_;
27+
}
28+
29+
constructor(IERC20 rifToken) Ownable(msg.sender) {
30+
tokenContract = rifToken;
31+
}
32+
33+
function recover() public returns (bool) {
34+
uint256 totalAmount = tokenContract.balanceOf(address(this));
35+
return tokenContract.transfer(owner(), totalAmount);
36+
}
37+
38+
function dispense(address to) public canDispense(to) returns (bool) {
39+
cannotDispenseUntil[to] = block.timestamp + dispenceFrequency;
40+
return tokenContract.transfer(to, dispenseValue);
41+
}
42+
43+
function setDispenseValue(uint256 value) public onlyOwner {
44+
emit DispenceValueChanged(msg.sender, dispenseValue, value);
45+
dispenseValue = value;
46+
}
47+
48+
function setDispenseFrequency(uint256 freqSeconds) public onlyOwner {
49+
emit DispenceFrequencyChanged(msg.sender, dispenceFrequency, freqSeconds);
50+
dispenceFrequency = freqSeconds;
51+
}
52+
}

hardhat.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { HardhatUserConfig } from 'hardhat/config'
22
import '@nomicfoundation/hardhat-toolbox'
33
import '@openzeppelin/hardhat-upgrades'
4+
import '@nomicfoundation/hardhat-viem'
45

56
const config: HardhatUserConfig = {
67
solidity: {
@@ -14,6 +15,11 @@ const config: HardhatUserConfig = {
1415
{ version: '0.4.24' },
1516
],
1617
},
18+
networks: {
19+
hardhat: {
20+
allowUnlimitedContractSize: true,
21+
},
22+
},
1723
}
1824

1925
export default config

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
"author": "RootstockLabs",
77
"license": "MIT",
88
"scripts": {
9-
"compile": "hardhat compile",
9+
"compile": "npx hardhat compile",
1010
"clean": "hardhat clean",
1111
"lint:ts": "eslint 'test/**/*.ts'",
1212
"lint:sol": "solhint 'contracts/**/*.sol'",
1313
"lint": "yarn lint:ts && yarn lint:sol",
14-
"test": "hardhat test"
14+
"test": "npx hardhat test"
1515
},
1616
"devDependencies": {
1717
"@eslint/js": "^9.5.0",
@@ -21,7 +21,9 @@
2121
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.0",
2222
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
2323
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
24+
"@nomicfoundation/hardhat-toolbox-viem": "^3.0.0",
2425
"@nomicfoundation/hardhat-verify": "^2.0.0",
26+
"@nomicfoundation/hardhat-viem": "^2.0.2",
2527
"@openzeppelin/contracts": "^5.0.2",
2628
"@openzeppelin/contracts-upgradeable": "^5.0.2",
2729
"@openzeppelin/hardhat-upgrades": "^3.2.0",
@@ -33,6 +35,7 @@
3335
"@types/node": ">=18.0.0",
3436
"@typescript-eslint/eslint-plugin": "^7.11.0",
3537
"@typescript-eslint/parser": "^7.11.0",
38+
"bigint-crypto-utils": "^3.3.0",
3639
"chai": "^4.2.0",
3740
"eslint": "^9.5.0",
3841
"eslint-config-prettier": "^9.1.0",
@@ -48,7 +51,8 @@
4851
"ts-node": ">=8.0.0",
4952
"typechain": "^8.3.2",
5053
"typescript": "^5.4.5",
51-
"typescript-eslint": "^7.14.1"
54+
"typescript-eslint": "^7.14.1",
55+
"viem": "^2.15.1"
5256
},
5357
"dependencies": {
5458
"rif-token-contracts": "https://github.com/Freshenext/RIF-Token"

scripts/deploy-governor.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import hre, { ethers, upgrades } from 'hardhat'
2+
import { GovernorTimelockControlUpgradeable, RootDao } from '../typechain-types'
3+
4+
export const deployGovernor = async (tokenAddress: string, deployerAddress: string) => {
5+
const RootDAOFactory = await ethers.getContractFactory('RootDao')
6+
const TimelockFactory = await ethers.getContractFactory('DaoTimelockUpgradable')
7+
// TODO: figure out why it allows to put only a single argument
8+
const timelock = (await upgrades.deployProxy(TimelockFactory, [1, [], [], ethers.ZeroAddress], {
9+
initializer: 'initialize(uint256,address[],address[],address)',
10+
kind: 'uups',
11+
timeout: 0,
12+
})) as unknown as GovernorTimelockControlUpgradeable
13+
14+
const rootDAOGovernor = (await upgrades.deployProxy(
15+
RootDAOFactory,
16+
[tokenAddress, await timelock.getAddress(), deployerAddress],
17+
{
18+
initializer: 'initialize',
19+
kind: 'uups',
20+
timeout: 0,
21+
},
22+
)) as unknown as RootDao
23+
24+
const rootDAOContact = await rootDAOGovernor.waitForDeployment()
25+
26+
console.log(`Deployed Governor on ${hre.network.name} with address ${await rootDAOContact.getAddress()}`)
27+
28+
return rootDAOContact
29+
}

scripts/deploy-rif.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
2+
import hre, { ethers } from 'hardhat'
3+
4+
export const deployRif = async (deployer: SignerWithAddress) => {
5+
const rifToken = await ethers.deployContract('RIFToken')
6+
await rifToken.waitForDeployment()
7+
8+
const rifAddress = await rifToken.getAddress()
9+
10+
console.log(`Deployed RIF Token on ${hre.network.name} with address ${rifAddress}`)
11+
12+
// `setAuthorizedManagerContract` transfers all tokens to Deployer
13+
const tx1 = await rifToken.setAuthorizedManagerContract(deployer.address)
14+
await tx1.wait()
15+
console.log(`All RIF tokens transferred to ${deployer.address}`)
16+
17+
// close distribution
18+
const block = await ethers.provider.getBlock('latest')
19+
const now = Math.round(Date.now() / 1000)
20+
const tx2 = await rifToken.closeTokenDistribution(block?.timestamp ?? now)
21+
await tx2.wait()
22+
console.log(`RIF distribution closed`)
23+
24+
const tokenFaucet = await (await ethers.deployContract('TokenFaucet', [rifToken])).waitForDeployment()
25+
26+
// transfer half of RIFs to the faucet
27+
const rifSupply = 10n ** 27n
28+
29+
const tx3 = await rifToken.transfer(await tokenFaucet.getAddress(), rifSupply / 2n)
30+
await tx3.wait()
31+
console.log(`RIF tokens transferred to the Faucet`)
32+
33+
console.log(`Deployed Token Faucet on ${hre.network.name} with address ${await tokenFaucet.getAddress()}`)
34+
35+
return { rifToken, rifAddress, tokenFaucet }
36+
}

scripts/deploy-stRIF.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ethers, upgrades } from 'hardhat'
1+
import hre, { ethers, upgrades } from 'hardhat'
22
import { StRIFToken } from '../typechain-types'
33

44
export const deployStRIF = async (rifTokenAddress: string, deployerAddress: string) => {
@@ -8,7 +8,13 @@ export const deployStRIF = async (rifTokenAddress: string, deployerAddress: stri
88
kind: 'uups',
99
timeout: 0, // wait indefinitely
1010
unsafeAllow: ['internal-function-storage'],
11-
}) as unknown as StRIFToken)
11+
})) as unknown as StRIFToken
1212

13-
return await stRIFToken.waitForDeployment()
13+
const stRIFContract = await stRIFToken.waitForDeployment()
14+
15+
console.log(
16+
`Deployed RIF Governance Token on ${hre.network.name} with address ${await stRIFContract.getAddress()}`,
17+
)
18+
19+
return stRIFContract
1420
}

0 commit comments

Comments
 (0)