I am using the following DAO contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract DAO is
Governor,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
uint256 public s_votingDelay;
uint256 public s_votingPeriod;
constructor(
ERC20Votes _token,
TimelockController _timelock,
uint256 _quorumPercentage,
uint256 _votingPeriod,
uint256 _votingDelay
)
Governor("GovernorContract")
GovernorVotes(_token)
GovernorVotesQuorumFraction(_quorumPercentage)
GovernorTimelockControl(_timelock)
{
s_votingDelay = _votingDelay;
s_votingPeriod = _votingPeriod;
}
function votingDelay() public view override returns (uint256) {
return s_votingDelay; // 1 day
}
function votingPeriod() public view override returns (uint256) {
return s_votingPeriod; // 1 week
}
// The functions below are overrides required by Solidity.
function quorum(uint256 blockNumber)
public
view
override(IGovernor, GovernorVotesQuorumFraction)
returns (uint256)
{
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(IGovernor, Governor)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
function state(uint256 proposalId)
public
view
override(Governor, GovernorTimelockControl)
returns (ProposalState)
{
return super.state(proposalId);
}
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override(Governor, IGovernor) returns (uint256) {
return super.propose(targets, values, calldatas, description);
}
function _execute(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) {
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}
function _cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor()
internal
view
override(Governor, GovernorTimelockControl)
returns (address)
{
return super._executor();
}
function supportsInterface(bytes4 interfaceId)
public
view
override(Governor, GovernorTimelockControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
Governance Token Contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol";
/**
@dev This is the governance token for the DAO contract.
*/
contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes {
uint256 m_supply = 100_000 * 10**18;
constructor() ERC20("Governance Token", "GT") ERC20Permit("GT") {
_mint(msg.sender, m_supply);
}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal override(ERC20, ERC20Votes) {
super._afterTokenTransfer(from, to, amount);
}
function _mint(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._mint(to, amount);
}
function _burn(address to, uint256 amount)
internal
override(ERC20, ERC20Votes)
{
super._burn(to, amount);
}
}
Simple Box contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract Box is Ownable {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}
Timelock contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/governance/TimelockController.sol";
contract Timelock is TimelockController {
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors
) TimelockController(minDelay, proposers, executors) {}
}
This is my script to test.
const { ethers, network } = require("hardhat");
const {
VOTING_DELAY,
VOTING_PERIOD,
PROPOSAL_DESCRIPTION,
NEW_STORE_VALUE,
FUNC,
DEVELOPMENT_CHAINS,
MIN_DELAY,
QUORUM_PERCENTAGE,
PROPOSAL_FILE,
} = require("../globals");
const fs = require("fs");
let caller;
let deployer;
let governorAddress;
let governorTokenAddress;
let timelockAddress;
let boxAddress;
let proposalIdString;
const setCaller = async () => {
const accs = await ethers.getSigners();
caller = accs[0];
deployer = accs[1];
};
const getLatestBlock = async () => {
return await ethers.provider.getBlockNumber();
};
async function moveTime(amount) {
await network.provider.send("evm_increaseTime", [amount]);
console.log(`🕞 Moved forward in time ${amount} seconds`);
}
async function moveBlocks(amount) {
console.log("Moving blocks...");
for (let index = 0; index < amount; index++) {
await network.provider.request({
method: "evm_mine",
params: [],
});
}
console.log(`Moved ${amount} blocks`);
}
const propose = async (args, functionToCall, proposalDescription) => {
// Deploying governor token contract.
const GovernorToken = await ethers.getContractFactory("GovernanceToken");
const governorToken = await GovernorToken.connect(deployer).deploy();
governorTokenAddress = governorToken.address;
// Deploying Timelock contract.
const Timelock = await ethers.getContractFactory("Timelock");
const timelock = await Timelock.connect(deployer).deploy(MIN_DELAY, [], []);
// Governor contract.
const Governor = await ethers.getContractFactory("DAO");
const governor = await Governor.connect(deployer).deploy(
governorToken.address,
timelock.address,
QUORUM_PERCENTAGE,
VOTING_PERIOD,
VOTING_DELAY
);
governorAddress = governor.address;
// Giving governor the rights to propose something on the timelock contract.
const proposerRole = await timelock.PROPOSER_ROLE();
const executorRole = await timelock.EXECUTOR_ROLE();
await (await timelock.grantRole(proposerRole, governor.address)).wait(1);
await (await timelock.grantRole(executorRole, governor.address)).wait(1);
// Box Contract.
const Box = await ethers.getContractFactory("Box");
const box = await Box.connect(deployer).deploy();
boxAddress = box.address;
const encodedFunctionCall = box.interface.encodeFunctionData(
functionToCall,
args
);
console.log(`Proposing ${functionToCall} on ${box.address} with ${args}`);
console.log(`Proposal Description:\n ${proposalDescription}`);
// Check the existing value in Box contract.
console.log("Box :: Value: ", await box.retrieve());
const proposeTx = await governor.propose(
[box.address],
[0],
[encodedFunctionCall],
proposalDescription
);
console.log("⛏ Block Number: ", await getLatestBlock());
if (DEVELOPMENT_CHAINS.includes(network.name)) {
console.log("Jumping blocks.");
await moveBlocks(VOTING_DELAY + 1);
}
const proposeReciept = await proposeTx.wait(1);
const proposalId = proposeReciept.events[0].args.proposalId;
proposalIdString = proposalId;
console.log(`Proposed with proposal ID:\n ${proposalId}`);
const proposalState = await governor.state(proposalId);
const proposalSnapShot = await governor.proposalSnapshot(proposalId);
const proposalDeadline = await governor.proposalDeadline(proposalId);
// save the proposalId
let proposals = JSON.parse(fs.readFileSync(PROPOSAL_FILE, "utf8"));
console.log("Network Chain Id: ", network.config.chainId);
proposals[network.config.chainId.toString()].push(proposalId.toString());
fs.writeFileSync(PROPOSAL_FILE, JSON.stringify(proposals));
// The state of the proposal. 1 is not passed. 0 is passed.
console.log(`Current Proposal State: ${proposalState}`);
// What block # the proposal was snapshot
console.log(`Current Proposal Snapshot: ${proposalSnapShot}`);
// The block number the proposal voting expires
console.log(`Current Proposal Deadline: ${proposalDeadline}`);
console.log("⛏ Block Number: ", await getLatestBlock());
const latestBlock = await getLatestBlock();
const power = await governorToken.getVotes(deployer.address);
console.log("💪🏻 Power: ", power);
console.table([
{ name: "Caller", balance: await governorToken.balanceOf(caller.address) },
{
name: "Deployer",
balance: await governorToken.balanceOf(deployer.address),
},
]);
};
const vote = async (proposalId, voteWay, reason) => {
console.log("⛏ Block Number: ", await getLatestBlock());
console.log("Voting started.");
const Governor = await ethers.getContractFactory("DAO");
console.log("Governor Address: ", governorAddress);
const governor = await Governor.attach(governorAddress);
const GovernanceToken = await ethers.getContractFactory("GovernanceToken");
const governanceToken = await GovernanceToken.attach(governorTokenAddress);
await (
await governanceToken.connect(deployer).delegate(deployer.address)
).wait(1);
const voteTx = await governor
.connect(deployer)
.castVoteWithReason(proposalId, voteWay, reason);
const voteTx1 = await governor
.connect(caller)
.castVoteWithReason(proposalId, voteWay, reason);
await voteTx1.wait(1);
const voteTxReceipt = await voteTx.wait(1);
console.log(voteTxReceipt.events[0].args.reason);
const proposalState = await governor.state(proposalId);
const voteBalance = await governanceToken.getVotes(deployer.address);
console.log(`Vote Balance: ${voteBalance}`);
console.log(`Current Proposal State: ${proposalState}`);
if (DEVELOPMENT_CHAINS.includes(network.name)) {
console.log(`📦 Moving ${VOTING_PERIOD + 1} blocks`);
await moveBlocks(VOTING_PERIOD + 1);
}
// Logging the proposal state.
console.log(`📝 State of proposal: ${await governor.state(proposalId)}`);
console.log(`Proposal Votes:`, await governor.proposalVotes(proposalId));
};
const queueAndExecute = async () => {
console.log("🛳 Queue and Execute");
const Box = await ethers.getContractFactory("Box");
const box = Box.attach(boxAddress);
const encodedFunctionCall = box.interface.encodeFunctionData(FUNC, [
NEW_STORE_VALUE,
]);
const descriptionHash = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(PROPOSAL_DESCRIPTION)
);
const Governor = await ethers.getContractFactory("DAO");
const governor = await Governor.attach(governorAddress);
const queueTx = await governor.queue(
[box.address],
[0],
[encodedFunctionCall],
descriptionHash
);
await queueTx.wait(1);
};
const main = async () => {
await setCaller();
await propose([NEW_STORE_VALUE], FUNC, PROPOSAL_DESCRIPTION);
await vote(proposalIdString, 1, "I like the proposal that's it.");
await queueAndExecute();
};
main()
.then(() => process.exit(0))
.catch((e) => {
console.error(e);
process.exit(1);
});
The issue is coming while calling the queue function with the message 'Governor: proposal not successful'
.
Can you please point me in the right direction about what am I missing?