prevent stacking attestation from same sender
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 35s Details

This commit is contained in:
Ladd Hoffman 2024-03-28 18:01:55 -05:00
parent 7ffd532292
commit e95a8c0cb9
12 changed files with 424 additions and 67 deletions

View File

@ -24,6 +24,7 @@ import WorkContract from './components/work-contracts/WorkContract';
import AddPostModal from './components/posts/AddPostModal';
import ViewPostModal from './components/posts/ViewPostModal';
import Post from './utils/Post';
import Proposals from './components/Proposals';
function App() {
const {
@ -147,17 +148,17 @@ function App() {
// TODO: Unsubscribe from events when effect is to be rerun
DAOContract.events.PostAdded({ fromBlock: 'latest' }).on('data', (event) => {
console.log('event: post added', event);
console.log('event: post added');
fetchPost(event.returnValues.postIndex);
});
DAOContract.events.ValidationPoolInitiated({ fromBlock: 'latest' }).on('data', (event) => {
console.log('event: validation pool initiated', event);
console.log('event: validation pool initiated');
fetchValidationPool(event.returnValues.poolIndex);
});
DAOContract.events.ValidationPoolResolved({ fromBlock: 'latest' }).on('data', (event) => {
console.log('event: validation pool resolved', event);
console.log('event: validation pool resolved');
fetchReputation();
fetchValidationPool(event.returnValues.poolIndex);
});
@ -357,8 +358,8 @@ function App() {
{' '}
Initiate Validation Pool
{' '}
<Button onClick={() => initiateValidationPool(post.id, 60)}>
1 Min.
<Button onClick={() => initiateValidationPool(post.id, 20)}>
20 s
</Button>
{' '}
<Button onClick={() => initiateValidationPool(post.id, 3600)}>
@ -450,6 +451,9 @@ function App() {
/>
)}
</Tab>
<Tab eventKey="proposals" title="Proposals">
<Proposals />
</Tab>
</Tabs>
</>
)}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,30 +1,110 @@
import { useContext, useMemo } from 'react';
import { PropTypes } from 'prop-types';
import {
useCallback, useContext, useEffect, useRef, useState,
} from 'react';
import { Web3 } from 'web3';
import Button from 'react-bootstrap/esm/Button';
import useList from '../utils/List';
import WorkContractContext from '../contexts/WorkContractContext';
import AvailabilityStakes from './work-contracts/AvailabilityStakes';
import WorkRequests from './work-contracts/WorkRequests';
import Web3Context from '../contexts/Web3Context';
import ProposalsArtifact from '../assets/Proposals.json';
import { getContractAddressByChainId } from '../utils/contract-config';
import AddPostModal from './posts/AddPostModal';
function Proposals() {
const {
provider, chainId, account, reputation,
} = useContext(Web3Context);
const [proposals, dispatchProposal] = useList();
const {provider} = useContext(Web3Context);
const proposalsContract = useRef();
const [showAddProposal, setShowAddProposal] = useState(false);
const fetchProposal = useCallback(async (proposalIndex) => {
const proposal = await proposalsContract.current.methods.proposals(proposalIndex).call();
dispatchProposal({
type: 'update',
item: {
...proposal,
id: proposalIndex,
},
});
}, [proposalsContract, dispatchProposal]);
const fetchProposals = useCallback(async () => {
const count = await proposalsContract.current.methods.proposalCount().call();
const promises = [];
dispatchProposal({ type: 'refresh' });
for (let i = 0; i < count; i += 1) {
promises.push(fetchProposal(i));
}
await Promise.all(promises);
}, [proposalsContract, fetchProposal, dispatchProposal]);
// Initial data load and event subscription
// TODO: unsubscribe on unmount
useEffect(() => {
if (chainId === undefined) return;
const web3 = new Web3(provider);
const ProposalsAddress = getContractAddressByChainId(chainId, 'Proposals');
const contract = new web3.eth.Contract(ProposalsArtifact.abi, ProposalsAddress);
proposalsContract.current = contract;
fetchProposals();
contract.events.NewProposal({ fromBlock: 'latest' }).on('data', (event) => {
console.log('event: new proposal', event);
fetchProposal(event.returnValues.proposalIndex);
});
}, [provider, chainId, proposalsContract, fetchProposals, fetchProposal]);
const handleShowAddProposal = () => setShowAddProposal(true);
const onSubmitProposal = useCallback(async (post) => {
// TODO: Make referenda durations configurable
await proposalsContract.current.methods.propose(post.hash, 20, 20, 20).send({
from: account,
gas: 1000000,
value: 10000,
});
}, [account, proposalsContract]);
const handleAttest = useCallback(async (proposalIndex) => {
await proposalsContract.current.methods.attest(proposalIndex, reputation).send({
from: account,
gas: 1000000,
});
}, [proposalsContract, account, reputation]);
return (
<>
<AddPostModal title="New Proposal" show={showAddProposal} setShow={setShowAddProposal} onSubmit={onSubmitProposal} />
<h2>Proposals</h2>
<div>
<Button onClick={handleShowAddProposal}>New Proposal</Button>
</div>
<div>
<table className="table">
<thead>
<tr>
<th>ID</th>
{/* <th>Pool ID</th> */}
<th>Fee</th>
<th>Stage</th>
<th>Attestation</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{proposals.filter((x) => !!x).map((request) => (
<tr key={request.id}>
<td>{request.id.toString()}</td>
<td>{request.fee.toString()}</td>
<td>{request.stage.toString()}</td>
<td>{request.attestationTotal.toString()}</td>
<td>
{request.stage === 0n && (
<Button onClick={() => handleAttest(request.id)}>Attest</Button>
)}
</td>
</tr>
))}
</tbody>
@ -34,21 +114,4 @@ function Proposals() {
);
}
WorkContract.propTypes = {
workContract: PropTypes.any.isRequired, // eslint-disable-line react/forbid-prop-types
showRequestWork: PropTypes.bool,
title: PropTypes.string.isRequired,
verb: PropTypes.string.isRequired,
showAvailabilityActions: PropTypes.bool,
showAvailabilityAmount: PropTypes.bool,
onlyShowAvailable: PropTypes.bool,
};
WorkContract.defaultProps = {
showRequestWork: false,
showAvailabilityActions: true,
showAvailabilityAmount: true,
onlyShowAvailable: false,
};
export default WorkContract;
export default Proposals;

View File

@ -1,8 +1,9 @@
{
"localhost": {
"DAO": "0xadB35d0E9d7B33441C7ED26add5D42F873430790",
"Work1": "0xb3FaD3422e1Edf2f7035952aa0f8760df466213a",
"Onboarding": "0xd7a4eDcC2Fbc139DfD88E25a996a8a9Dd385B1f0"
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
"Proposals": "0x71cb20D63576a0Fa4F620a2E96C73F82848B09e1"
},
"sepolia": {
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",

View File

@ -1,8 +1,9 @@
{
"localhost": {
"DAO": "0xadB35d0E9d7B33441C7ED26add5D42F873430790",
"Work1": "0xb3FaD3422e1Edf2f7035952aa0f8760df466213a",
"Onboarding": "0xd7a4eDcC2Fbc139DfD88E25a996a8a9Dd385B1f0"
"DAO": "0x65B0922fe7F0c4012aa38704071f26aeF6F22650",
"Work1": "0x95673D8710A8eD59f8551e9B12509D6812e0623e",
"Onboarding": "0xc6b3b8A641c52F7bC13a9D444e1f0759CA3b87b4",
"Proposals": "0x71cb20D63576a0Fa4F620a2E96C73F82848B09e1"
},
"sepolia": {
"DAO": "0x8Cb4ab513A863ac29e855c85064ea53dec7dA24C",

View File

@ -7,11 +7,6 @@ import "./IOnValidate.sol";
import "hardhat/console.sol";
contract Proposals is DAOContract, IOnValidate {
struct Attestation {
address sender;
uint amount;
}
enum Stage {
Proposal,
Referendum0,
@ -41,8 +36,8 @@ contract Proposals is DAOContract, IOnValidate {
uint postIndex;
uint startTime;
Stage stage;
mapping(uint => Attestation) attestations;
uint attestationCount;
mapping(address => uint) attestations;
uint attestationTotal;
Referendum[3] referenda;
}
@ -57,11 +52,12 @@ contract Proposals is DAOContract, IOnValidate {
constructor(DAO dao) DAOContract(dao) {}
function propose(
uint postIndex,
string calldata contentId,
uint referendum0Duration,
uint referendum1Duration,
uint referendum100Duration
) external payable returns (uint proposalIndex) {
uint postIndex = dao.addPost(msg.sender, contentId);
proposalIndex = proposalCount++;
Proposal storage proposal = proposals[proposalIndex];
proposal.postIndex = postIndex;
@ -86,12 +82,9 @@ contract Proposals is DAOContract, IOnValidate {
"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;
proposal.attestationTotal -= proposal.attestations[msg.sender];
proposal.attestations[msg.sender] = amount;
proposal.attestationTotal += amount;
}
// --- Sequences of validation pool parameters ---
@ -214,11 +207,8 @@ contract Proposals is DAOContract, IOnValidate {
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 meetsAttestation = 10 * proposal.attestationTotal >=
dao.totalSupply();
bool expired = block.timestamp > proposal.startTime + 365 days;
if (!meetsAttestation) {
if (expired) {

View File

@ -0,0 +1,10 @@
const deployDAOContract = require('./util/deploy-dao-contract');
async function main() {
await deployDAOContract('Proposals');
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

View File

@ -1,10 +1,12 @@
const deployWorkContract = require('./util/deploy-work-contract');
const deployContract = require('./util/deploy-contract');
const deployDAOContract = require('./util/deploy-dao-contract');
async function main() {
await deployContract('DAO');
await deployWorkContract('Work1');
await deployWorkContract('Onboarding');
await deployDAOContract('Proposals');
}
main().catch((error) => {

View File

@ -0,0 +1,13 @@
const deployContract = require('./deploy-contract');
const contractAddresses = require('../../contract-addresses.json');
require('dotenv').config();
const network = process.env.HARDHAT_NETWORK;
const deployDAOContract = async (name) => {
await deployContract(name, [contractAddresses[network].DAO]);
};
module.exports = deployDAOContract;