Add onboarding contract
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 37s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 37s
Details
This commit is contained in:
parent
4674a184ce
commit
591a2c3627
|
@ -3,6 +3,7 @@ pragma solidity ^0.8.24;
|
|||
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "./IAcceptAvailability.sol";
|
||||
import "./IOnValidate.sol";
|
||||
|
||||
import "hardhat/console.sol";
|
||||
|
||||
|
@ -26,12 +27,12 @@ struct ValidationPool {
|
|||
mapping(uint => Stake) stakes;
|
||||
uint stakeCount;
|
||||
uint256 fee;
|
||||
uint256 initialStakedFor;
|
||||
uint256 initialStakedAgainst;
|
||||
uint duration;
|
||||
uint endTime;
|
||||
bool resolved;
|
||||
bool outcome;
|
||||
bool callbackOnValidate;
|
||||
bytes callbackData;
|
||||
}
|
||||
|
||||
/// This contract must manage validation pools and reputation,
|
||||
|
@ -74,7 +75,9 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
/// TODO: Constrain duration to allowable range
|
||||
function initiateValidationPool(
|
||||
uint postIndex,
|
||||
uint duration
|
||||
uint duration,
|
||||
bool callbackOnValidate,
|
||||
bytes calldata callbackData
|
||||
) public payable returns (uint poolIndex) {
|
||||
require(msg.value > 0, "Fee is required to initiate validation pool");
|
||||
require(duration >= minDuration, "Duration is too short");
|
||||
|
@ -89,6 +92,9 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
pool.duration = duration;
|
||||
pool.endTime = block.timestamp + duration;
|
||||
pool.id = poolIndex;
|
||||
pool.callbackOnValidate = callbackOnValidate;
|
||||
pool.callbackData = callbackData;
|
||||
|
||||
// Because we need to stake part of the mited value for the pool an part against,
|
||||
// we mint two new tokens.
|
||||
// Here we assume a minting ratio of 1, and a stakeForAuthor ratio of 0.5
|
||||
|
@ -183,6 +189,10 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
// TODO: For efficiency this could be modified to hold the funds for recipients to withdraw
|
||||
payable(member).transfer(share);
|
||||
}
|
||||
// Callback if requested
|
||||
if (pool.callbackOnValidate) {
|
||||
IOnValidate(pool.sender).onValidate(votePasses, pool.callbackData);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transfer REP to a contract, and call that contract's receiveTransfer method
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
interface IOnValidate {
|
||||
function onValidate(bool votePasses, bytes calldata callbackData) external;
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import "./DAO.sol";
|
||||
import "./IAcceptAvailability.sol";
|
||||
import "./IOnValidate.sol";
|
||||
|
||||
struct AvailabilityStake {
|
||||
address worker;
|
||||
uint256 amount;
|
||||
uint endTime;
|
||||
bool assigned;
|
||||
bool reclaimed;
|
||||
}
|
||||
|
||||
enum WorkStatus {
|
||||
Requested,
|
||||
EvidenceSubmitted,
|
||||
ApprovalSubmitted,
|
||||
Complete
|
||||
}
|
||||
|
||||
struct WorkRequest {
|
||||
address customer;
|
||||
uint256 fee;
|
||||
WorkStatus status;
|
||||
uint stakeIndex;
|
||||
bool approval;
|
||||
uint reviewPoolIndex;
|
||||
uint onboardPoolIndex;
|
||||
}
|
||||
|
||||
contract Onboarding is IAcceptAvailability, IOnValidate {
|
||||
DAO immutable dao;
|
||||
uint public immutable price;
|
||||
mapping(uint => AvailabilityStake) public stakes;
|
||||
uint public stakeCount;
|
||||
mapping(uint => WorkRequest) public requests;
|
||||
uint public requestCount;
|
||||
|
||||
// TODO: Make parameters configurable
|
||||
uint constant POOL_DURATION = 1 days;
|
||||
|
||||
event AvailabilityStaked(uint stakeIndex);
|
||||
event WorkAssigned(address worker, uint requestIndex);
|
||||
event WorkEvidenceSubmitted(uint requestIndex);
|
||||
event WorkApprovalSubmitted(uint requestIndex, bool approval);
|
||||
|
||||
constructor(DAO dao_, uint price_) {
|
||||
dao = dao_;
|
||||
price = price_;
|
||||
}
|
||||
|
||||
/// 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.reclaimed, "Stake has already been reclaimed");
|
||||
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);
|
||||
}
|
||||
|
||||
function reclaimAvailability(uint stakeIndex) external {
|
||||
AvailabilityStake storage stake = stakes[stakeIndex];
|
||||
require(
|
||||
msg.sender == stake.worker,
|
||||
"Worker can only reclaim their own availability stake"
|
||||
);
|
||||
require(
|
||||
block.timestamp > stake.endTime,
|
||||
"Stake duration has not yet elapsed"
|
||||
);
|
||||
require(!stake.reclaimed, "Stake has already been reclaimed");
|
||||
require(!stake.assigned, "Stake has already been assigned work");
|
||||
stake.reclaimed = true;
|
||||
dao.transfer(msg.sender, stake.amount);
|
||||
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(stake.worker, requestIndex);
|
||||
}
|
||||
|
||||
/// Accept work request with fee
|
||||
function requestWork() external payable {
|
||||
require(msg.value >= price, "Insufficient fee");
|
||||
uint requestIndex = requestCount++;
|
||||
WorkRequest storage request = requests[requestIndex];
|
||||
request.customer = msg.sender;
|
||||
request.fee = msg.value;
|
||||
request.stakeIndex = assignWork(requestIndex);
|
||||
}
|
||||
|
||||
/// Accept work evidence from worker
|
||||
function submitWorkEvidence(uint requestIndex) external {
|
||||
WorkRequest storage request = requests[requestIndex];
|
||||
require(
|
||||
request.status == WorkStatus.Requested,
|
||||
"Status must be Requested"
|
||||
);
|
||||
AvailabilityStake storage stake = stakes[request.stakeIndex];
|
||||
require(
|
||||
stake.worker == msg.sender,
|
||||
"Worker can only submit evidence for work they are assigned"
|
||||
);
|
||||
request.status = WorkStatus.EvidenceSubmitted;
|
||||
emit WorkEvidenceSubmitted(requestIndex);
|
||||
}
|
||||
|
||||
/// Accept work approval/disapproval from customer
|
||||
function submitWorkApproval(uint requestIndex, bool approval) external {
|
||||
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;
|
||||
// Make work evidence post
|
||||
uint postIndex = dao.addPost(stake.worker);
|
||||
emit WorkApprovalSubmitted(requestIndex, approval);
|
||||
// Initiate validation pool
|
||||
request.reviewPoolIndex = dao.initiateValidationPool{
|
||||
value: request.fee - request.fee / 10
|
||||
}(postIndex, POOL_DURATION, true, abi.encode(requestIndex));
|
||||
}
|
||||
|
||||
/// Callback to be executed when review pool completes
|
||||
function onValidate(bool votePasses, bytes calldata callbackData) external {
|
||||
require(
|
||||
msg.sender == address(dao),
|
||||
"onValidate may only be called by the DAO contract"
|
||||
);
|
||||
if (!votePasses) return;
|
||||
uint requestIndex = abi.decode(callbackData, (uint));
|
||||
WorkRequest storage request = requests[requestIndex];
|
||||
uint postIndex = dao.addPost(request.customer);
|
||||
request.onboardPoolIndex = dao.initiateValidationPool{
|
||||
value: request.fee / 10
|
||||
}(postIndex, POOL_DURATION, false, "");
|
||||
}
|
||||
}
|
|
@ -169,7 +169,9 @@ contract Work1 is IAcceptAvailability {
|
|||
// Initiate validation pool
|
||||
request.poolIndex = dao.initiateValidationPool{value: request.fee}(
|
||||
postIndex,
|
||||
POOL_DURATION
|
||||
POOL_DURATION,
|
||||
false,
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
const {
|
||||
time,
|
||||
loadFixture,
|
||||
} = require('@nomicfoundation/hardhat-toolbox/network-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { ethers } = require('hardhat');
|
||||
|
||||
describe('Onboarding', () => {
|
||||
const PRICE = 100;
|
||||
const STAKE_DURATION = 60;
|
||||
async function deploy() {
|
||||
// Contracts are deployed using the first signer/account by default
|
||||
const [account1, account2] = await ethers.getSigners();
|
||||
|
||||
const DAO = await ethers.getContractFactory('DAO');
|
||||
const dao = await DAO.deploy();
|
||||
const Onboarding = await ethers.getContractFactory('Onboarding');
|
||||
const onboarding = await Onboarding.deploy(dao.target, PRICE);
|
||||
|
||||
await dao.addPost(account1);
|
||||
const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
|
||||
await dao.initiateValidationPool(0, 60, false, callbackData, { value: 100 });
|
||||
await time.increase(61);
|
||||
await dao.evaluateOutcome(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
|
||||
return {
|
||||
dao, onboarding, account1, account2,
|
||||
};
|
||||
}
|
||||
|
||||
it('Should deploy', async () => {
|
||||
const { dao, onboarding, account1 } = await loadFixture(deploy);
|
||||
expect(dao).to.exist;
|
||||
expect(onboarding).to.exist;
|
||||
expect(await dao.memberCount()).to.equal(1);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
expect(await dao.totalSupply()).to.equal(100);
|
||||
expect(await onboarding.stakeCount()).to.equal(0);
|
||||
});
|
||||
|
||||
describe('Stake availability', () => {
|
||||
let dao;
|
||||
let onboarding;
|
||||
let account1;
|
||||
let account2;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy));
|
||||
await expect(dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION)).to.emit(onboarding, 'AvailabilityStaked').withArgs(0);
|
||||
});
|
||||
|
||||
it('Should be able to stake availability', async () => {
|
||||
expect(await dao.balanceOf(account1)).to.equal(50);
|
||||
expect(await dao.balanceOf(onboarding.target)).to.equal(50);
|
||||
expect(await onboarding.stakeCount()).to.equal(1);
|
||||
const stake = await onboarding.stakes(0);
|
||||
expect(stake.worker).to.equal(account1);
|
||||
expect(stake.amount).to.equal(50);
|
||||
expect(stake.endTime).to.equal(await time.latest() + STAKE_DURATION);
|
||||
});
|
||||
|
||||
it('should not be able to stake availability without reputation value', async () => {
|
||||
await expect(dao.stakeAvailability(onboarding.target, 0, STAKE_DURATION)).to.be.revertedWith('No stake provided');
|
||||
});
|
||||
|
||||
it('should be able to reclaim staked availability after duration elapses', async () => {
|
||||
expect(await dao.balanceOf(account1)).to.equal(50);
|
||||
time.increase(STAKE_DURATION + 1);
|
||||
await expect(onboarding.reclaimAvailability(0)).to.emit(onboarding, 'AvailabilityStaked').withArgs(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
});
|
||||
|
||||
it('should not be able to reclaim staked availability twice', async () => {
|
||||
expect(await dao.balanceOf(account1)).to.equal(50);
|
||||
time.increase(STAKE_DURATION + 1);
|
||||
await onboarding.reclaimAvailability(0);
|
||||
await expect(onboarding.reclaimAvailability(0)).to.be.revertedWith('Stake has already been reclaimed');
|
||||
});
|
||||
|
||||
it('should not be able to reclaim staked availability before duration elapses', async () => {
|
||||
await expect(onboarding.reclaimAvailability(0)).to.be.revertedWith('Stake duration has not yet elapsed');
|
||||
});
|
||||
|
||||
it('should not be able to reclaim availability staked by another account', async () => {
|
||||
time.increase(STAKE_DURATION + 1);
|
||||
await expect(onboarding.connect(account2).reclaimAvailability(0)).to.be.revertedWith('Worker can only reclaim their own availability stake');
|
||||
});
|
||||
|
||||
it('should be able to extend the duration of an availability stake before it expires', async () => {
|
||||
await time.increase(STAKE_DURATION / 2);
|
||||
await expect(onboarding.extendAvailability(0, STAKE_DURATION)).to.emit(onboarding, 'AvailabilityStaked').withArgs(0);
|
||||
});
|
||||
|
||||
it('should be able to extend the duration of an availability stake after it expires', async () => {
|
||||
await time.increase(STAKE_DURATION * 2);
|
||||
await onboarding.extendAvailability(0, STAKE_DURATION);
|
||||
});
|
||||
|
||||
it('should not be able to extend the duration of another worker\'s availability stake', async () => {
|
||||
await time.increase(STAKE_DURATION * 2);
|
||||
await expect(onboarding.connect(account2).extendAvailability(0, STAKE_DURATION)).to.be.revertedWith('Worker can only extend their own availability stake');
|
||||
});
|
||||
|
||||
it('should not be able to extend a stake that has been reclaimed', async () => {
|
||||
await time.increase(STAKE_DURATION * 2);
|
||||
await onboarding.reclaimAvailability(0);
|
||||
await expect(onboarding.extendAvailability(0, STAKE_DURATION)).to.be.revertedWith('Stake has already been reclaimed');
|
||||
});
|
||||
|
||||
it('extending a stake before expiration should increase the end time by the given duration', async () => {
|
||||
await time.increase(STAKE_DURATION / 2);
|
||||
await onboarding.extendAvailability(0, STAKE_DURATION * 2);
|
||||
const expectedEndTime = await time.latest() + 2.5 * STAKE_DURATION;
|
||||
const stake = await onboarding.stakes(0);
|
||||
expect(stake.endTime).to.be.within(expectedEndTime - 1, expectedEndTime);
|
||||
});
|
||||
|
||||
it('extending a stake after expiration should restart the stake for the given duration', async () => {
|
||||
await time.increase(STAKE_DURATION * 2);
|
||||
await onboarding.extendAvailability(0, STAKE_DURATION * 2);
|
||||
const expectedEndTime = await time.latest() + STAKE_DURATION * 2;
|
||||
const stake = await onboarding.stakes(0);
|
||||
expect(stake.endTime).to.be.within(expectedEndTime - 1, expectedEndTime);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Request and assign work', () => {
|
||||
it('should be able to request work and assign to a worker', async () => {
|
||||
const {
|
||||
dao, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(requestWork()).to.emit(onboarding, 'WorkAssigned').withArgs(account1, 0);
|
||||
expect(await onboarding.requestCount()).to.equal(1);
|
||||
const request = await onboarding.requests(0);
|
||||
expect(request.customer).to.equal(account2);
|
||||
});
|
||||
|
||||
it('should not be able to reclaim stake after work is assigned', async () => {
|
||||
const {
|
||||
dao, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(requestWork()).to.emit(onboarding, 'WorkAssigned').withArgs(account1, 0);
|
||||
await time.increase(STAKE_DURATION + 1);
|
||||
await expect(onboarding.reclaimAvailability(0)).to.be.revertedWith('Stake has already been assigned work');
|
||||
});
|
||||
it('should not be able to request work if there are no availability stakes', async () => {
|
||||
const {
|
||||
onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(requestWork()).to.be.revertedWith('No available worker stakes');
|
||||
});
|
||||
|
||||
it('should not be able to request work if fee is insufficient', async () => {
|
||||
const {
|
||||
onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE / 2 });
|
||||
await expect(requestWork()).to.be.revertedWith('Insufficient fee');
|
||||
});
|
||||
|
||||
it('should not assign work to an expired availability stake', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await time.increase(STAKE_DURATION + 1);
|
||||
await expect(requestWork()).to.be.revertedWith('No available worker stakes');
|
||||
});
|
||||
|
||||
it('should not assign work to the same availability stake twice', async () => {
|
||||
const {
|
||||
dao, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
const requestWork = () => onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(requestWork()).to.emit(onboarding, 'WorkAssigned').withArgs(account1, 0);
|
||||
await expect(requestWork()).to.be.revertedWith('No available worker stakes');
|
||||
});
|
||||
|
||||
it('should not be able to extend a stake that has been assigned work', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await time.increase(STAKE_DURATION * 2);
|
||||
await expect(onboarding.extendAvailability(0, STAKE_DURATION)).to.be.revertedWith('Stake has already been assigned work');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Work evidence and approval/disapproval', () => {
|
||||
let dao;
|
||||
let onboarding;
|
||||
let account1;
|
||||
let account2;
|
||||
|
||||
beforeEach(async () => {
|
||||
({
|
||||
dao, onboarding, account1, account2,
|
||||
} = await loadFixture(deploy));
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
});
|
||||
|
||||
it('should be able to submit work evidence', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(onboarding.submitWorkEvidence(0)).to.emit(onboarding, 'WorkEvidenceSubmitted').withArgs(0);
|
||||
});
|
||||
|
||||
it('should not be able to submit work evidence twice', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(onboarding.submitWorkEvidence(0)).to.emit(onboarding, 'WorkEvidenceSubmitted').withArgs(0);
|
||||
await expect(onboarding.submitWorkEvidence(0)).to.be.revertedWith('Status must be Requested');
|
||||
});
|
||||
|
||||
it('should not be able to submit work evidence for a different worker', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(onboarding.connect(account2).submitWorkEvidence(0)).to.be.revertedWith('Worker can only submit evidence for work they are assigned');
|
||||
});
|
||||
|
||||
it('should be able to submit work approval', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, true))
|
||||
.to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
|
||||
.to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, true);
|
||||
const post = await dao.posts(1);
|
||||
expect(post.author).to.equal(account1);
|
||||
expect(post.sender).to.equal(onboarding.target);
|
||||
const pool = await dao.validationPools(1);
|
||||
expect(pool.postIndex).to.equal(1);
|
||||
expect(pool.fee).to.equal(PRICE * 0.9);
|
||||
expect(pool.sender).to.equal(onboarding.target);
|
||||
});
|
||||
|
||||
it('should be able to submit work disapproval', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, false))
|
||||
.to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
|
||||
.to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, false);
|
||||
});
|
||||
|
||||
it('should not be able to submit work approval/disapproval twice', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted');
|
||||
});
|
||||
|
||||
it('should not be able to submit work evidence after work approval', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
await expect(onboarding.submitWorkEvidence(0)).to.be.revertedWith('Status must be Requested');
|
||||
});
|
||||
|
||||
it('should not be able to submit work approval/disapproval before work evidence', async () => {
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Onboarding followup', () => {
|
||||
it('resolving the first validation pool should trigger a second pool', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
await time.increase(86401);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
|
||||
expect(await dao.postCount()).to.equal(3);
|
||||
const post = await dao.posts(2);
|
||||
expect(post.author).to.equal(account2);
|
||||
expect(post.sender).to.equal(onboarding.target);
|
||||
const pool = await dao.validationPools(2);
|
||||
expect(pool.postIndex).to.equal(2);
|
||||
expect(pool.fee).to.equal(PRICE * 0.1);
|
||||
expect(pool.sender).to.equal(onboarding.target);
|
||||
expect(pool.fee);
|
||||
});
|
||||
|
||||
it('if the first validation pool is rejected it should not trigger a second pool', async () => {
|
||||
const {
|
||||
dao, onboarding, account2,
|
||||
} = await loadFixture(deploy);
|
||||
await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
|
||||
await onboarding.connect(account2).requestWork({ value: PRICE });
|
||||
await onboarding.submitWorkEvidence(0);
|
||||
await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
await dao.stake(1, 50, false);
|
||||
await time.increase(86401);
|
||||
await expect(dao.evaluateOutcome(1)).not.to.emit(dao, 'ValidationPoolInitiated');
|
||||
expect(await dao.postCount()).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue