dgf-prototype/ethereum/test/Onboarding.js

309 lines
14 KiB
JavaScript
Raw Normal View History

2024-03-17 21:00:31 -05:00
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);
});
});
});