Use ERC20 allowance to implement staking
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 36s Details

This commit is contained in:
Ladd Hoffman 2024-04-09 20:50:04 -05:00
parent ea6d068555
commit bb26571779
18 changed files with 164 additions and 216 deletions

View File

@ -263,7 +263,7 @@ function App() {
const stake = useCallback(async (poolIndex, amount, inFavor) => {
console.log(`Attempting to stake ${amount} ${inFavor ? 'for' : 'against'} pool ${poolIndex}`);
await DAO.methods.stake(poolIndex, amount, inFavor).send({
await DAO.methods.stakeOnValidationPool(poolIndex, amount, inFavor).send({
from: account,
gas: 999999,
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,6 @@ import Web3Context from '../../contexts/Web3Context';
import WorkContractContext from '../../contexts/WorkContractContext';
const getAvailabilityStatus = (stake) => {
if (stake.reclaimed) return 'Reclaimed';
if (stake.assigned) return 'Assigned';
if (new Date() < new Date(Number(stake.endTime) * 1000)) return 'Available';
return 'Expired';
@ -75,13 +74,6 @@ function AvailabilityStakes({
setReputation(reputation / BigInt(2));
}, [DAO, workContract, account, reputation, setReputation]);
const reclaimAvailabilityStake = useCallback(async (stakeIndex) => {
await workContract.methods.reclaimAvailability(stakeIndex).send({
from: account,
gas: 999999,
});
}, [workContract, account]);
const extendAvailabilityStake = useCallback(async (stakeIndex, duration) => {
await workContract.methods.extendAvailability(stakeIndex, duration).send({
from: account,
@ -139,7 +131,7 @@ function AvailabilityStakes({
<td>{getAvailabilityStatus(s)}</td>
{showActions && (
<td>
{s.currentUserIsWorker() && !s.assigned && !s.reclaimed && (
{s.currentUserIsWorker() && !s.assigned && (
<>
<Button onClick={() => extendAvailabilityStake(s.id, 3600)}>
Extend 1 Hr.
@ -149,15 +141,6 @@ function AvailabilityStakes({
</Button>
</>
)}
{s.currentUserIsWorker() && s.timeRemaining <= 0
&& !s.assigned && !s.reclaimed && (
<>
{' '}
<Button onClick={() => reclaimAvailabilityStake(s.id)}>
Reclaim
</Button>
</>
)}
</td>
)}
</tr>

View File

@ -1,9 +1,9 @@
{
"localhost": {
"DAO": "0x614fE39E47E48Ed39a078791917F6290C1C8D0cd",
"Work1": "0x35d9024a19e970b1454Fa1C0e124dD2bd71E6360",
"Onboarding": "0x861fD16fA5C26c53bf6C6E6210dD4d2364A26213",
"Proposals": "0x918040581A6817fa2F3F6e73f7F10C3A3a7Bbc29"
"DAO": "0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3",
"Work1": "0xfe58B9EB03F75A603de1B286584f5E9532ab8fB5",
"Onboarding": "0x1d63FDe5B461106729fE1e5e38A02fc68C518Af5",
"Proposals": "0x050C420Cc4995B41217Eba1B54B82Fd5687e9139"
},
"sepolia": {
"DAO": "0x58c41E768aCA55B39b5dC0618c0D0bE3f5519943",

View File

@ -1,9 +1,9 @@
{
"localhost": {
"DAO": "0x614fE39E47E48Ed39a078791917F6290C1C8D0cd",
"Work1": "0x35d9024a19e970b1454Fa1C0e124dD2bd71E6360",
"Onboarding": "0x861fD16fA5C26c53bf6C6E6210dD4d2364A26213",
"Proposals": "0x918040581A6817fa2F3F6e73f7F10C3A3a7Bbc29"
"DAO": "0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3",
"Work1": "0xfe58B9EB03F75A603de1B286584f5E9532ab8fB5",
"Onboarding": "0x1d63FDe5B461106729fE1e5e38A02fc68C518Af5",
"Proposals": "0x050C420Cc4995B41217Eba1B54B82Fd5687e9139"
},
"sepolia": {
"DAO": "0x58c41E768aCA55B39b5dC0618c0D0bE3f5519943",

View File

@ -6,23 +6,19 @@ import "./core/Reputation.sol";
import "./core/ValidationPools.sol";
import "./core/Forum.sol";
import "./interfaces/IAcceptAvailability.sol";
import "hardhat/console.sol";
// TODO: consider dynamically constructing contract instances rather than merging at build time
contract DAO is Reputation, Forum, ValidationPools {
/// Transfer REP to a contract, and call that contract's receiveTransfer method
function stakeAvailability(
address to,
uint256 value,
uint duration
) external returns (bool transferred) {
transferred = super.transfer(to, value);
if (transferred)
IAcceptAvailability(to).acceptAvailability(
msg.sender,
value,
duration
);
) external returns (bool) {
_approve(msg.sender, to, value);
IAcceptAvailability(to).acceptAvailability(msg.sender, value, duration);
return true;
}
}

View File

@ -41,7 +41,9 @@ contract Onboarding is WorkContract, IOnValidate {
true,
abi.encode(requestIndex)
);
dao.stake(poolIndex, stake.amount, true);
// We have an approval from stake.worker to transfer up to stake.amount
dao.transferFrom(stake.worker, address(this), stake.amount);
dao.stakeOnValidationPool(poolIndex, stake.amount, true);
}
/// Callback to be executed when review pool completes

View File

@ -5,6 +5,7 @@ import "./DAO.sol";
import "./Proposals.sol";
import "./interfaces/IAcceptAvailability.sol";
import "./interfaces/IOnProposalAccepted.sol";
import "hardhat/console.sol";
abstract contract WorkContract is
DAOContract,
@ -16,7 +17,6 @@ abstract contract WorkContract is
uint256 amount;
uint endTime;
bool assigned;
bool reclaimed;
}
enum WorkStatus {
@ -89,7 +89,6 @@ abstract contract WorkContract is
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;
@ -99,23 +98,6 @@ abstract contract WorkContract is
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;
@ -204,7 +186,13 @@ abstract contract WorkContract is
false,
""
);
dao.stake(poolIndex, stake.amount, true);
// We have an approval from stake.worker to transfer up to stake.amount
dao.delegatedStakeOnValidationPool(
poolIndex,
stake.worker,
stake.amount,
true
);
}
function proposeNewPrice(

View File

@ -10,4 +10,3 @@ contract Reputation is ERC20("Reputation", "REP") {
}
// TODO: try implementing as ERC721
// If that doesn't work, try writing from scratch

View File

@ -3,10 +3,10 @@ pragma solidity ^0.8.24;
import "./Reputation.sol";
import "./Forum.sol";
import "../interfaces/IOnValidate.sol";
import "hardhat/console.sol";
struct Stake {
struct ValidationPoolStake {
uint id;
bool inFavor;
uint256 amount;
@ -26,7 +26,7 @@ struct ValidationPool {
uint id;
uint postIndex;
address sender;
mapping(uint => Stake) stakes;
mapping(uint => ValidationPoolStake) stakes;
uint stakeCount;
ValidationPoolParams params;
uint256 fee;
@ -45,9 +45,6 @@ contract ValidationPools is Reputation, Forum {
uint public memberCount;
mapping(address => bool) public isMember;
// TODO: possible parameter for minting ratio
// TODO: possible parameter for stakeForAuthor
uint constant minDuration = 1; // 1 second
uint constant maxDuration = 365_000_000 days; // 1 million years
uint[2] minQuorum = [1, 10];
@ -60,8 +57,9 @@ contract ValidationPools is Reputation, Forum {
);
// TODO: Add forum parameters
/// Internal function to register a stake for/against a validation pool
function _stake(
function _stakeOnValidationPool(
ValidationPool storage pool,
address sender,
uint256 amount,
@ -69,9 +67,9 @@ contract ValidationPools is Reputation, Forum {
bool fromMint
) internal {
require(block.timestamp <= pool.endTime, "Pool end time has passed");
_update(sender, address(this), amount);
//_update(sender, address(this), amount);
uint stakeIndex = pool.stakeCount++;
Stake storage s = pool.stakes[stakeIndex];
ValidationPoolStake storage s = pool.stakes[stakeIndex];
s.sender = sender;
s.inFavor = inFavor;
s.amount = amount;
@ -80,9 +78,25 @@ contract ValidationPools is Reputation, Forum {
}
/// Accept reputation stakes toward a validation pool
function stake(uint poolIndex, uint256 amount, bool inFavor) public {
function stakeOnValidationPool(
uint poolIndex,
uint256 amount,
bool inFavor
) public {
ValidationPool storage pool = validationPools[poolIndex];
_stake(pool, msg.sender, amount, inFavor, false);
_stakeOnValidationPool(pool, msg.sender, amount, inFavor, false);
}
/// Accept reputation stakes toward a validation pool
function delegatedStakeOnValidationPool(
uint poolIndex,
address owner,
uint256 amount,
bool inFavor
) public {
ValidationPool storage pool = validationPools[poolIndex];
_spendAllowance(owner, msg.sender, amount);
_stakeOnValidationPool(pool, owner, amount, inFavor, false);
}
/// Accept fee to initiate a validation pool
@ -129,8 +143,8 @@ contract ValidationPools is Reputation, Forum {
_mint(post.author, msg.value);
// Here we assume a stakeForAuthor ratio of 0.5
// TODO: Make stakeForAuthor an adjustable parameter
_stake(pool, post.author, msg.value / 2, true, true);
_stake(pool, post.author, msg.value / 2, false, true);
_stakeOnValidationPool(pool, post.author, msg.value / 2, true, true);
_stakeOnValidationPool(pool, post.author, msg.value / 2, false, true);
emit ValidationPoolInitiated(poolIndex);
}
@ -141,7 +155,7 @@ contract ValidationPools is Reputation, Forum {
require(pool.resolved == false, "Pool is already resolved");
uint256 stakedFor;
uint256 stakedAgainst;
Stake storage s;
ValidationPoolStake storage s;
for (uint i = 0; i < pool.stakeCount; i++) {
s = pool.stakes[i];
if (s.inFavor) {
@ -164,11 +178,6 @@ contract ValidationPools is Reputation, Forum {
// Refund fee
// TODO: this could be made available for the sender to withdraw
// payable(pool.sender).transfer(pool.fee);
// Refund stakes
for (uint i = 0; i < pool.stakeCount; i++) {
s = pool.stakes[i];
_update(address(this), s.sender, s.amount);
}
pool.resolved = true;
emit ValidationPoolResolved(poolIndex, false, false);
// Callback if requested
@ -203,52 +212,55 @@ contract ValidationPools is Reputation, Forum {
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
// Only bindingPercent % should be redistributed
// Stake senders should get (100-bindingPercent) % back
uint256 totalAllocated;
// We have allowances for each stake. Time to collect from the losing stakes.
uint totalRewards;
uint totalAllocated;
for (uint i = 0; i < pool.stakeCount; i++) {
s = pool.stakes[i];
uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent;
if (votePasses == s.inFavor) {
// Winning stake
// If this stake is from the minted fee, always redistribute it to the winners
bool redistributeLosingStakes = s.fromMint ||
pool.params.redistributeLosingStakes;
uint reward = redistributeLosingStakes
? ((s.amount * amountFromLosers) / amountFromWinners) *
(bindingPercent / 100)
: 0;
_update(address(this), s.sender, s.amount + reward);
totalAllocated += reward;
} else {
bool redistributeLosingStakes = s.fromMint ||
pool.params.redistributeLosingStakes;
if (votePasses != s.inFavor) {
// Losing stake
// If this stake is from the minted fee, don't burn it
if (!s.fromMint) {
uint refund = (s.amount * (100 - bindingPercent)) / 100;
if (refund > 0) {
_update(address(this), s.sender, refund);
}
if (!pool.params.redistributeLosingStakes) {
uint amountToBurn = (s.amount *
pool.params.bindingPercent) / 100;
_burn(address(this), amountToBurn);
totalAllocated += amountToBurn;
}
totalAllocated += refund;
uint amount = (s.amount * bindingPercent) / 100;
if (redistributeLosingStakes) {
_update(s.sender, address(this), amount);
totalRewards += amount;
} else {
_burn(s.sender, amount);
}
}
}
// Due to rounding, there may be some REP left over. Include this as a reward to the author.
uint256 remainder = amountFromLosers - totalAllocated;
// Issue rewards to the winners
for (uint i = 0; i < pool.stakeCount; i++) {
s = pool.stakes[i];
uint bindingPercent = s.fromMint ? 100 : pool.params.bindingPercent;
bool redistributeLosingStakes = s.fromMint ||
pool.params.redistributeLosingStakes;
if (redistributeLosingStakes && votePasses == s.inFavor) {
// Winning stake
// If this stake is from the minted fee, always redistribute it to the winners
uint reward = (((amountFromLosers * s.amount) /
amountFromWinners) * bindingPercent) / 100;
totalAllocated += reward;
_update(address(this), s.sender, reward);
}
}
// Due to rounding, some reward may be left over. Let's give it to the author.
uint remainder = totalRewards - totalAllocated;
if (remainder > 0) {
_update(address(this), post.author, remainder);
}
// Distribute fee proportionatly among all reputation holders
// Distribute fee proportionately among all reputation holders
for (uint i = 0; i < memberCount; i++) {
address member = members[i];
uint256 share = (pool.fee * balanceOf(member)) / totalSupply();
// TODO: For efficiency this could be modified to hold the funds for recipients to withdraw
// TODO: Exclude encumbered reputation from totalSupply
payable(member).transfer(share);
}
// Callback if requested
if (pool.callbackOnValidate) {
IOnValidate(pool.sender).onValidate(

View File

@ -145,7 +145,7 @@ const getPoolStatus = (pool) => {
const stake = async (pool, amount, inFavor) => {
console.log(`staking ${amount} ${inFavor ? 'in favor of' : 'against'} pool ${pool.id.toString()}`);
await dao.stake(pool.id, amount, true);
await dao.stakeOnValidationPool(pool.id, amount, true);
await fetchReputation();
};

View File

@ -73,7 +73,7 @@ describe('DAO', () => {
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(0);
expect(await dao.validationPoolCount()).to.equal(1);
expect(await dao.memberCount()).to.equal(0);
expect(await dao.balanceOf(account1)).to.equal(0);
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.totalSupply()).to.equal(POOL_FEE);
});
@ -131,13 +131,14 @@ describe('DAO', () => {
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
await initiateValidationPool();
expect(await dao.balanceOf(dao.target)).to.equal(100);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(dao.target)).to.equal(0);
});
it('should be able to stake before validation pool has elapsed', async () => {
await dao.stake(1, 10, true);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(110);
await dao.stakeOnValidationPool(1, 10, true);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
expect(await dao.balanceOf(dao.target)).to.equal(0);
@ -146,13 +147,13 @@ describe('DAO', () => {
it('should not be able to stake after validation pool has elapsed', async () => {
time.increase(POOL_DURATION + 1);
await expect(dao.stake(1, 10, true)).to.be.revertedWith('Pool end time has passed');
await expect(dao.stakeOnValidationPool(1, 10, true)).to.be.revertedWith('Pool end time has passed');
});
it('should be able to stake against a validation pool', async () => {
await dao.stake(1, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(110);
await dao.stakeOnValidationPool(1, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, false, true);
expect(await dao.balanceOf(dao.target)).to.equal(0);
@ -174,12 +175,12 @@ describe('DAO', () => {
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0));
await initiateValidationPool({ fee: 100 });
await dao.stake(1, 100, true);
await dao.stakeOnValidationPool(1, 100, true);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
});
it('should be able to evaluate outcome after duration has elapsed', async () => {
expect(await dao.balanceOf(dao.target)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
expect(await dao.memberCount()).to.equal(1);
@ -211,7 +212,7 @@ describe('DAO', () => {
expect(await dao.validationPoolCount()).to.equal(2);
time.increase(POOL_DURATION + 1);
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true, true);
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.balanceOf(account1)).to.equal(200);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
expect(await dao.balanceOf(account1)).to.equal(200);
});
@ -241,11 +242,11 @@ describe('DAO', () => {
it('Binding validation pool should redistribute stakes', async () => {
const init = () => initiateValidationPool();
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.connect(account1).stake(2, 10, true);
await dao.connect(account2).stake(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(account2)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(120);
await dao.connect(account1).stakeOnValidationPool(2, 10, true);
await dao.connect(account2).stakeOnValidationPool(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(account2)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await dao.evaluateOutcome(2);
expect(await dao.balanceOf(account1)).to.equal(210);
@ -256,11 +257,11 @@ describe('DAO', () => {
it('Non binding validation pool should not redistribute stakes', async () => {
const init = () => initiateValidationPool({ bindingPercent: 0 });
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.connect(account1).stake(2, 10, true);
await dao.connect(account2).stake(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(account2)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(120);
await dao.connect(account1).stakeOnValidationPool(2, 10, true);
await dao.connect(account2).stakeOnValidationPool(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(account2)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await dao.evaluateOutcome(2);
expect(await dao.balanceOf(account1)).to.equal(200);
@ -271,11 +272,11 @@ describe('DAO', () => {
it('Partially binding validation pool should redistribute some stakes', async () => {
const init = () => initiateValidationPool({ bindingPercent: 50 });
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.connect(account1).stake(2, 10, true);
await dao.connect(account2).stake(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(account2)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(120);
await dao.connect(account1).stakeOnValidationPool(2, 10, true);
await dao.connect(account2).stakeOnValidationPool(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(account2)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await dao.evaluateOutcome(2);
expect(await dao.balanceOf(account1)).to.equal(205);
@ -290,11 +291,11 @@ describe('DAO', () => {
redistributeLosingStakes: false,
});
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.connect(account1).stake(2, 10, true);
await dao.connect(account2).stake(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(account2)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(120);
await dao.connect(account1).stakeOnValidationPool(2, 10, true);
await dao.connect(account2).stakeOnValidationPool(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(account2)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await dao.evaluateOutcome(2);
expect(await dao.balanceOf(account1)).to.equal(200);
@ -309,11 +310,11 @@ describe('DAO', () => {
redistributeLosingStakes: false,
});
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.connect(account1).stake(2, 10, true);
await dao.connect(account2).stake(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(90);
expect(await dao.balanceOf(account2)).to.equal(90);
expect(await dao.balanceOf(dao.target)).to.equal(120);
await dao.connect(account1).stakeOnValidationPool(2, 10, true);
await dao.connect(account2).stakeOnValidationPool(2, 10, false);
expect(await dao.balanceOf(account1)).to.equal(200);
expect(await dao.balanceOf(account2)).to.equal(100);
expect(await dao.balanceOf(dao.target)).to.equal(0);
time.increase(POOL_DURATION + 1);
await dao.evaluateOutcome(2);
expect(await dao.balanceOf(account1)).to.equal(200);

View File

@ -139,7 +139,7 @@ describe('Onboarding', () => {
await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
await onboarding.submitWorkEvidence(0, 'evidence-content-id');
await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
await dao.stake(1, 60, false);
await dao.stakeOnValidationPool(1, 60, false);
await time.increase(86401);
await expect(dao.evaluateOutcome(1)).not.to.emit(dao, 'ValidationPoolInitiated');
expect(await dao.postCount()).to.equal(2);

View File

@ -141,7 +141,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet participation rate', async () => {
await dao.stake(2, 200, true);
await dao.stakeOnValidationPool(2, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true)
@ -151,7 +151,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet win ratio', async () => {
await dao.stake(2, 1000, false);
await dao.stakeOnValidationPool(2, 1000, false);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, false, true)
@ -169,7 +169,7 @@ describe('Proposal', () => {
});
it('proposal fails if a referendum fails to meet participation rate 3 times', async () => {
await dao.stake(2, 200, true);
await dao.stakeOnValidationPool(2, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true)
@ -177,7 +177,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(1);
await dao.stake(3, 200, true);
await dao.stakeOnValidationPool(3, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true)
@ -185,7 +185,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(1);
await dao.stake(4, 200, true);
await dao.stakeOnValidationPool(4, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true);
@ -194,7 +194,7 @@ describe('Proposal', () => {
});
it('advances to next referendum if it meets participation rate and win ratio', async () => {
await dao.stake(2, 1000, true);
await dao.stakeOnValidationPool(2, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true)
@ -208,7 +208,7 @@ describe('Proposal', () => {
beforeEach(async () => {
await proposals.attest(0, 200);
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.stake(2, 1000, true);
await dao.stakeOnValidationPool(2, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true)
@ -230,7 +230,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet participation rate', async () => {
await dao.stake(3, 200, true);
await dao.stakeOnValidationPool(3, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true)
@ -240,7 +240,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet win ratio', async () => {
await dao.stake(3, 1000, false);
await dao.stakeOnValidationPool(3, 1000, false);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, false, true)
@ -250,7 +250,7 @@ describe('Proposal', () => {
});
it('proposal fails if a referendum fails to meet participation rate 3 times', async () => {
await dao.stake(3, 200, true);
await dao.stakeOnValidationPool(3, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true)
@ -258,7 +258,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(2);
await dao.stake(4, 200, true);
await dao.stakeOnValidationPool(4, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true)
@ -266,7 +266,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(2);
await dao.stake(5, 200, true);
await dao.stakeOnValidationPool(5, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(5))
.to.emit(dao, 'ValidationPoolResolved').withArgs(5, true, true);
@ -275,7 +275,7 @@ describe('Proposal', () => {
});
it('advances to next referendum if it meets participation rate and win ratio', async () => {
await dao.stake(3, 1000, true);
await dao.stakeOnValidationPool(3, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true)
@ -289,12 +289,12 @@ describe('Proposal', () => {
beforeEach(async () => {
await proposals.attest(0, 200);
await expect(proposals.evaluateAttestation(0)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
await dao.stake(2, 1000, true);
await dao.stakeOnValidationPool(2, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(2))
.to.emit(dao, 'ValidationPoolResolved').withArgs(2, true, true)
.to.emit(dao, 'ValidationPoolInitiated').withArgs(3);
await dao.stake(3, 1000, true);
await dao.stakeOnValidationPool(3, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(3))
.to.emit(dao, 'ValidationPoolResolved').withArgs(3, true, true)
@ -316,7 +316,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet participation rate', async () => {
await dao.stake(4, 200, true);
await dao.stakeOnValidationPool(4, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true)
@ -326,7 +326,7 @@ describe('Proposal', () => {
});
it('referendum retries if it fails to meet win ratio', async () => {
await dao.stake(4, 1000, false);
await dao.stakeOnValidationPool(4, 1000, false);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, false, true)
@ -336,7 +336,7 @@ describe('Proposal', () => {
});
it('proposal fails if a referendum fails to meet participation rate 3 times', async () => {
await dao.stake(4, 200, true);
await dao.stakeOnValidationPool(4, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true)
@ -344,7 +344,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(3);
await dao.stake(5, 200, true);
await dao.stakeOnValidationPool(5, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(5))
.to.emit(dao, 'ValidationPoolResolved').withArgs(5, true, true)
@ -352,7 +352,7 @@ describe('Proposal', () => {
proposal = await proposals.proposals(0);
expect(proposal.stage).to.equal(3);
await dao.stake(6, 200, true);
await dao.stakeOnValidationPool(6, 200, true);
await time.increase(21);
await expect(dao.evaluateOutcome(6))
.to.emit(dao, 'ValidationPoolResolved').withArgs(6, true, true);
@ -361,8 +361,8 @@ describe('Proposal', () => {
});
it('advances to accepted stage if it meets participation rate and win ratio', async () => {
await dao.connect(account1).stake(4, 1000, true);
await dao.connect(account2).stake(4, 1000, true);
await dao.connect(account1).stakeOnValidationPool(4, 1000, true);
await dao.connect(account2).stakeOnValidationPool(4, 1000, true);
await time.increase(21);
await expect(dao.evaluateOutcome(4))
.to.emit(dao, 'ValidationPoolResolved').withArgs(4, true, true);

View File

@ -64,8 +64,9 @@ describe('Work1', () => {
});
it('Should be able to stake availability', async () => {
expect(await dao.balanceOf(account1)).to.equal(50);
expect(await dao.balanceOf(work1.target)).to.equal(50);
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.balanceOf(work1.target)).to.equal(0);
expect(await dao.allowance(account1, work1.target)).to.equal(50);
expect(await work1.stakeCount()).to.equal(1);
const stake = await work1.stakes(0);
expect(stake.worker).to.equal(account1);
@ -77,29 +78,6 @@ describe('Work1', () => {
await expect(dao.stakeAvailability(work1.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(work1.reclaimAvailability(0)).to.emit(work1, '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 work1.reclaimAvailability(0);
await expect(work1.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(work1.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(work1.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(work1.extendAvailability(0, STAKE_DURATION)).to.emit(work1, 'AvailabilityStaked').withArgs(0);
@ -115,12 +93,6 @@ describe('Work1', () => {
await expect(work1.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 work1.reclaimAvailability(0);
await expect(work1.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 work1.extendAvailability(0, STAKE_DURATION * 2);
@ -152,16 +124,6 @@ describe('Work1', () => {
expect(request.requestContentId).to.equal('req-content-id');
});
it('should not be able to reclaim stake after work is assigned', async () => {
const {
dao, work1, account2,
} = await loadFixture(deploy);
await dao.stakeAvailability(work1.target, 50, STAKE_DURATION);
const requestWork = () => work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE });
await expect(requestWork()).to.emit(work1, 'WorkAssigned').withArgs(0, 0);
await time.increase(STAKE_DURATION + 1);
await expect(work1.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 {
work1, account2,
@ -242,9 +204,13 @@ describe('Work1', () => {
it('should be able to submit work approval', async () => {
await work1.connect(account2).requestWork('req-content-id', { value: WORK1_PRICE });
await work1.submitWorkEvidence(0, 'evidence-content-id');
expect(await dao.balanceOf(account1)).to.equal(100);
expect(await dao.balanceOf(work1.target)).to.equal(0);
await expect(work1.submitWorkApproval(0, true))
.to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
.to.emit(work1, 'WorkApprovalSubmitted').withArgs(0, true);
expect(await dao.balanceOf(work1.target)).to.equal(0);
expect(await dao.balanceOf(account1)).to.equal(200);
const post = await dao.posts(1);
expect(post.author).to.equal(account1);
expect(post.sender).to.equal(work1.target);
@ -256,6 +222,7 @@ describe('Work1', () => {
expect(pool.stakeCount).to.equal(3);
await time.increase(86401);
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true, true);
expect(await dao.balanceOf(account1)).to.equal(200);
});
it('should be able to submit work disapproval', async () => {