Executing Proposal when Majority votes against

Hi everyone! I'm having an issue with governance contracts.

I want to build functionality such as a proposal has 2 calldatas and I want to execute one of them based on the results of the voting. The first should be executed when the majority votes FOR. The second one is to be executed when the majority votes AGAINST. Is it possible to do this? If so, how can I achieve this?

2 Issues I am facing:

  1. When proposal is created, it creates a proposal ID using 2 calldatas i.e the parameters (targets, values, calldatas, reason) and when I am trying to execute only one calldata using if condition, it is saying proposal ID doesn't exist.
  2. It only executes when the vote has succeeded i.e VotesFor > VotesAgainst and Quorum Reached. I am not able to execute when VotesAgainst > VotesFor as the proposal is not "Succeeded" State when this happens. It goes into "Defeated" state.

The way I have approached this is to store a state in the governor contract based on the voting when the voting period ends, and handle the executing in my "BOX" contract. (Contract owned by the DAO).

If there a better way to do this?

Thanks, in advance!

Hi, sorry for the delay. Have you made progress on this issue?

Can you share the code you have written?

I wanted to build a functionality where I am calling GPT through Chainlink Functions to vote on a proposal with 30% voting power each time. The issue was that GPT doesn't have any tokens delegated, so I changed the logic in the "GovernorCountingSimple" contract to account for GPT vote, specifically changed the logic in the "voteSucceeded" as the proposal is only executed when it is succeeded.

function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) {
        ProposalData storage _proposal = proposalIdToData[proposalId];
        if(_proposal.shouldGPTVote == true) {
            return proposalIdToGPTData[proposalId].hasVoted; //this is made true when Chainlink DON calls fulfill request
        } else {
            (
            uint256 againstVotes,
            uint256 forVotes,
        ) = proposalVotes(proposalId);
            return forVotes > againstVotes;
        }
    }

When chainlink node calls fulfillRequest with GPTs vote, I am storing it in a mapping and then calling my custom execute function which calls the Time-locks execute functions.

        (
            uint256 againstVotes,
            uint256 forVotes,
        ) = proposalVotes(_proposalId);

        uint8 result = forVotes > againstVotes ? 1 : 0;
        uint8 gptVote = proposalIdToGPTData[_proposalId]._voteWay;

        uint newresult = Decide.whichOne(gptVote, result, againstVotes, forVotes); //custom library which is re-counting the votes based on GPT vote

        ProposalData memory data = proposalIdToData[_proposalId];
        address[] memory targets = new address[](1);
        targets[0] = data.targets[0];

        uint256[] memory values = new uint256[](1);
        values[0] = 0;

        bytes[] memory datas = new bytes[](1);
        datas[0] = data.calldatas[newresult]; //only passing 1 calldata

        proposalIdToData[_proposalId].result = newresult;

        super._execute(_proposalId, targets, values, datas, "reason");

The library looks like this:

library Decide {
    
    function whichOne(uint8 way, uint8 result, uint256 againstVotes, uint256 forVotes) internal view returns (uint8) {
        if (result == 1) {
            //majority voted FOR
            if (way == 1) {
                return 1;
            } else {
                //recount
                if (
                    (againstVotes + calculateGPTVotingPower(againstVotes, forVotes)) >
                    forVotes
                ) {
                    return 0;
                } else {
                    return 1;
                }
                
            }
        } else {
            if(way == 1) {
                if (
                    (forVotes + calculateGPTVotingPower(againstVotes, forVotes)) >
                    againstVotes
                ) {
                    return 1;
                } else {
                    return 0;
                }
            } else {
                return 0;
            }
        }
    }

    function calculateGPTVotingPower(uint256 againstVotes, uint256 forVotes
    ) public view returns (uint256) {
        
        uint256 bps = 3000; // 30%
        uint256 _30percent = calculatePercentage(forVotes + againstVotes, bps);

        return _30percent;
    }

    function calculatePercentage(
        uint256 amount,
        uint256 bps
    ) public pure returns (uint256) {
        return (amount * bps) / 10_000;
    }
}

I noticed that there is a vulnerability fix made in the Execute Batch functions in TimeLock which was causing the proposal ID issue (TimelockController Vulnerability Post-mortem).

I changed the reentrancy logic to account for GPT and it seems to work fine but I would love your suggestions on this.

I'm really unsure about this. There are a lot of moving pieces so I can't give an informed comment. Why is the targets always data.targets[0] and not part of deciding which part of the proposal to execute?

Also, by GPT do you mean OpenAI's GPT?

Yes, I am using OpenAIs GPT to vote on a proposal using Chainlink Functions.

The motivation comes from the vulnerable nature of DAOs. In traditional DAO systems, decentralization can be hard to achieve, especially with small DAOs or early stage ones. Since DAOs are vulnerable to a governance attack, where attackers can borrow a large amount of the governance token to push through a proposal, I am building a DAO which involves GPT to read a proposal description and vote on each proposal with 30% voting power each proposal. This approach ensures that there is always one vote casted and makes it challenging to manipulate the system without prior knowledge of GPT's voting decision. White paper: https://freelancodao.gitbook.io/freelancodao/

Currently, the system only allows proposals to be created from the "Freelanco" contract which handles the escrow transactions between the clients and freelancers. In the case of a dispute, the client or freelancer sends a proposal to the DAO through the "Freelanco" contract. Hence, the targets for both of the calldatas are same so I'm just using the first one and only checking which calldata to use.

You can read the full contracts here: https://github.com/FreelancoDAO/hardhat

PLEASE NOTE: This is in development.