switched to ERC20 base
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 43s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 43s
Details
This commit is contained in:
parent
fa1dbb897a
commit
b3cdfcef73
|
@ -1,14 +1,12 @@
|
|||
// SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.24;
|
||||
|
||||
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||
import "./ReputationHolder.sol";
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
struct Stake {
|
||||
bool inFavor;
|
||||
uint256 amount;
|
||||
address sender;
|
||||
uint256 tokenId;
|
||||
}
|
||||
|
||||
struct ValidationPool {
|
||||
|
@ -16,12 +14,12 @@ struct ValidationPool {
|
|||
uint stakeCount;
|
||||
address author;
|
||||
uint256 fee;
|
||||
uint256 initialStakedFor;
|
||||
uint256 initialStakedAgainst;
|
||||
uint duration;
|
||||
uint endTime;
|
||||
bool resolved;
|
||||
bool outcome;
|
||||
uint256 tokenIdFor;
|
||||
uint256 tokenIdAgainst;
|
||||
}
|
||||
|
||||
struct StakeData {
|
||||
|
@ -32,10 +30,10 @@ struct StakeData {
|
|||
/// This contract must manage validation pools and reputation,
|
||||
/// because otherwise there's no way to enforce appropriate permissions on
|
||||
/// transfer of value between reputation NFTs.
|
||||
contract DAO is ERC721("Reputation", "REP"), ReputationHolder {
|
||||
mapping(uint256 tokenId => uint256) tokenValues;
|
||||
uint256 nextTokenId;
|
||||
uint256 public totalValue;
|
||||
contract DAO is ERC20("Reputation", "REP") {
|
||||
mapping(uint => address) public members;
|
||||
uint public memberCount;
|
||||
mapping(address => bool) public isMember;
|
||||
mapping(uint => ValidationPool) public validationPools;
|
||||
uint public validationPoolCount;
|
||||
|
||||
|
@ -47,53 +45,11 @@ contract DAO is ERC721("Reputation", "REP"), ReputationHolder {
|
|||
// TODO: Add forum parameters
|
||||
|
||||
event ValidationPoolInitiated(uint poolIndex);
|
||||
event ValidationPoolResolved(bool votePasses, uint256 newTokenId);
|
||||
|
||||
/// Inspect the value of a given reputation NFT
|
||||
function valueOf(uint256 tokenId) public view returns (uint256 value) {
|
||||
value = tokenValues[tokenId];
|
||||
}
|
||||
|
||||
/// Confirm ownership of a token and return its value.
|
||||
/// This should be used when receiving an NFT transfer, because otherwise
|
||||
/// someone could send any NFT with a tokenId matching one of ours.
|
||||
function verifiedValueOf(
|
||||
address owner,
|
||||
uint256 tokenId
|
||||
) public view returns (uint256 value) {
|
||||
require(ownerOf(tokenId) == owner, "NFT owner mismatch");
|
||||
value = valueOf(tokenId);
|
||||
}
|
||||
|
||||
/// Internal function to mint a new reputation NFT
|
||||
function mint(uint256 value) internal returns (uint256 tokenId) {
|
||||
// Generate a new (sequential) ID for the token
|
||||
tokenId = nextTokenId++;
|
||||
// Mint the token, initially to be owned by the current contract.
|
||||
_mint(address(this), tokenId);
|
||||
tokenValues[tokenId] = value;
|
||||
// Keep track of total value minted
|
||||
// TODO: More sophisticated logic can compute total _available_, _active_ reputation
|
||||
totalValue += value;
|
||||
}
|
||||
|
||||
/// Internal function to transfer value from one reputation token to another
|
||||
function transferValueFrom(
|
||||
uint256 fromTokenId,
|
||||
uint256 toTokenId,
|
||||
uint256 amount
|
||||
) internal {
|
||||
require(amount >= 0, "Value transfer amount must be positive");
|
||||
require(
|
||||
valueOf(fromTokenId) >= amount,
|
||||
"Source token has insufficient value"
|
||||
);
|
||||
tokenValues[fromTokenId] -= amount;
|
||||
tokenValues[toTokenId] += amount;
|
||||
}
|
||||
event ValidationPoolResolved(bool votePasses);
|
||||
|
||||
/// Accept fee to initiate a validation pool
|
||||
/// TODO: Rather than accept author as a parameter, accept a reference to a forum post
|
||||
/// TODO: Handle multiple authors
|
||||
/// TODO: Constrain duration to allowable range
|
||||
function initiateValidationPool(
|
||||
address author,
|
||||
|
@ -112,42 +68,30 @@ contract DAO is ERC721("Reputation", "REP"), ReputationHolder {
|
|||
// 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
|
||||
pool.tokenIdFor = mint(msg.value / 2);
|
||||
pool.tokenIdAgainst = mint(msg.value / 2);
|
||||
stake(pool, address(this), true, pool.tokenIdFor);
|
||||
stake(pool, address(this), false, pool.tokenIdAgainst);
|
||||
_mint(address(this), msg.value);
|
||||
_stake(pool, author, msg.value / 2, true);
|
||||
_stake(pool, author, msg.value / 2, false);
|
||||
emit ValidationPoolInitiated(poolIndex);
|
||||
}
|
||||
|
||||
/// Internal function to register a stake for/against a validation pool
|
||||
function stake(
|
||||
function _stake(
|
||||
ValidationPool storage pool,
|
||||
address sender,
|
||||
bool inFavor,
|
||||
uint256 tokenId
|
||||
uint256 amount,
|
||||
bool inFavor
|
||||
) internal {
|
||||
require(block.timestamp <= pool.endTime, "Pool end time has passed");
|
||||
Stake storage _stake = pool.stakes[pool.stakeCount++];
|
||||
_stake.sender = sender;
|
||||
_stake.inFavor = inFavor;
|
||||
_stake.amount = verifiedValueOf(sender, tokenId);
|
||||
_stake.tokenId = tokenId;
|
||||
Stake storage s = pool.stakes[pool.stakeCount++];
|
||||
s.sender = sender;
|
||||
s.inFavor = inFavor;
|
||||
s.amount = amount;
|
||||
}
|
||||
|
||||
/// Accept reputation stakes toward a validation pool
|
||||
function onERC721Received(
|
||||
address,
|
||||
address from,
|
||||
uint256 tokenId,
|
||||
bytes calldata data
|
||||
) public override returns (bytes4) {
|
||||
// `data` needs to encode the target validation pool, and the for/again boolean
|
||||
StakeData memory stakeParameters = abi.decode(data, (StakeData));
|
||||
ValidationPool storage pool = validationPools[
|
||||
stakeParameters.poolIndex
|
||||
];
|
||||
stake(pool, from, stakeParameters.inFavor, tokenId);
|
||||
return super.onERC721Received.selector;
|
||||
function stake(uint poolIndex, uint256 amount, bool inFavor) public {
|
||||
ValidationPool storage pool = validationPools[poolIndex];
|
||||
_stake(pool, msg.sender, amount, inFavor);
|
||||
}
|
||||
|
||||
/// Evaluate outcome of a validation pool
|
||||
|
@ -158,15 +102,15 @@ contract DAO is ERC721("Reputation", "REP"), ReputationHolder {
|
|||
"Pool end time has not yet arrived"
|
||||
);
|
||||
require(pool.resolved == false, "Pool is already resolved");
|
||||
uint256 amountFor;
|
||||
uint256 amountAgainst;
|
||||
Stake storage _stake;
|
||||
uint256 stakedFor;
|
||||
uint256 stakedAgainst;
|
||||
Stake storage s;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
_stake = pool.stakes[i];
|
||||
if (_stake.inFavor) {
|
||||
amountFor += _stake.amount;
|
||||
s = pool.stakes[i];
|
||||
if (s.inFavor) {
|
||||
stakedFor += s.amount;
|
||||
} else {
|
||||
amountAgainst += _stake.amount;
|
||||
stakedAgainst += s.amount;
|
||||
}
|
||||
}
|
||||
// Here we assume a quorum of 0
|
||||
|
@ -174,82 +118,30 @@ contract DAO is ERC721("Reputation", "REP"), ReputationHolder {
|
|||
// A tie is resolved in favor of the validation pool.
|
||||
// This is especially important so that the DAO's first pool can pass,
|
||||
// when no reputation has yet been minted.
|
||||
votePasses = amountFor >= amountAgainst;
|
||||
votePasses = stakedFor >= stakedAgainst;
|
||||
if (votePasses && !isMember[pool.author]) {
|
||||
members[memberCount++] = pool.author;
|
||||
isMember[pool.author] = true;
|
||||
}
|
||||
pool.resolved = true;
|
||||
emit ValidationPoolResolved(votePasses, pool.tokenIdFor);
|
||||
// If the outcome is true, value of all stakes against the pool should be distributed among the stakes in favor.
|
||||
// If the outcome is false, value of all stakes for the pool should be distributed among the stakes against.
|
||||
uint256 amountFromWinners;
|
||||
uint256 amountFromLosers;
|
||||
// Collect the reputation from the losing stakes
|
||||
emit ValidationPoolResolved(votePasses);
|
||||
// Value of losing stakes should be distributed among winners, in proportion to their stakes
|
||||
uint256 amountFromWinners = votePasses ? stakedFor : stakedAgainst;
|
||||
uint256 amountFromLosers = votePasses ? stakedAgainst : stakedFor;
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
_stake = pool.stakes[i];
|
||||
if (votePasses && !_stake.inFavor) {
|
||||
// Transfer value to the token that was minted in favor
|
||||
amountFromLosers += _stake.amount;
|
||||
transferValueFrom(
|
||||
_stake.tokenId,
|
||||
pool.tokenIdFor,
|
||||
_stake.amount
|
||||
);
|
||||
} else if (!votePasses && _stake.inFavor) {
|
||||
// Transfer value to the token that was minted against
|
||||
amountFromLosers += _stake.amount;
|
||||
transferValueFrom(
|
||||
_stake.tokenId,
|
||||
pool.tokenIdAgainst,
|
||||
_stake.amount
|
||||
);
|
||||
} else if (
|
||||
votePasses &&
|
||||
_stake.inFavor &&
|
||||
_stake.tokenId != pool.tokenIdFor
|
||||
) {
|
||||
// Tally the total value of winning stakes
|
||||
amountFromWinners += _stake.amount;
|
||||
} else if (
|
||||
!votePasses &&
|
||||
!_stake.inFavor &&
|
||||
_stake.tokenId != pool.tokenIdAgainst
|
||||
) {
|
||||
// Tally the total value of winning stakes
|
||||
amountFromWinners += _stake.amount;
|
||||
}
|
||||
}
|
||||
// Distribute reputation from losing stakes to winning stakes
|
||||
for (uint i = 0; i < pool.stakeCount; i++) {
|
||||
_stake = pool.stakes[i];
|
||||
if (
|
||||
votePasses &&
|
||||
_stake.inFavor &&
|
||||
_stake.tokenId != pool.tokenIdFor
|
||||
) {
|
||||
uint256 reward = (amountFromLosers * _stake.amount) /
|
||||
s = pool.stakes[i];
|
||||
if (votePasses == s.inFavor) {
|
||||
uint256 reward = (amountFromLosers * s.amount) /
|
||||
amountFromWinners;
|
||||
transferValueFrom(pool.tokenIdAgainst, _stake.tokenId, reward);
|
||||
} else if (
|
||||
!votePasses &&
|
||||
!_stake.inFavor &&
|
||||
_stake.tokenId != pool.tokenIdAgainst
|
||||
) {
|
||||
uint256 reward = (amountFromLosers * _stake.amount) /
|
||||
amountFromWinners;
|
||||
transferValueFrom(pool.tokenIdFor, _stake.tokenId, reward);
|
||||
_transfer(address(this), s.sender, s.amount + reward);
|
||||
}
|
||||
}
|
||||
// Transfer minted reputation to the author
|
||||
// TODO: Handle multiple authors
|
||||
if (votePasses) {
|
||||
_transfer(address(this), pool.author, pool.tokenIdFor);
|
||||
}
|
||||
// Distribute fee proportionatly among all reputation holders
|
||||
for (uint tokenId = 0; tokenId < nextTokenId; tokenId++) {
|
||||
address recipient = ownerOf(tokenId);
|
||||
// Don't count tokens owned by this contract, as these are part of validation pools in progress
|
||||
if (recipient == address(this)) continue;
|
||||
uint256 share = (pool.fee * valueOf(tokenId)) / totalValue;
|
||||
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
|
||||
payable(recipient).transfer(share);
|
||||
payable(member).transfer(share);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import "./ReputationHolder.sol";
|
|||
|
||||
struct AvailabilityStake {
|
||||
address worker;
|
||||
uint256 tokenId;
|
||||
uint256 amount;
|
||||
bool assigned;
|
||||
}
|
||||
|
@ -46,18 +45,13 @@ contract Work1 is ReputationHolder {
|
|||
}
|
||||
|
||||
/// Accept availability stakes as reputation token transfer
|
||||
function onERC721Received(
|
||||
address,
|
||||
address from,
|
||||
uint256 tokenId,
|
||||
bytes calldata
|
||||
) public override returns (bytes4) {
|
||||
function stakeAvailability(uint256 amount) public {
|
||||
require(dao.balanceOf(msg.sender) >= amount);
|
||||
AvailabilityStake storage stake = stakes[stakeCount++];
|
||||
stake.worker = from;
|
||||
stake.tokenId = tokenId;
|
||||
stake.amount = dao.verifiedValueOf(from, tokenId);
|
||||
// TODO: use `data` parameter to include stake options such as duration
|
||||
return super.onERC721Received.selector;
|
||||
stake.worker = msg.sender;
|
||||
stake.amount = amount;
|
||||
// TODO: Token locking
|
||||
// TODO: Duration
|
||||
}
|
||||
|
||||
/// Select a worker randomly from among the available workers, weighted by amount staked
|
||||
|
|
|
@ -38,7 +38,8 @@ describe('DAO', () => {
|
|||
const init = () => dao.initiateValidationPool(account1, POOL_DURATION, { value: fee });
|
||||
await expect(init()).to.emit(dao, 'ValidationPoolInitiated').withArgs(0);
|
||||
expect(await dao.validationPoolCount()).to.equal(1);
|
||||
expect(await dao.ownerOf(0)).to.equal(dao.target);
|
||||
expect(await dao.memberCount()).to.equal(0);
|
||||
expect(await dao.balanceOf(account1)).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not be able to initiate a validation pool without a fee', async () => {
|
||||
|
@ -65,13 +66,14 @@ describe('DAO', () => {
|
|||
|
||||
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, 0);
|
||||
expect(await dao.ownerOf(0)).to.equal(account1);
|
||||
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 evaluate outcome more than once', async () => {
|
||||
time.increase(POOL_DURATION + 1);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(true, 0);
|
||||
await expect(dao.evaluateOutcome(0)).to.emit(dao, 'ValidationPoolResolved').withArgs(true);
|
||||
await expect(dao.evaluateOutcome(0)).to.be.revertedWith('Pool is already resolved');
|
||||
});
|
||||
|
||||
|
@ -80,8 +82,10 @@ describe('DAO', () => {
|
|||
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(1)).to.emit(dao, 'ValidationPoolResolved').withArgs(true, 2);
|
||||
expect(await dao.ownerOf(2)).to.equal(account1);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue