UI for work evidence and approval
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 43s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 43s
Details
This commit is contained in:
parent
b437459a20
commit
8d7793a947
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
useCallback, useEffect, useRef, useState,
|
||||
useCallback, useEffect, useReducer, useState,
|
||||
} from 'react';
|
||||
import { useSDK } from '@metamask/sdk-react';
|
||||
import { Web3 } from 'web3';
|
||||
|
@ -19,6 +19,34 @@ const contracts = {
|
|||
},
|
||||
};
|
||||
|
||||
const getPoolStatus = (pool) => {
|
||||
if (pool.resolved) {
|
||||
return pool.outcome ? 'Accepted' : 'Rejected';
|
||||
}
|
||||
return pool.timeRemaining > 0 ? 'In Progress' : 'Ready to Evaluate';
|
||||
};
|
||||
|
||||
const getRequestStatus = (request) => {
|
||||
switch (Number(request.status)) {
|
||||
case 0:
|
||||
return 'Requested';
|
||||
case 1:
|
||||
return 'Evidence Submitted';
|
||||
case 2:
|
||||
return 'Approval Submitted';
|
||||
case 3:
|
||||
return 'Complete';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
const updateListItem = (list, item) => {
|
||||
const newList = [...list];
|
||||
newList[Number(item.id)] = item;
|
||||
return newList;
|
||||
};
|
||||
|
||||
function App() {
|
||||
const {
|
||||
sdk, connected, provider, chainId, account, balance,
|
||||
|
@ -28,13 +56,12 @@ function App() {
|
|||
const [work1, setWork1] = useState();
|
||||
const [work1Price, setWork1Price] = useState();
|
||||
const [balanceEther, setBalanceEther] = useState();
|
||||
const reputation = useRef();
|
||||
const [reputation, setReputation] = useState();
|
||||
const [totalReputation, setTotalReputation] = useState();
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [validationPools, setValidationPools] = useState([]);
|
||||
const stakedPools = useRef([]);
|
||||
const [availabilityStakes, setAvailabilityStakes] = useState([]);
|
||||
const [workRequests, setWorkRequests] = useState([]);
|
||||
const [posts, dispatchPost] = useReducer(updateListItem, []);
|
||||
const [validationPools, dispatchValidationPool] = useReducer(updateListItem, []);
|
||||
const [availabilityStakes, dispatchAvailabilityStake] = useReducer(updateListItem, []);
|
||||
const [workRequests, dispatchWorkRequest] = useReducer(updateListItem, []);
|
||||
|
||||
// const watchReputationToken = useCallback(async () => {
|
||||
// await provider.request({
|
||||
|
@ -48,73 +75,68 @@ function App() {
|
|||
// });
|
||||
// }, [provider]);
|
||||
|
||||
const getPoolStatus = (pool) => {
|
||||
if (pool.resolved) {
|
||||
return pool.outcome ? 'Accepted' : 'Rejected';
|
||||
}
|
||||
const endDate = new Date(Number(pool.endTime) * 1000);
|
||||
return new Date() < endDate ? 'In Progress' : 'Ready to Evaluate';
|
||||
};
|
||||
|
||||
const getRequestStatus = (request) => {
|
||||
switch (Number(request.status)) {
|
||||
case 0:
|
||||
return 'Requested';
|
||||
case 1:
|
||||
return 'Evidence Submitted';
|
||||
case 2:
|
||||
return 'Approval Submitted';
|
||||
case 3:
|
||||
return 'Complete';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
// In this effect, we initialize everything and add contract event listeners.
|
||||
// TODO: Refactor -- make separate, functional components?
|
||||
useEffect(() => {
|
||||
if (!provider || !chainId || !account) return;
|
||||
if (!provider || !chainId || !account || balance === undefined) return;
|
||||
if (!contracts[chainId]) return;
|
||||
|
||||
console.log('INITIALIZATION EFFECT', {
|
||||
provider, chainId, account,
|
||||
});
|
||||
|
||||
const web3 = new Web3(provider);
|
||||
const DAOContract = new web3.eth.Contract(DAOArtifact.abi, contracts[chainId].DAO);
|
||||
const work1Contract = new web3.eth.Contract(work1Artifact.abi, contracts[chainId].Work1);
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- BEGIN FETCHERS ------------------------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
const fetchPrice = async () => {
|
||||
const fetchedPrice = await work1Contract.methods.price().call();
|
||||
setWork1Price(web3.utils.fromWei(fetchedPrice, 'ether'));
|
||||
};
|
||||
|
||||
const fetchReputation = async () => {
|
||||
reputation.current = Number(await DAOContract.methods.balanceOf(account).call());
|
||||
setTotalReputation(await DAOContract.methods.totalSupply().call());
|
||||
setReputation(Number(await DAOContract.methods.balanceOf(account).call()));
|
||||
setTotalReputation(Number(await DAOContract.methods.totalSupply().call()));
|
||||
};
|
||||
|
||||
const fetchPost = async (postIndex) => {
|
||||
const p = await DAOContract.methods.posts(postIndex).call();
|
||||
p.id = Number(p.id);
|
||||
dispatchPost(p);
|
||||
return p;
|
||||
};
|
||||
|
||||
const fetchPosts = async () => {
|
||||
const count = await DAOContract.methods.postCount().call();
|
||||
const promises = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
promises.push(DAOContract.methods.posts(i).call());
|
||||
promises.push(fetchPost(i));
|
||||
}
|
||||
const fetchedPosts = await Promise.all(promises);
|
||||
setPosts(fetchedPosts);
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
const stake = async (poolIndex, amount, inFavor) => {
|
||||
console.log(`Attempting to stake ${amount} ${inFavor ? 'for' : 'against'} pool ${poolIndex}`);
|
||||
await DAOContract.methods.stake(poolIndex, amount, inFavor).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
const fetchValidationPool = async (poolIndex) => {
|
||||
console.trace('fetchValidationPool');
|
||||
const pool = await DAOContract.methods.validationPools(poolIndex).call();
|
||||
pool.id = Number(pool.id);
|
||||
pool.timeRemaining = new Date(Number(pool.endTime) * 1000) - new Date();
|
||||
pool.status = getPoolStatus(pool);
|
||||
dispatchValidationPool(pool);
|
||||
|
||||
// Since this is the result we expect from the server, we preemptively set it here.
|
||||
// We can let this value be negative -- this would just mean we'll be getting
|
||||
// at least one error from the server, and a corrected reputation.
|
||||
reputation.current = Number(reputation.current) - Number(stake);
|
||||
// When remaing time expires, we want to update the status for this pool
|
||||
if (pool.timeRemaining > 0) {
|
||||
setTimeout(() => {
|
||||
pool.timeRemaining = 0;
|
||||
pool.status = getPoolStatus(pool);
|
||||
dispatchValidationPool(pool);
|
||||
}, pool.timeRemaining);
|
||||
}
|
||||
};
|
||||
|
||||
const stakeAllInFavor = (poolIndex) => stake(poolIndex, reputation.current, true);
|
||||
|
||||
const fetchValidationPools = async () => {
|
||||
// TODO: Pagination
|
||||
// TODO: Memoization
|
||||
|
@ -122,71 +144,57 @@ function App() {
|
|||
const count = await DAOContract.methods.validationPoolCount().call();
|
||||
const promises = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
promises.push(DAOContract.methods.validationPools(i).call());
|
||||
// promises.push(DAOContract.methods.validationPools(i).call());
|
||||
promises.push(fetchValidationPool(i));
|
||||
}
|
||||
const pools = (await Promise.all(promises)).map((p) => {
|
||||
const pool = p;
|
||||
pool.status = getPoolStatus(pool);
|
||||
const timeRemaining = new Date(Number(pool.endTime) * 1000) - new Date();
|
||||
if (timeRemaining > 0 && !pool.resolved && reputation.current
|
||||
&& !stakedPools.current.includes(pool.id)) {
|
||||
// Naievely stake all reputation that this validation pool is valid.
|
||||
// This is the greediest possible strategy.
|
||||
// Staking reputation transfers it, thus it's important we update our reputation
|
||||
// locally before hearing back from the server -- since blockchains are slow.
|
||||
// Note that this means refresing the page will re-send any pending staking operations.
|
||||
stakeAllInFavor(pool.id);
|
||||
stakedPools.current = stakedPools.current.concat(pool.id);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
// TODO: When remaing time expires, we want to update the status for this pool
|
||||
// if (timeRemaining > 0) {
|
||||
// setTimeout(() => {
|
||||
// pool.status = getStatus(pool);
|
||||
// setValidationPools((currentPools) => {
|
||||
// const newPools = currentPools;
|
||||
// newPools[pool.id] = pool;
|
||||
// return newPools;
|
||||
// });
|
||||
// console.log(`attepted to update pool status: ${pool.status}`);
|
||||
// }, timeRemaining);
|
||||
// }
|
||||
|
||||
return pool;
|
||||
const fetchAvailabilityStake = async (stakeIndex) => {
|
||||
const s = await work1Contract.methods.stakes(stakeIndex).call();
|
||||
Object.assign(s, {
|
||||
id: Number(stakeIndex),
|
||||
currentUserIsWorker: () => s.worker.toLowerCase() === account.toString().toLowerCase(),
|
||||
});
|
||||
setValidationPools(pools);
|
||||
dispatchAvailabilityStake(s);
|
||||
return s;
|
||||
};
|
||||
|
||||
const fetchAvailabilityStakes = async () => {
|
||||
const count = await work1Contract.methods.stakeCount().call();
|
||||
const promises = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
promises.push(work1Contract.methods.stakes(i).call());
|
||||
promises.push(fetchAvailabilityStake(i));
|
||||
}
|
||||
const fetchedStakes = (await Promise.all(promises)).map((x, index) => {
|
||||
Object.assign(x, { id: index });
|
||||
return x;
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
const fetchWorkRequest = async (requestIndex) => {
|
||||
const r = await work1Contract.methods.requests(requestIndex).call();
|
||||
Object.assign(r, {
|
||||
id: Number(requestIndex),
|
||||
statusString: getRequestStatus(r),
|
||||
feeEther: web3.utils.fromWei(r.fee, 'ether'),
|
||||
currentUserIsCustomer: () => r.customer.toLowerCase()
|
||||
=== account.toString().toLowerCase(),
|
||||
});
|
||||
setAvailabilityStakes(fetchedStakes);
|
||||
dispatchWorkRequest(r);
|
||||
return r;
|
||||
};
|
||||
|
||||
const fetchWorkRequests = async () => {
|
||||
const count = await work1Contract.methods.requestCount().call();
|
||||
const promises = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
promises.push(work1Contract.methods.requests(i).call());
|
||||
promises.push(fetchWorkRequest(i));
|
||||
}
|
||||
const fetchedRequests = (await Promise.all(promises)).map((x, index) => {
|
||||
Object.assign(x, {
|
||||
id: index,
|
||||
statusString: getRequestStatus(x),
|
||||
feeEther: web3.utils.fromWei(x.fee, 'ether'),
|
||||
});
|
||||
return x;
|
||||
});
|
||||
setWorkRequests(fetchedRequests);
|
||||
await Promise.all(promises);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- END FETCHERS --------------------------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
fetchPrice();
|
||||
fetchReputation();
|
||||
fetchPosts();
|
||||
|
@ -199,20 +207,36 @@ function App() {
|
|||
|
||||
DAOContract.events.PostAdded({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
console.log('event: post added', event);
|
||||
fetchPosts();
|
||||
fetchPost(event.returnValues.postIndex);
|
||||
});
|
||||
|
||||
DAOContract.events.ValidationPoolInitiated({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
console.log('event: validation pool initiated', event);
|
||||
fetchValidationPools();
|
||||
fetchValidationPool(event.returnValues.poolIndex);
|
||||
});
|
||||
|
||||
DAOContract.events.ValidationPoolResolved({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
console.log('event: validation pool resolved', event);
|
||||
fetchReputation();
|
||||
fetchValidationPools();
|
||||
fetchValidationPool(event.returnValues.poolIndex);
|
||||
});
|
||||
}, [provider, account, chainId, reputation]);
|
||||
|
||||
work1Contract.events.WorkAssigned({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
console.log('event: work assigned', event);
|
||||
const r = fetchWorkRequest(event.returnValues.requestIndex);
|
||||
fetchAvailabilityStake(r.stakeIndex);
|
||||
});
|
||||
|
||||
work1Contract.events.WorkEvidenceSubmitted({ fromBlock: 'latest' }).on('data', (event) => {
|
||||
console.log('event: work evidence submitted', event);
|
||||
fetchWorkRequest(event.returnValues.requestIndex);
|
||||
});
|
||||
}, [provider, account, chainId, balance, setReputation, dispatchAvailabilityStake,
|
||||
dispatchValidationPool, dispatchWorkRequest]);
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- END MAIN INITIALIZION EFFECT ----------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
useEffect(() => {
|
||||
if (!provider || balance === undefined) return;
|
||||
|
@ -236,43 +260,63 @@ function App() {
|
|||
}
|
||||
};
|
||||
|
||||
const addPost = async () => {
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- BEGIN UI ACTIONS ----------------------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
const addPost = useCallback(async () => {
|
||||
await DAO.methods.addPost(account).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
};
|
||||
}, [DAO, account]);
|
||||
|
||||
const initiateValidationPool = async (postIndex, poolDuration) => {
|
||||
const initiateValidationPool = useCallback(async (postIndex, poolDuration) => {
|
||||
await DAO.methods.initiateValidationPool(postIndex, poolDuration ?? 3600).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
value: 100,
|
||||
});
|
||||
};
|
||||
}, [DAO, account]);
|
||||
|
||||
const evaluateOutcome = async (poolIndex) => {
|
||||
const stake = useCallback(async (poolIndex, amount, inFavor) => {
|
||||
console.log(`Attempting to stake ${amount} ${inFavor ? 'for' : 'against'} pool ${poolIndex}`);
|
||||
await DAO.methods.stake(poolIndex, amount, inFavor).send({
|
||||
from: account,
|
||||
gas: 999999,
|
||||
});
|
||||
|
||||
// Since this is the result we expect from the server, we preemptively set it here.
|
||||
// We can let this value be negative -- this would just mean we'll be getting
|
||||
// at least one error from the server, and a corrected reputation.
|
||||
setReputation((current) => current - stake);
|
||||
}, [DAO, account, setReputation]);
|
||||
|
||||
const stakeAllInFavor = useCallback(async (poolIndex) => {
|
||||
await stake(poolIndex, reputation, true);
|
||||
}, [stake, reputation]);
|
||||
|
||||
const evaluateOutcome = useCallback(async (poolIndex) => {
|
||||
await DAO.methods.evaluateOutcome(poolIndex).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
};
|
||||
}, [DAO, account]);
|
||||
|
||||
const stakeAvailability = useCallback(async () => {
|
||||
const duration = 300; // 5 minutes
|
||||
const target = contracts[chainId].Work1;
|
||||
await DAO.methods.stakeAvailability(target, reputation.current, duration).send({
|
||||
await DAO.methods.stakeAvailability(target, reputation, duration).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
// Note that as with validation pool stakes, we should keep track locally of our reputation
|
||||
reputation.current = 0;
|
||||
}, [DAO, account, reputation, chainId]);
|
||||
setReputation(0);
|
||||
}, [DAO, account, chainId, reputation, setReputation]);
|
||||
|
||||
const requestWork = useCallback(async () => {
|
||||
const web3 = new Web3(provider);
|
||||
const priceWei = BigInt(web3.utils.toWei(work1Price, 'ether'));
|
||||
console.log('requestWork, ');
|
||||
await work1.methods.requestWork().send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
|
@ -280,6 +324,31 @@ function App() {
|
|||
});
|
||||
}, [provider, work1, account, work1Price]);
|
||||
|
||||
const submitWorkEvidence = useCallback(async (requestIndex) => {
|
||||
await work1.methods.submitWorkEvidence(requestIndex).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
}, [work1, account]);
|
||||
|
||||
const submitWorkApproval = useCallback(async (requestIndex) => {
|
||||
await work1.methods.submitWorkApproval(requestIndex, true).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
}, [work1, account]);
|
||||
|
||||
const submitWorkDisapproval = useCallback(async (requestIndex) => {
|
||||
await work1.methods.submitWorkApproval(requestIndex, false).send({
|
||||
from: account,
|
||||
gas: 1000000,
|
||||
});
|
||||
}, [work1, account]);
|
||||
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
/* --------------------------- END UI ACTIONS ------------------------------------- */
|
||||
/* -------------------------------------------------------------------------------- */
|
||||
|
||||
return (
|
||||
<>
|
||||
{!connected && <Button onClick={() => connect()}>Connect</Button>}
|
||||
|
@ -302,7 +371,7 @@ function App() {
|
|||
{`Balance: ${balanceEther} ETH`}
|
||||
</div>
|
||||
<div>
|
||||
{`Your REP: ${reputation.current}`}
|
||||
{`Your REP: ${reputation}`}
|
||||
</div>
|
||||
<div>
|
||||
{`Total REP: ${totalReputation}`}
|
||||
|
@ -322,7 +391,7 @@ function App() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{posts.map((post) => (
|
||||
{posts.filter((x) => !!x).map((post) => (
|
||||
<tr key={post.id}>
|
||||
<td>{post.id.toString()}</td>
|
||||
<td>{post.author}</td>
|
||||
|
@ -371,7 +440,7 @@ function App() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{validationPools.map((pool) => (
|
||||
{validationPools.filter((x) => !!x).map((pool) => (
|
||||
<tr key={pool.id}>
|
||||
<td>{pool.id.toString()}</td>
|
||||
<td>{pool.postIndex.toString()}</td>
|
||||
|
@ -381,7 +450,15 @@ function App() {
|
|||
<td>{pool.stakeCount.toString()}</td>
|
||||
<td>{pool.status}</td>
|
||||
<td>
|
||||
{!pool.resolved && (
|
||||
{!pool.resolved && reputation > 0 && pool.timeRemaining > 0 && (
|
||||
<>
|
||||
<Button onClick={() => stakeAllInFavor(pool.id)}>
|
||||
Stake
|
||||
</Button>
|
||||
{' '}
|
||||
</>
|
||||
)}
|
||||
{!pool.resolved && pool.timeRemaining <= 0 && (
|
||||
<Button onClick={() => evaluateOutcome(pool.id)}>
|
||||
Evaluate Outcome
|
||||
</Button>
|
||||
|
@ -421,14 +498,14 @@ function App() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{availabilityStakes.map((stake) => (
|
||||
<tr key={stake.id}>
|
||||
<td>{stake.id.toString()}</td>
|
||||
<td>{stake.worker.toString()}</td>
|
||||
<td>{stake.amount.toString()}</td>
|
||||
<td>{new Date(Number(stake.endTime) * 1000).toLocaleString()}</td>
|
||||
<td>{stake.assigned.toString()}</td>
|
||||
<td>{stake.reclaimed.toString()}</td>
|
||||
{availabilityStakes.filter((x) => !!x).map((s) => (
|
||||
<tr key={s.id}>
|
||||
<td>{s.id.toString()}</td>
|
||||
<td>{s.worker.toString()}</td>
|
||||
<td>{s.amount.toString()}</td>
|
||||
<td>{new Date(Number(s.endTime) * 1000).toLocaleString()}</td>
|
||||
<td>{s.assigned.toString()}</td>
|
||||
<td>{s.reclaimed.toString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
@ -453,10 +530,11 @@ function App() {
|
|||
<th>Stake ID</th>
|
||||
<th>Approval</th>
|
||||
<th>Pool ID</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{workRequests.map((request) => (
|
||||
{workRequests.filter((x) => !!x).map((request) => (
|
||||
<tr key={request.id}>
|
||||
<td>{request.id.toString()}</td>
|
||||
<td>{request.customer.toString()}</td>
|
||||
|
@ -469,6 +547,26 @@ function App() {
|
|||
<td>{request.stakeIndex.toString()}</td>
|
||||
<td>{request.approval.toString()}</td>
|
||||
<td>{request.poolIndex.toString()}</td>
|
||||
<td>
|
||||
{availabilityStakes.length > 0
|
||||
&& availabilityStakes[Number(request.stakeIndex)]?.currentUserIsWorker()
|
||||
&& Number(request.status) === 0 && (
|
||||
<Button onClick={() => submitWorkEvidence(request.id)}>
|
||||
Submit Work Evidence
|
||||
</Button>
|
||||
)}
|
||||
{request.currentUserIsCustomer()
|
||||
&& Number(request.status) === 1 && (
|
||||
<>
|
||||
<Button onClick={() => submitWorkApproval(request.id)}>
|
||||
Submit Approval
|
||||
</Button>
|
||||
<Button onClick={() => submitWorkDisapproval(request.id)}>
|
||||
Submit Dispproval
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
Loading…
Reference in New Issue