309 lines
14 KiB
JavaScript
309 lines
14 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|