// SPDX-License-Identifier: Unlicense pragma solidity ^0.8.24; import "./DAO.sol"; import "hardhat/console.sol"; contract Proposals is DAOContract { struct Attestation { address sender; uint amount; } enum Stage { Proposal, Referendum0, Referendum1, Referendum100, Closed } struct Referendum { uint duration; uint poolIndex; } struct Proposal { address sender; uint fee; uint feeRemaining; uint postIndex; uint startTime; Stage stage; mapping(uint => Attestation) attestations; uint attestationCount; Referendum[3] referenda; } mapping(uint => Proposal) public proposals; uint public proposalCount; constructor(DAO dao) DAOContract(dao) {} function propose( uint postIndex, uint referendum0Duration, uint referendum1Duration, uint referendum100Duration ) external payable returns (uint proposalIndex) { proposalIndex = proposalCount++; Proposal storage proposal = proposals[proposalIndex]; proposal.postIndex = postIndex; proposal.startTime = block.timestamp; proposal.referenda[0].duration = referendum0Duration; proposal.referenda[1].duration = referendum1Duration; proposal.referenda[2].duration = referendum100Duration; proposal.fee = msg.value; proposal.feeRemaining = proposal.fee; } function attest(uint proposalIndex, uint amount) external { // Since this is non-binding, non-encumbering, we only need to verify that // the sender actually has the rep they claim to stake. require( dao.balanceOf(msg.sender) >= amount, "Sender has insufficient REP balance" ); Proposal storage proposal = proposals[proposalIndex]; uint attestationIndex = proposal.attestationCount++; Attestation storage attestation = proposal.attestations[ attestationIndex ]; attestation.sender = msg.sender; attestation.amount = amount; } // todo onValidate() { // This callback will get proposalIndex // todo } function evaluateAttestation(uint proposalIndex) external returns (bool) { Proposal storage proposal = proposals[proposalIndex]; require( proposal.stage == Stage.Proposal, "Attestation only pertains to Proposal stage" ); uint totalAttestation; for (uint i = 0; i < proposal.attestationCount; i++) { totalAttestation += proposal.attestations[i].amount; } bool meetsAttestation = 10 * totalAttestation >= dao.totalSupply(); bool expired = block.timestamp > proposal.startTime + 365 days; if (!meetsAttestation) { if (expired) { proposal.stage = Stage.Closed; return false; } return false; } // Initiate validation pool proposal.stage = Stage.Referendum0; uint thisFee = proposal.fee / 3; proposal.feeRemaining -= thisFee; proposal.referenda[0].poolIndex = dao.initiateValidationPool{ value: thisFee }( proposal.postIndex, // uint postIndex, proposal.referenda[0].duration, // uint duration, 1, // uint quorumNumerator, 3, // uint quorumDenominator, 0, // uint bindingPercent, false, // bool redistributeLosingStakes, false, // TODO bool callbackOnValidate : true, "" // TODO bytes calldata callbackData : This should probably be proposalIndex ); return true; } }