Compare commits
No commits in common. "37fd387cf33db5cd73cfdc8ba6facfa800a36496" and "b175a34b9f951ddd652b4339e9aa528be5a0677e" have entirely different histories.
37fd387cf3
...
b175a34b9f
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./core/DAO.sol";
|
||||
import "./interfaces/IAcceptAvailability.sol";
|
||||
|
||||
contract Availability is IAcceptAvailability, DAOContract {
|
||||
struct AvailabilityStake {
|
||||
address worker;
|
||||
uint256 amount;
|
||||
uint endTime;
|
||||
bool assigned;
|
||||
}
|
||||
|
||||
mapping(uint => AvailabilityStake) public stakes;
|
||||
uint public stakeCount;
|
||||
|
||||
event AvailabilityStaked(uint stakeIndex);
|
||||
|
||||
constructor(DAO dao) DAOContract(dao) {}
|
||||
|
||||
/// Accept availability stakes as reputation token transfer
|
||||
function acceptAvailability(
|
||||
address sender,
|
||||
uint256 amount,
|
||||
uint duration
|
||||
) external {
|
||||
require(
|
||||
msg.sender == address(dao),
|
||||
"acceptAvailability must only be called by DAO contract"
|
||||
);
|
||||
require(amount > 0, "No stake provided");
|
||||
uint stakeIndex = stakeCount++;
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
stake.worker = sender;
|
||||
stake.amount = amount;
|
||||
stake.endTime = block.timestamp + duration;
|
||||
emit AvailabilityStaked(stakeIndex);
|
||||
}
|
||||
|
||||
function extendAvailability(uint stakeIndex, uint duration) external {
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
require(
|
||||
msg.sender == stake.worker,
|
||||
"Worker can only extend their own availability stake"
|
||||
);
|
||||
require(!stake.assigned, "Stake has already been assigned work");
|
||||
if (block.timestamp > stake.endTime) {
|
||||
stake.endTime = block.timestamp + duration;
|
||||
} else {
|
||||
stake.endTime = stake.endTime + duration;
|
||||
}
|
||||
emit AvailabilityStaked(stakeIndex);
|
||||
}
|
||||
|
||||
/// Select a worker randomly from among the available workers, weighted by amount staked
|
||||
function randomWeightedSelection() internal view returns (uint stakeIndex) {
|
||||
uint totalStakes;
|
||||
for (uint i = 0; i < stakeCount; i++) {
|
||||
if (stakes[i].assigned) continue;
|
||||
if (block.timestamp > stakes[i].endTime) continue;
|
||||
totalStakes += stakes[i].amount;
|
||||
}
|
||||
require(totalStakes > 0, "No available worker stakes");
|
||||
uint select = block.prevrandao % totalStakes;
|
||||
uint acc;
|
||||
for (uint i = 0; i < stakeCount; i++) {
|
||||
if (stakes[i].assigned) continue;
|
||||
if (block.timestamp > stakes[i].endTime) continue;
|
||||
acc += stakes[i].amount;
|
||||
if (acc > select) {
|
||||
stakeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a random available worker
|
||||
function assignWork() internal returns (uint stakeIndex) {
|
||||
stakeIndex = randomWeightedSelection();
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
stake.assigned = true;
|
||||
}
|
||||
}
|
|
@ -59,17 +59,22 @@ contract Proposals is DAOContract, IOnValidate {
|
|||
|
||||
// TODO receive : we want to be able to accept refunds from validation pools
|
||||
|
||||
/// Submit a post as a proposal. DAO.addPost should be called before this.
|
||||
function propose(
|
||||
string calldata postId,
|
||||
string calldata contentId,
|
||||
address author,
|
||||
uint[3] calldata durations,
|
||||
bool callbackOnAccepted,
|
||||
bytes calldata callbackData
|
||||
) external payable returns (uint proposalIndex) {
|
||||
// TODO: Take citations as a parameter
|
||||
Citation[] memory emptyCitations;
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, author);
|
||||
dao.addPost(authors, contentId, emptyCitations);
|
||||
proposalIndex = proposalCount++;
|
||||
Proposal storage proposal = proposals[proposalIndex];
|
||||
proposal.sender = msg.sender;
|
||||
proposal.postId = postId;
|
||||
proposal.postId = contentId;
|
||||
proposal.startTime = block.timestamp;
|
||||
proposal.referenda[0].duration = durations[0];
|
||||
proposal.referenda[1].duration = durations[1];
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./WorkContract.sol";
|
||||
import "./Rollup.sol";
|
||||
|
||||
abstract contract RollableWorkContract is WorkContract {
|
||||
Rollup immutable rollupContract;
|
||||
|
||||
constructor(
|
||||
DAO dao,
|
||||
Proposals proposalsContract,
|
||||
uint price,
|
||||
Rollup rollupContract_
|
||||
) WorkContract(dao, proposalsContract, price) {
|
||||
rollupContract = rollupContract_;
|
||||
}
|
||||
|
||||
/// Accept work approval/disapproval from customer
|
||||
function submitWorkApproval(
|
||||
uint requestIndex,
|
||||
bool approval
|
||||
) external override {
|
||||
WorkRequest storage request = requests[requestIndex];
|
||||
require(
|
||||
request.status == WorkStatus.EvidenceSubmitted,
|
||||
"Status must be EvidenceSubmitted"
|
||||
);
|
||||
AvailabilityStake storage stake = stakes[request.stakeIndex];
|
||||
request.status = WorkStatus.ApprovalSubmitted;
|
||||
request.approval = approval;
|
||||
emit WorkApprovalSubmitted(requestIndex, approval);
|
||||
|
||||
// Make work evidence post
|
||||
Author[] memory authors = new Author[](1);
|
||||
authors[0] = Author(1000000, stake.worker);
|
||||
|
||||
// TODO: send post and fee to rollup contract
|
||||
dao.forwardAllowance(
|
||||
stake.worker,
|
||||
address(rollupContract),
|
||||
stake.amount
|
||||
);
|
||||
payable(address(rollupContract)).transfer(request.fee);
|
||||
rollupContract.addItem(
|
||||
stake.worker,
|
||||
stake.amount,
|
||||
request.fee,
|
||||
request.evidenceContentId
|
||||
);
|
||||
|
||||
// dao.addPost(authors, request.evidenceContentId, request.citations);
|
||||
// Initiate validation pool
|
||||
uint poolIndex = dao.initiateValidationPool{value: request.fee}(
|
||||
request.evidenceContentId,
|
||||
POOL_DURATION,
|
||||
[uint256(1), uint256(3)],
|
||||
[uint256(1), uint256(2)],
|
||||
100,
|
||||
true,
|
||||
false,
|
||||
""
|
||||
);
|
||||
// We have an approval from stake.worker to transfer up to stake.amount
|
||||
dao.delegatedStakeOnValidationPool(
|
||||
poolIndex,
|
||||
stake.worker,
|
||||
stake.amount,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./core/DAO.sol";
|
||||
import "./Availability.sol";
|
||||
|
||||
contract Rollup is Availability {
|
||||
constructor(DAO dao) Availability(dao) {}
|
||||
|
||||
struct BatchItem {
|
||||
address worker;
|
||||
uint stakeAmount;
|
||||
uint fee;
|
||||
string evidenceContentId;
|
||||
}
|
||||
|
||||
mapping(uint => BatchItem) items;
|
||||
uint itemCount;
|
||||
address batchWorker;
|
||||
uint batchWorkerStakeIndex;
|
||||
|
||||
function addItem(
|
||||
address worker,
|
||||
uint stakeAmount,
|
||||
uint fee,
|
||||
string calldata evidenceContentId
|
||||
) public {
|
||||
BatchItem storage item = items[itemCount++];
|
||||
item.worker = worker;
|
||||
item.stakeAmount = stakeAmount;
|
||||
item.fee = fee;
|
||||
item.evidenceContentId = evidenceContentId;
|
||||
}
|
||||
|
||||
function submitBatch(
|
||||
string calldata batchPostId,
|
||||
uint poolDuration
|
||||
) public {
|
||||
if (batchWorker != address(0)) {
|
||||
require(
|
||||
msg.sender == batchWorker,
|
||||
"Batch result must be submitted by current batch worker"
|
||||
);
|
||||
}
|
||||
// initiate a validation pool for this batch
|
||||
uint fee;
|
||||
for (uint i = 0; i < itemCount; i++) {
|
||||
fee += items[i].fee;
|
||||
}
|
||||
uint poolIndex = dao.initiateValidationPool{value: fee}(
|
||||
batchPostId,
|
||||
poolDuration,
|
||||
[uint256(1), uint256(3)],
|
||||
[uint256(1), uint256(2)],
|
||||
100,
|
||||
true,
|
||||
false,
|
||||
""
|
||||
);
|
||||
// Include all the availability stakes from the batched work
|
||||
for (uint i = 0; i < itemCount; i++) {
|
||||
dao.delegatedStakeOnValidationPool(
|
||||
poolIndex,
|
||||
items[i].worker,
|
||||
items[i].stakeAmount,
|
||||
true
|
||||
);
|
||||
}
|
||||
// Include availability stakes from the batch worker
|
||||
dao.delegatedStakeOnValidationPool(
|
||||
poolIndex,
|
||||
batchWorker,
|
||||
stakes[batchWorkerStakeIndex].amount,
|
||||
true
|
||||
);
|
||||
// Reset item count so we can start the next batch
|
||||
itemCount = 0;
|
||||
// Select the next worker
|
||||
batchWorkerStakeIndex = assignWork();
|
||||
batchWorker = stakes[batchWorkerStakeIndex].worker;
|
||||
}
|
||||
}
|
|
@ -3,11 +3,22 @@ pragma solidity ^0.8.24;
|
|||
|
||||
import "./core/DAO.sol";
|
||||
import "./core/Forum.sol";
|
||||
import "./Availability.sol";
|
||||
import "./Proposals.sol";
|
||||
import "./interfaces/IAcceptAvailability.sol";
|
||||
import "./interfaces/IOnProposalAccepted.sol";
|
||||
|
||||
abstract contract WorkContract is Availability, IOnProposalAccepted {
|
||||
abstract contract WorkContract is
|
||||
DAOContract,
|
||||
IAcceptAvailability,
|
||||
IOnProposalAccepted
|
||||
{
|
||||
struct AvailabilityStake {
|
||||
address worker;
|
||||
uint256 amount;
|
||||
uint endTime;
|
||||
bool assigned;
|
||||
}
|
||||
|
||||
enum WorkStatus {
|
||||
Requested,
|
||||
EvidenceSubmitted,
|
||||
|
@ -35,11 +46,14 @@ abstract contract WorkContract is Availability, IOnProposalAccepted {
|
|||
uint public price;
|
||||
mapping(uint => PriceProposal) public priceProposals;
|
||||
uint public priceProposalCount;
|
||||
mapping(uint => AvailabilityStake) public stakes;
|
||||
uint public stakeCount;
|
||||
mapping(uint => WorkRequest) public requests;
|
||||
uint public requestCount;
|
||||
|
||||
uint constant POOL_DURATION = 20;
|
||||
|
||||
event AvailabilityStaked(uint stakeIndex);
|
||||
event WorkAssigned(uint requestIndex, uint stakeIndex);
|
||||
event WorkEvidenceSubmitted(uint requestIndex);
|
||||
event WorkApprovalSubmitted(uint requestIndex, bool approval);
|
||||
|
@ -50,11 +64,71 @@ abstract contract WorkContract is Availability, IOnProposalAccepted {
|
|||
DAO dao,
|
||||
Proposals proposalsContract_,
|
||||
uint price_
|
||||
) Availability(dao) {
|
||||
) DAOContract(dao) {
|
||||
price = price_;
|
||||
proposalsContract = proposalsContract_;
|
||||
}
|
||||
|
||||
/// Accept availability stakes as reputation token transfer
|
||||
function acceptAvailability(
|
||||
address sender,
|
||||
uint256 amount,
|
||||
uint duration
|
||||
) external {
|
||||
require(amount > 0, "No stake provided");
|
||||
uint stakeIndex = stakeCount++;
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
stake.worker = sender;
|
||||
stake.amount = amount;
|
||||
stake.endTime = block.timestamp + duration;
|
||||
emit AvailabilityStaked(stakeIndex);
|
||||
}
|
||||
|
||||
function extendAvailability(uint stakeIndex, uint duration) external {
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
require(
|
||||
msg.sender == stake.worker,
|
||||
"Worker can only extend their own availability stake"
|
||||
);
|
||||
require(!stake.assigned, "Stake has already been assigned work");
|
||||
if (block.timestamp > stake.endTime) {
|
||||
stake.endTime = block.timestamp + duration;
|
||||
} else {
|
||||
stake.endTime = stake.endTime + duration;
|
||||
}
|
||||
emit AvailabilityStaked(stakeIndex);
|
||||
}
|
||||
|
||||
/// Select a worker randomly from among the available workers, weighted by amount staked
|
||||
function randomWeightedSelection() internal view returns (uint stakeIndex) {
|
||||
uint totalStakes;
|
||||
for (uint i = 0; i < stakeCount; i++) {
|
||||
if (stakes[i].assigned) continue;
|
||||
if (block.timestamp > stakes[i].endTime) continue;
|
||||
totalStakes += stakes[i].amount;
|
||||
}
|
||||
require(totalStakes > 0, "No available worker stakes");
|
||||
uint select = block.prevrandao % totalStakes;
|
||||
uint acc;
|
||||
for (uint i = 0; i < stakeCount; i++) {
|
||||
if (stakes[i].assigned) continue;
|
||||
if (block.timestamp > stakes[i].endTime) continue;
|
||||
acc += stakes[i].amount;
|
||||
if (acc > select) {
|
||||
stakeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a random available worker
|
||||
function assignWork(uint requestIndex) internal returns (uint stakeIndex) {
|
||||
stakeIndex = randomWeightedSelection();
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
stake.assigned = true;
|
||||
emit WorkAssigned(requestIndex, stakeIndex);
|
||||
}
|
||||
|
||||
/// Accept work request with fee
|
||||
function requestWork(string calldata requestContentId) external payable {
|
||||
require(msg.value >= price, "Insufficient fee");
|
||||
|
@ -62,9 +136,8 @@ abstract contract WorkContract is Availability, IOnProposalAccepted {
|
|||
WorkRequest storage request = requests[requestIndex];
|
||||
request.customer = msg.sender;
|
||||
request.fee = msg.value;
|
||||
request.stakeIndex = assignWork();
|
||||
request.stakeIndex = assignWork(requestIndex);
|
||||
request.requestContentId = requestContentId;
|
||||
emit WorkAssigned(requestIndex, request.stakeIndex);
|
||||
}
|
||||
|
||||
/// Accept work evidence from worker
|
||||
|
@ -129,11 +202,9 @@ abstract contract WorkContract is Availability, IOnProposalAccepted {
|
|||
);
|
||||
}
|
||||
|
||||
/// Initiate a new proposal to change the price for this work contract.
|
||||
/// This takes a postId; DAO.addPost should be called before or concurrently with this.
|
||||
function proposeNewPrice(
|
||||
uint newPrice,
|
||||
string calldata postId,
|
||||
string calldata contentId,
|
||||
uint[3] calldata durations
|
||||
) external payable {
|
||||
uint priceProposalIndex = priceProposalCount++;
|
||||
|
@ -143,7 +214,13 @@ abstract contract WorkContract is Availability, IOnProposalAccepted {
|
|||
priceProposal.price = newPrice;
|
||||
priceProposal.proposalIndex = proposalsContract.propose{
|
||||
value: msg.value
|
||||
}(postId, durations, true, abi.encode(priceProposalIndex));
|
||||
}(
|
||||
contentId,
|
||||
msg.sender,
|
||||
durations,
|
||||
true,
|
||||
abi.encode(priceProposalIndex)
|
||||
);
|
||||
emit PriceChangeProposed(priceProposalIndex);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,13 +24,4 @@ contract Reputation is ERC20("Reputation", "REP") {
|
|||
) public pure override returns (bool) {
|
||||
revert("REP transfer is not allowed");
|
||||
}
|
||||
|
||||
function forwardAllowance(
|
||||
address owner,
|
||||
address to,
|
||||
uint256 amount
|
||||
) public {
|
||||
_spendAllowance(owner, msg.sender, amount);
|
||||
_approve(owner, to, allowance(owner, to) + amount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,8 +79,7 @@ describe('Proposal', () => {
|
|||
} = await loadFixture(deploy));
|
||||
|
||||
const emptyCallbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.addPost([{ authorAddress: account1, weightPPM: 1000000 }], 'proposal-content-id', []);
|
||||
await proposals.propose('proposal-content-id', [20, 20, 20], false, emptyCallbackData, { value: 100 });
|
||||
await proposals.propose('proposal-content-id', account1, [20, 20, 20], false, emptyCallbackData, { value: 100 });
|
||||
expect(await proposals.proposalCount()).to.equal(1);
|
||||
proposal = await proposals.proposals(0);
|
||||
expect(proposal.postId).to.equal('proposal-content-id');
|
||||
|
|
|
@ -78,10 +78,6 @@ describe('Work1', () => {
|
|||
await expect(dao.stakeAvailability(work1.target, 0, STAKE_DURATION)).to.be.revertedWith('No stake provided');
|
||||
});
|
||||
|
||||
it('should not be able to call acceptAvailability directly', async () => {
|
||||
await expect(work1.acceptAvailability(account1, 50, STAKE_DURATION)).to.be.revertedWith('acceptAvailability must only be called by DAO contract');
|
||||
});
|
||||
|
||||
it('should be able to extend the duration of an availability stake before it expires', async () => {
|
||||
await time.increase(STAKE_DURATION / 2);
|
||||
await expect(work1.extendAvailability(0, STAKE_DURATION)).to.emit(work1, 'AvailabilityStaked').withArgs(0);
|
||||
|
|
|
@ -28,7 +28,7 @@ const getProposalStatus = (proposal) => {
|
|||
|
||||
function Proposals() {
|
||||
const {
|
||||
provider, chainId, account, reputation, DAORef,
|
||||
provider, chainId, account, reputation,
|
||||
} = useContext(Web3Context);
|
||||
const [proposals, dispatchProposal] = useList();
|
||||
const proposalsContract = useRef();
|
||||
|
@ -105,15 +105,9 @@ function Proposals() {
|
|||
const onSubmitProposal = useCallback(async (post) => {
|
||||
const web3 = new Web3(provider);
|
||||
const emptyCallbackData = web3.eth.abi.encodeParameter('bytes', '0x00');
|
||||
// First add a post for this proposal
|
||||
await DAORef.current.methods.addPost(
|
||||
[{ authorAddress: account, weightPPM: 1000000 }],
|
||||
post.hash,
|
||||
[], // TODO: Proposal can cite posts from matrix, semantic scholar, etc
|
||||
);
|
||||
// Now submit the post as a proposal
|
||||
await proposalsContract.current.methods.propose(
|
||||
post.hash,
|
||||
account,
|
||||
durations,
|
||||
false,
|
||||
emptyCallbackData,
|
||||
|
@ -122,7 +116,7 @@ function Proposals() {
|
|||
gas: 1000000,
|
||||
value: 10000,
|
||||
});
|
||||
}, [provider, account, proposalsContract, durations, DAORef]);
|
||||
}, [provider, account, proposalsContract, durations]);
|
||||
|
||||
const handleAttest = useCallback(async (proposalIndex) => {
|
||||
await proposalsContract.current.methods.attest(proposalIndex, reputation).send({
|
||||
|
|
Loading…
Reference in New Issue