refactor and stub for rollup

This commit is contained in:
Ladd Hoffman 2024-04-28 15:06:10 -05:00
parent b175a34b9f
commit 8e272bf2e8
5 changed files with 116 additions and 78 deletions

View File

@ -0,0 +1,86 @@
// 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);
event WorkAssigned(uint requestIndex, 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(uint requestIndex) internal returns (uint stakeIndex) {
stakeIndex = randomWeightedSelection();
AvailabilityStake storage stake = stakes[stakeIndex];
stake.assigned = true;
emit WorkAssigned(requestIndex, stakeIndex);
}
}

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.24;
import "./WorkContract.sol";
import "./Rollup.sol";
abstract contract RollableWorkContract is WorkContract {
constructor(
DAO dao,
Proposals proposalsContract,
uint price,
Rollup rollupContract_
) WorkContract(dao, proposalsContract, price) {}
}

View File

@ -0,0 +1,9 @@
// 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) {}
}

View File

@ -3,22 +3,11 @@ 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
DAOContract,
IAcceptAvailability,
IOnProposalAccepted
{
struct AvailabilityStake {
address worker;
uint256 amount;
uint endTime;
bool assigned;
}
abstract contract WorkContract is Availability, IOnProposalAccepted {
enum WorkStatus {
Requested,
EvidenceSubmitted,
@ -46,15 +35,11 @@ abstract contract WorkContract is
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);
event PriceChangeProposed(uint priceProposalIndex);
@ -64,71 +49,11 @@ abstract contract WorkContract is
DAO dao,
Proposals proposalsContract_,
uint price_
) DAOContract(dao) {
) Availability(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");

View File

@ -78,6 +78,10 @@ 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);