staking
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
f59bfc3c9c
commit
71a1d3b529
|
@ -4,6 +4,8 @@ pragma solidity ^0.8.24;
|
|||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "./IAcceptAvailability.sol";
|
||||
|
||||
import "hardhat/console.sol";
|
||||
|
||||
struct Stake {
|
||||
bool inFavor;
|
||||
uint256 amount;
|
||||
|
@ -46,7 +48,7 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
// TODO: Add forum parameters
|
||||
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(bool votePasses);
|
||||
event ValidationPoolResolved(uint poolIndex, bool votePasses);
|
||||
|
||||
/// Accept fee to initiate a validation pool
|
||||
/// TODO: Rather than accept author as a parameter, accept a reference to a forum post
|
||||
|
@ -69,7 +71,7 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
// Implementing this with adjustable parameters will require more advanced fixed point math.
|
||||
// TODO: Make minting ratio an adjustable parameter
|
||||
// TODO: Make stakeForAuthor an adjustable parameter
|
||||
_mint(address(this), msg.value);
|
||||
_mint(author, msg.value);
|
||||
// TODO: We need a way to exclude this pending reputation from the total supply when computing fee distribution
|
||||
_stake(pool, author, msg.value / 2, true);
|
||||
_stake(pool, author, msg.value / 2, false);
|
||||
|
@ -84,6 +86,7 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
bool inFavor
|
||||
) internal {
|
||||
require(block.timestamp <= pool.endTime, "Pool end time has passed");
|
||||
_transfer(sender, address(this), amount);
|
||||
Stake storage s = pool.stakes[pool.stakeCount++];
|
||||
s.sender = sender;
|
||||
s.inFavor = inFavor;
|
||||
|
@ -126,18 +129,25 @@ contract DAO is ERC20("Reputation", "REP") {
|
|||
isMember[pool.author] = true;
|
||||
}
|
||||
pool.resolved = true;
|
||||
emit ValidationPoolResolved(votePasses);
|
||||
// Value of losing stakes should be distributed among winners, in proportion to their stakes
|
||||
emit ValidationPoolResolved(poolIndex, votePasses);
|
||||
// Value of losing stakes should be di stributed among winners, in proportion to their stakes
|
||||
uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst;
|
||||
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
|
||||
uint256 totalRewards;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
s = pool.stakes[i];
|
||||
if (votePasses == s.inFavor) {
|
||||
uint256 reward = (amountFromLosers * s.amount) /
|
||||
amountFromWinners;
|
||||
_transfer(address(this), s.sender, s.amount + reward);
|
||||
totalRewards += reward;
|
||||
}
|
||||
}
|
||||
// Due to rounding, there may be some reward left over. Include this as a reward to the author.
|
||||
uint256 remainder = amountFromLosers - totalRewards;
|
||||
if (remainder > 0) {
|
||||
_transfer(address(this), pool.author, remainder);
|
||||
}
|
||||
// Distribute fee proportionatly among all reputation holders
|
||||
for (uint i = 0; i < memberCount; i++) {
|
||||
address member = members[i];
|
||||
|
|
|
@ -7,8 +7,9 @@ import "./IAcceptAvailability.sol";
|
|||
struct AvailabilityStake {
|
||||
address worker;
|
||||
uint256 amount;
|
||||
uint duration;
|
||||
uint endTime;
|
||||
bool assigned;
|
||||
bool reclaimed;
|
||||
}
|
||||
|
||||
enum WorkStatus {
|
||||
|
@ -54,7 +55,31 @@ contract Work1 is IAcceptAvailability {
|
|||
AvailabilityStake storage stake = stakes[stakeCount++];
|
||||
stake.worker = sender;
|
||||
stake.amount = amount;
|
||||
stake.duration = duration;
|
||||
stake.endTime = block.timestamp + duration;
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
stake.endTime = block.timestamp + duration;
|
||||
}
|
||||
|
||||
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");
|
||||
stake.reclaimed = true;
|
||||
dao.transfer(msg.sender, stake.amount);
|
||||
}
|
||||
|
||||
/// Select a worker randomly from among the available workers, weighted by amount staked
|
||||
|
@ -62,12 +87,14 @@ contract Work1 is IAcceptAvailability {
|
|||
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;
|
||||
}
|
||||
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) return i;
|
||||
}
|
||||
|
|
|
@ -22,71 +22,111 @@ describe('DAO', () => {
|
|||
it('Should deploy', async () => {
|
||||
const { dao } = await loadFixture(deploy);
|
||||
expect(dao).to.exist;
|
||||
expect(await dao.totalValue()).to.equal(0);
|
||||
expect(await dao.totalSupply()).to.equal(0);
|
||||
});
|
||||
|
||||
describe('Validation Pool', () => {
|
||||
let dao;
|
||||
let account1;
|
||||
const POOL_DURATION = 3600; // 1 hour
|
||||
const fee = 100;
|
||||
const POOL_FEE = 100;
|
||||
|
||||
beforeEach(async () => {
|
||||
const setup = await loadFixture(deploy);
|
||||
dao = setup.dao;
|
||||
account1 = setup.account1;
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: fee });
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE });
|
||||
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.totalSupply()).to.equal(fee);
|
||||
expect(await dao.totalSupply()).to.equal(POOL_FEE);
|
||||
});
|
||||
|
||||
it('should not be able to initiate a validation pool without a fee', async () => {
|
||||
const setup = await loadFixture(deploy);
|
||||
const init = () => setup.dao.initiateValidationPool(setup.account1, POOL_DURATION);
|
||||
await expect(init()).to.be.revertedWith('Fee is required to initiate validation pool');
|
||||
describe('Initiate', () => {
|
||||
it('should not be able to initiate a validation pool without a fee', async () => {
|
||||
const setup = await loadFixture(deploy);
|
||||
const init = () => setup.dao.initiateValidationPool(setup.account1, POOL_DURATION);
|
||||
await expect(init()).to.be.revertedWith('Fee is required to initiate validation pool');
|
||||
});
|
||||
|
||||
it('should be able to initiate a second validation pool', async () => {
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
});
|
||||
|
||||
it('Should be able to fetch pool instance', async () => {
|
||||
const pool = await dao.validationPools(0);
|
||||
expect(pool).to.exist;
|
||||
expect(pool.duration).to.equal(POOL_DURATION);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to initiate a second validation pool', async () => {
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: fee });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
describe('Evaluate outcome', () => {
|
||||
it('should not be able to evaluate outcome before duration has elapsed', async () => {
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool end time has not yet arrived');
|
||||
});
|
||||
|
||||
it('should be able to evaluate outcome after duration has elapsed', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
expect(await dao.memberCount()).to.equal(1);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
});
|
||||
|
||||
it('should not be able to evaluate outcome more than once', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool is already resolved');
|
||||
});
|
||||
|
||||
it('should be able to evaluate outcome of second validation pool', async () => {
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(0, true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to fetch pool instance', async () => {
|
||||
const pool = await dao.validationPools(0);
|
||||
expect(pool).to.exist;
|
||||
expect(pool.duration).to.equal(POOL_DURATION);
|
||||
});
|
||||
describe('Stake', async () => {
|
||||
beforeEach(async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await dao.evaluateOutcome(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
await dao.initiateValidationPool(account1, POOL_DURATION, { value: POOL_FEE });
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(100);
|
||||
});
|
||||
|
||||
it('should not be able to evaluate outcome before duration has elapsed', async () => {
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool end time has not yet arrived');
|
||||
});
|
||||
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);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, true);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
});
|
||||
|
||||
it('should be able to evaluate outcome after duration has elapsed', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(true);
|
||||
expect(await dao.memberCount()).to.equal(1);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
});
|
||||
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');
|
||||
});
|
||||
|
||||
it('should not be able to evaluate outcome more than once', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(true);
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool is already resolved');
|
||||
});
|
||||
|
||||
it('should be able to evaluate outcome of second validation pool', async () => {
|
||||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: fee });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
|
||||
expect(await dao.validationPoolCount()).to.equal(2);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(100);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(true);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
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);
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(1, false);
|
||||
expect(await dao.balanceOf(dao.target)).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const {
|
||||
time,
|
||||
loadFixture,
|
||||
} = require('@nomicfoundation/hardhat-toolbox/network-helpers');
|
||||
const { expect } = require('chai');
|
||||
|
@ -42,6 +43,6 @@ describe('Work1', () => {
|
|||
const stake = await work1.stakes(0);
|
||||
expect(stake.worker).to.equal(account1);
|
||||
expect(stake.amount).to.equal(50);
|
||||
expect(stake.duration).to.equal(60);
|
||||
expect(stake.endTime).to.equal(await time.latest() + 60);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue